relationalFramework.RelationalRule.java Source code

Java tutorial

Introduction

Here is the source code for relationalFramework.RelationalRule.java

Source

/*
 *    This file is part of the CERRLA algorithm
 *
 *    CERRLA is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    CERRLA is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with CERRLA. If not, see <http://www.gnu.org/licenses/>.
 */

/*
 *    src/relationalFramework/RelationalRule.java
 *    Copyright (C) 2012 Samuel Sarjant
 */
package relationalFramework;

import relationalFramework.RelationalPredicate;
import relationalFramework.RelationalRule;
import relationalFramework.StateSpec;
import relationalFramework.agentObservations.RangeContext;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.collections.BidiMap;

import cerrla.LocalCrossEntropyDistribution;
import cerrla.ProgramArgument;
import cerrla.Slot;
import cerrla.modular.GeneralGoalCondition;
import cerrla.modular.PolicyItem;
import cerrla.modular.SpecificGoalCondition;

import util.ConditionComparator;
import util.MultiMap;

/**
 * A class that keeps track of the guided predicates that make up the rule
 * contained within.
 * 
 * @author Sam Sarjant
 */
public class RelationalRule implements Serializable, Comparable<RelationalRule>, PolicyItem, RelationalQuery {
    private static final long serialVersionUID = -7517726681678896438L;

    /**
     * The rule has to see 5 states without changing to be considered
     * artificially LGG.
     */
    private static final int SETTLED_RULE_STATES = 50;

    /** The ancestry count of the rule (how far from the RLGG). */
    private int ancestryCount_;

    /** The constant facts in the rule conditions, if any. Excludes type conds. */
    private Collection<SpecificGoalCondition> constantCondition_;

    /** The general conditions of this rule. */
    @SuppressWarnings("unchecked")
    private final Collection<GeneralGoalCondition>[] generalConditions_ = new Collection[2];

    /** If this rule has spawned any mutant rules yet. */
    private Integer hasSpawned_ = null;

    /** The rule's internal count of value updates. */
    private int internalCount_ = 0;

    /** The rule's internal mean. */
    private double internalMean_ = 0;

    /** The rule's internal value for calculating standard deviation. */
    private double internalS_ = 0;

    /** If this slot is a mutation. */
    private boolean mutant_ = false;

    /** If the rule has mutated, the children of the mutation. */
    private Collection<RelationalRule> mutantChildren_;

    /** If the rule is a mutant, the parent of the mutant. */
    private Collection<RelationalRule> mutantParents_;

    /** The actual parameters given for this rule. */
    private List<RelationalArgument> parameters_;

    /** The query parameters associated with this rule. */
    private List<RelationalArgument> queryParams_;

    /** The collection of ranges contained within the rule. */
    private SortedSet<RangeContext> rangeContexts_;

    /** The guided predicate that defined the action. */
    private RelationalPredicate ruleAction_;

    /** The conditions of the rule. */
    private List<RelationalPredicate> rawConditions_;

    /** The conditions of the rule. */
    private List<RelationalPredicate> simplifiedConditions_;

    /** The hash value of the rule. */
    private Integer ruleHash_ = null;

    /** The number of times this rule has been used in a policy. */
    private int ruleUses_ = 0;

    /** The slot this rule was generated from. */
    private Slot slot_;

    /** The number of states seen by this rule. */
    private int statesSeen_ = 0;

    /** A map noting the types of unbound variables in this rule. */
    private MultiMap<String, String> unboundTypeMap_;

    /** A link to the containing distribution for simplification tasks. */
    private LocalCrossEntropyDistribution lced_;

    /**
     * A private constructor used only for the clone.
     * 
     * @param conditions
     *            The conditions for the rule.
     * @param action
     *            The actions for the rule.
     * @param agentObservations
     *            The agent observations to simplify the rule's conditions.
     */
    private RelationalRule(Collection<RelationalPredicate> cloneConds, RelationalPredicate ruleAction,
            LocalCrossEntropyDistribution agentObservations) {
        this(cloneConds, ruleAction, null, agentObservations);
    }

    /**
     * A constructor taking in the raw conditions and actions.
     * 
     * @param conditions
     *            The conditions for the rule.
     * @param action
     *            The actions for the rule.
     * @param parent
     *            If this rule has a parent - hence is a mutant (null if not).
     * @param lced
     *            The local cross-entropy distribution containing this rule.
     */
    public RelationalRule(Collection<RelationalPredicate> conditions, RelationalPredicate action,
            RelationalRule parent, LocalCrossEntropyDistribution lced) {
        rawConditions_ = new ArrayList<RelationalPredicate>(conditions);
        if (action != null)
            ruleAction_ = new RelationalPredicate(action);
        if (parent != null)
            setMutant(parent);
        lced_ = lced;
        slot_ = null;
        expandConditions(null);
        findConstantsAndRanges();
        ruleHash_ = hashCode();
    }

    /**
     * A constructor taking the bare minimum for a guided rule.
     * 
     * @param rule
     *            The rule this rule represents
     */
    public RelationalRule(String ruleString) {
        this(ruleString, null);
    }

    /**
     * A constructor taking a rule string and the rule generator (for
     * simplification).
     * 
     * @param ruleString
     *            The rule string.
     * @param lced
     *            The local rule generator.
     */
    public RelationalRule(String ruleString, LocalCrossEntropyDistribution lced) {
        lced_ = lced;
        String[] split = ruleString.split(StateSpec.INFERS_ACTION);
        if (split.length == 2)
            ruleAction_ = StateSpec.toRelationalPredicate(split[1].trim());
        rawConditions_ = splitConditions(split[0], ruleAction_);
        slot_ = null;
        ancestryCount_ = 0;
        expandConditions(null);
        findConstantsAndRanges();
    }

    /**
     * A relational rule constructor for specialised rules that add conditions.
     * 
     * @param baseRule
     *            The base rule to specialise.
     * @param condition
     *            The condition being added.
     */
    public RelationalRule(RelationalRule baseRule, RelationalPredicate condition) {
        lced_ = baseRule.lced_;
        rawConditions_ = new ArrayList<RelationalPredicate>(baseRule.rawConditions_.size() + 1);
        for (RelationalPredicate cond : baseRule.rawConditions_) {
            rawConditions_.add(new RelationalPredicate(cond));
        }
        ruleAction_ = new RelationalPredicate(baseRule.getAction());
        slot_ = null;
        setMutant(baseRule);
        rangeContexts_ = baseRule.rangeContexts_;

        expandConditions(condition);
        findConstantsAndRanges();
        ruleHash_ = hashCode();
    }

    /**
     * Creates the inequals tests from the terms given. Note anonymous terms are
     * special in that they aren't inequal to one-another.
     * 
     * @param ruleConditions
     *            The current conditions for the rule.
     * @param variableTerms
     *            The variable terms in the rule.
     * @param constantTerms
     *            The constant terms in the rule.
     */
    private void addInequalityTests(List<RelationalPredicate> ruleConditions,
            Collection<RelationalArgument> constantTerms) {
        List<RelationalArgument> variableTerms = new ArrayList<RelationalArgument>();
        int lastVars = 0;
        int ruleConditionIndex = 0;
        for (Object condObj : ruleConditions.toArray()) {
            RelationalPredicate cond = (RelationalPredicate) condObj;
            RelationalArgument[] condArgs = cond.getRelationalArguments();
            String[] argTypes = cond.getArgTypes();
            for (int i = 0; i < condArgs.length; i++) {
                // Is an unseen variable (but not a numerical variable)
                if (condArgs[i].isVariable() && !StateSpec.isNumberType(argTypes[i])
                        && !variableTerms.contains(condArgs[i]) && !cond.isNegated())
                    variableTerms.add(condArgs[i]);
            }

            int numTerms = variableTerms.size();
            for (int i = numTerms; i > lastVars && numTerms + constantTerms.size() >= 2; i--) {
                StringBuffer buffer = new StringBuffer("(<>");
                // (Potentially) Create an inequality test
                RelationalArgument varTermA = null;
                boolean isValid = false;
                for (ListIterator<RelationalArgument> varIter = variableTerms.listIterator(i); varIter
                        .hasPrevious();) {
                    RelationalArgument varTerm = varIter.previous();
                    if (varTermA == null) {
                        // First term
                        varTermA = varTerm;
                        buffer.append(" " + varTermA);
                    } else if (!varTermA.isNonActionVar() || !varTerm.isNonActionVar()) {
                        isValid = true;
                        buffer.append(" " + varTerm);
                    }
                }

                // Adding constant terms
                for (RelationalArgument constant : constantTerms) {
                    isValid = true;
                    buffer.append(" " + constant);
                }

                if (isValid) {
                    buffer.append(")");
                    RelationalPredicate inequality = new RelationalPredicate(StateSpec.TEST_DEFINITION,
                            new String[] { buffer.toString() });
                    ruleConditionIndex++;
                    ruleConditions.add(ruleConditionIndex, inequality);
                }
            }
            lastVars = numTerms;
            ruleConditionIndex++;
        }
    }

    /**
     * Finds the constants in the rule conditions.
     */
    private void findConstantsAndRanges() {
        rangeContexts_ = new TreeSet<RangeContext>();
        constantCondition_ = new ArrayList<SpecificGoalCondition>();
        generalConditions_[0] = new HashSet<GeneralGoalCondition>();
        generalConditions_[1] = new HashSet<GeneralGoalCondition>();

        Map<String, String> emptyReplacements = new HashMap<String, String>();
        for (RelationalPredicate cond : simplifiedConditions_) {
            // If the condition isn't a type predicate or test
            if (StateSpec.getInstance().isNotInternalPredicate(cond.getFactName())) {
                if (!StateSpec.getInstance().isTypePredicate(cond.getFactName()) && !cond.isNegated()) {
                    // If the condition doesn't contain variables - except
                    // modular variables
                    boolean isConstant = true;
                    for (RelationalArgument argument : cond.getRelationalArguments()) {
                        // If the arg isn't a constant or a goal term, the
                        // condition isn't a constant condition.
                        if (!argument.isConstant()) {
                            isConstant = false;
                            break;
                        }
                    }

                    if (isConstant) {
                        constantCondition_.add(new SpecificGoalCondition(cond));
                    }

                    // Checking for RangeContexts
                    rangeContexts_.addAll(cond.getRangeContexts());
                }

                // Adding generalised condition
                RelationalPredicate general = new RelationalPredicate(cond);
                general.replaceArguments(emptyReplacements, false, false);
                if (!cond.isNegated())
                    generalConditions_[0].add(new GeneralGoalCondition(general));
                else {
                    generalConditions_[1].add(new GeneralGoalCondition(general));
                }
            }
        }
    }

    /**
     * Initialises the variable normalisation map with the action terms, so the
     * fundamental structure of the rule doesn't change.
     * 
     * @return The normalisation map, containing any variable action terms.
     */
    private Map<String, RelationalArgument> initialiseNormalisationMap() {
        if (ruleAction_ == null)
            return null;

        Map<String, RelationalArgument> normalisationMap = new HashMap<String, RelationalArgument>();
        RelationalArgument[] actionArgs = ruleAction_.getRelationalArguments();
        for (int i = 0; i < actionArgs.length; i++) {
            if (actionArgs[i].isVariable()) {
                if (actionArgs[i].isGoalVariable() || actionArgs[i].isRangeVariable())
                    normalisationMap.put(actionArgs[i].toString(), actionArgs[i]);
                else if (!normalisationMap.containsKey(actionArgs[i].toString()))
                    normalisationMap.put(actionArgs[i].toString(), RelationalArgument.createVariableTermArg(i));
            }
        }
        return normalisationMap;
    }

    /**
     * Sets this rule as a mutant and adds the parent rule.
     * 
     * @param parent
     *            The parent rule to add.
     */
    private void setMutant(RelationalRule parent) {
        if (mutantParents_ == null) {
            mutantParents_ = new ArrayList<RelationalRule>();
            ancestryCount_ = parent.ancestryCount_ + 1;
        }
        mutant_ = true;
        mutantParents_.add(parent);
        if (parent.ancestryCount_ < ancestryCount_ - 1)
            ancestryCount_ = parent.ancestryCount_ + 1;
    }

    /**
     * Adds all parents to this rule.
     * 
     * @param parentRules
     *            The parent rules to add.
     */
    public void addParents(Collection<RelationalRule> parentRules) {
        if (mutantParents_ == null)
            mutantParents_ = new ArrayList<RelationalRule>();
        mutantParents_.addAll(parentRules);
    }

    /**
     * Clone this rule.
     * 
     * @param cloneInternalMembers
     *            If internal counters should also be cloned.
     * @param lced
     *            The distribution the clone rule is contained within.
     * @return A clone of this rule.
     */
    public RelationalRule clone(boolean cloneInternalMembers, LocalCrossEntropyDistribution lced) {
        Collection<RelationalPredicate> cloneConds = new ArrayList<RelationalPredicate>();
        for (RelationalPredicate cond : rawConditions_)
            cloneConds.add(new RelationalPredicate(cond));
        RelationalRule clone = new RelationalRule(cloneConds, ruleAction_, lced);
        if (!cloneInternalMembers)
            return clone;

        clone.hasSpawned_ = hasSpawned_;
        clone.statesSeen_ = statesSeen_;
        clone.mutant_ = mutant_;
        if (mutantParents_ != null)
            clone.mutantParents_ = new ArrayList<RelationalRule>(mutantParents_);

        if (queryParams_ != null)
            clone.queryParams_ = new ArrayList<RelationalArgument>(queryParams_);
        if (parameters_ != null)
            clone.parameters_ = new ArrayList<RelationalArgument>(parameters_);
        return clone;
    }

    @Override
    public int compareTo(RelationalRule o) {
        if (o == null)
            return -1;
        int result = Double.compare(ruleHash_, o.ruleHash_);
        if (result != 0)
            return result;

        return toString().compareTo(o.toString());
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        RelationalRule other = (RelationalRule) obj;
        if (ruleAction_ == null) {
            if (other.ruleAction_ != null)
                return false;
        } else if (!ruleAction_.equals(other.ruleAction_))
            return false;
        if (simplifiedConditions_ == null) {
            if (other.simplifiedConditions_ != null)
                return false;
        } else if (!simplifiedConditions_.containsAll(other.simplifiedConditions_))
            return false;
        else if (!other.simplifiedConditions_.containsAll(simplifiedConditions_))
            return false;
        return true;
    }

    /**
     * Expands the conditions by parsing the base definitions of the condition
     * into a fully type and inequal'ed condition. Also normalises the
     * variables.
     * 
     * @param condition
     *            An optional added condition for the raw conditions.
     */
    public void expandConditions(RelationalPredicate condition) {
        condition = preProcessRawConds(condition);

        unboundTypeMap_ = null;
        simplifiedConditions_ = new ArrayList<RelationalPredicate>();
        Collection<RelationalPredicate> simplified = null;
        if (lced_ != null && ruleAction_ != null)
            simplified = lced_.getLocalAgentObservations().simplifyRule(rawConditions_, condition, ruleAction_,
                    true);
        else
            simplified = new ArrayList<RelationalPredicate>(rawConditions_);
        if (simplified == null)
            return;
        else if (condition != null)
            rawConditions_.add(condition);

        Set<RelationalArgument> constantTerms = new TreeSet<RelationalArgument>();

        Map<String, RelationalArgument> normalisationMap = initialiseNormalisationMap();
        int normalisedIndex[] = new int[2]; // [non-action, action]
        normalisedIndex[1] = (ruleAction_ != null) ? ruleAction_.factTypes_.length : 0;

        // Unbound collections
        MultiMap<RelationalArgument, Integer> indexedUnbounds = MultiMap.createListMultiMap();

        // Scan each condition
        int index = 0;
        for (RelationalPredicate simpCond : simplified) {
            if (StateSpec.getInstance().isNotInternalPredicate(simpCond.getFactName())) {
                // Adding the terms
                RelationalArgument[] arguments = simpCond.getRelationalArguments();
                for (int i = 0; i < arguments.length; i++) {

                    if (StateSpec.isNumberType(simpCond.getArgTypes()[i])) {
                        if (arguments[i].isAnonymous()) {
                            // Swap anon for range variable
                            arguments[i] = RelationalArgument.createRangeVariable();
                        }

                        if (arguments[i].isRangeVariable()) {
                            // Numerical argument - resolve range context
                            RangeContext rc = new RangeContext(i, simpCond, ruleAction_);
                            boolean isFree = arguments[i].isFreeVariable();
                            arguments[i] = new RelationalArgument(arguments[i], rc);
                            arguments[i].setFreeVariable(isFree);
                            simpCond.getRangeContexts().add(rc);
                        } else if (arguments[i].isUnboundVariable()) {
                            // Note the unbound variable
                            normalisationMap.put(arguments[i].toString(),
                                    RelationalArgument.createUnboundVariable(normalisedIndex[0]++));
                            arguments[i] = normalisationMap.get(arguments[i].toString());
                        } else if (arguments[i].isBoundVariable()) {
                            // Normalise bound variables.
                            normalisationMap.put(arguments[i].toString(),
                                    RelationalArgument.createBoundVariable(normalisedIndex[0]++));
                            arguments[i] = normalisationMap.get(arguments[i].toString());
                        }
                    } else {
                        // Adding variable terms
                        if (arguments[i].isVariable() && normalisationMap != null) {
                            // Normalise the variable terms
                            if (!normalisationMap.containsKey(arguments[i].toString())) {
                                if (arguments[i].isGoalVariable())
                                    // Maintain goal conditions.
                                    normalisationMap.put(arguments[i].toString(), arguments[i]);
                                else if (arguments[i].isUnboundVariable()) {
                                    // Note the unbound variable
                                    normalisationMap.put(arguments[i].toString(),
                                            RelationalArgument.createUnboundVariable(normalisedIndex[0]++));
                                } else if (arguments[i].isBoundVariable()) {
                                    // Normalise bound variables.
                                    normalisationMap.put(arguments[i].toString(),
                                            RelationalArgument.createBoundVariable(normalisedIndex[0]++));
                                } else {
                                    normalisationMap.put(arguments[i].toString(),
                                            RelationalArgument.createVariableTermArg(normalisedIndex[1]++));
                                }
                            }
                            arguments[i] = normalisationMap.get(arguments[i].toString());

                            // Note the free variables.
                            if (arguments[i].isFreeVariable() && !arguments[i].isAnonymous())
                                indexedUnbounds.put(arguments[i], index);
                        } else if (arguments[i].isAnonymous()
                                && ProgramArgument.USING_UNBOUND_VARS.booleanValue()) {
                            // If the argument is anonymous, convert it to an
                            // unbound variable.
                            arguments[i] = RelationalArgument.createUnboundVariable(normalisedIndex[0]++);
                        } else if (arguments[i].isConstant())
                            // Adding constant terms
                            constantTerms.add(arguments[i]);
                    }
                }
                simpCond = new RelationalPredicate(simpCond, arguments, simpCond.isNegated());

                simplifiedConditions_.add(simpCond);
                index++;
            } else if (simpCond.getFactName().equals(StateSpec.GOALARGS_PRED)) {
                simplifiedConditions_.add(simpCond);
                index++;
            }
        }

        if (ruleAction_ != null) {
            Map<RelationalArgument, RelationalArgument> relArgMap = new HashMap<RelationalArgument, RelationalArgument>();
            for (String arg : normalisationMap.keySet()) {
                relArgMap.put(new RelationalArgument(arg), normalisationMap.get(arg.toString()));
            }
            ruleAction_.replaceArguments(relArgMap, true, true);
        }

        Collections.sort(simplifiedConditions_, ConditionComparator.getInstance());
        // Adding the inequality predicates
        addInequalityTests(simplifiedConditions_, constantTerms);

        Integer oldHash = ruleHash_;
        ruleHash_ = null;
        Integer newHash = hashCode();
        if (!newHash.equals(oldHash))
            statesSeen_ = 0;
    }

    /**
     * Swaps all occurrences of anonymous variables in the raw conditions (and
     * the added condition, if applicable) for a unique unbound variable. This
     * also binds any necessary raw conds to non action variables in the
     * optional added condition.
     * 
     * @param condition
     *            An optional condition to swap anon terms for.
     */
    private RelationalPredicate preProcessRawConds(RelationalPredicate condition) {
        if (ruleAction_ == null)
            return condition;

        // Check free variables for ranges
        int unboundIndex = 0;
        for (RelationalPredicate cond : rawConditions_) {
            for (RelationalArgument arg : cond.getActualArguments()) {
                if (ruleAction_.containsArg(arg) && arg.isVariable())
                    arg.setFreeVariable(false);
            }
            if (ProgramArgument.USING_UNBOUND_VARS.booleanValue())
                unboundIndex = cond.replaceAnonymousWithUnbound(unboundIndex);
        }

        if (condition != null) {
            for (RelationalArgument arg : condition.getActualArguments()) {
                if (ruleAction_.containsArg(arg) && arg.isVariable())
                    arg.setFreeVariable(false);
            }
            if (ProgramArgument.USING_UNBOUND_VARS.booleanValue())
                condition.replaceAnonymousWithUnbound(unboundIndex);
        }

        RelationalArgument[] actionArgs = ruleAction_.getActualArguments();
        for (RelationalArgument arg : actionArgs) {
            if (arg.isVariable())
                arg.setFreeVariable(false);
        }

        return condition;
    }

    public RelationalPredicate getAction() {
        return ruleAction_;
    }

    /**
     * Gets the action predicate.
     * 
     * @return The action predicate for the action.
     */
    public String getActionPredicate() {
        return ruleAction_.getFactName();
    }

    public RelationalArgument[] getActionTerms() {
        return ruleAction_.getRelationalArguments();
    }

    public int getAncestryCount() {
        return ancestryCount_;
    }

    public Collection<RelationalRule> getChildrenRules() {
        return mutantChildren_;
    }

    /**
     * Gets the conditions in a sorted order, including the inequals predicate.
     * 
     * @param withoutInequals
     *            If inequals predicates should be removed.
     * @return The conditions of the rule.
     */
    public List<RelationalPredicate> getSimplifiedConditions(boolean withoutInequals) {
        if (withoutInequals) {
            List<RelationalPredicate> conds = new ArrayList<RelationalPredicate>(simplifiedConditions_.size());

            for (RelationalPredicate cond : simplifiedConditions_) {
                if (!cond.getFactName().equals("test"))
                    conds.add(cond);
            }
            return conds;
        }
        return new ArrayList<RelationalPredicate>(simplifiedConditions_);
    }

    public Collection<RelationalPredicate> getRawConditions(boolean withoutInequals) {
        if (withoutInequals) {
            List<RelationalPredicate> conds = new ArrayList<RelationalPredicate>(rawConditions_.size());

            for (RelationalPredicate cond : rawConditions_) {
                if (!cond.getFactName().equals("test"))
                    conds.add(cond);
            }
            return conds;
        }
        return new ArrayList<RelationalPredicate>(rawConditions_);
    }

    public Collection<GeneralGoalCondition>[] getGeneralisedConditions() {
        return generalConditions_;
    }

    /**
     * Gets the internal mean for the rule.
     * 
     * @return The rule's internal mean.
     */
    public double getInternalMean() {
        return internalMean_;
    }

    /**
     * Gets the internal SD for the rule (which uses the count).
     * 
     * @return The internal standard deviation.
     */
    public double getInternalSD() {
        if (internalCount_ <= 1)
            return 0;
        return Math.sqrt(internalS_ / (internalCount_ - 1));
    }

    public List<RelationalArgument> getParameters() {
        return parameters_;
    }

    public Collection<RelationalRule> getParentRules() {
        return mutantParents_;
    }

    public List<RelationalArgument> getQueryParameters() {
        return queryParams_;
    }

    public SortedSet<RangeContext> getRangeContexts() {
        return rangeContexts_;
    }

    /**
     * Gets the parameter replacement for the query parameter if one exists.
     * 
     * @param queryParam
     *            The query parameter to replace.
     * @return The replacement parameter or the original variable if none given.
     */
    public RelationalArgument getReplacementParameter(RelationalArgument queryParam) {
        if (parameters_ == null)
            return queryParam;

        return parameters_.get(queryParams_.indexOf(queryParam));
    }

    public Slot getSlot() {
        return slot_;
    }

    public Collection<SpecificGoalCondition> getSpecificSubGoals() {
        return constantCondition_;
    }

    public String getStringConditions() {
        return StateSpec.conditionsToString(simplifiedConditions_);
    }

    /**
     * Gets the unbound type conditions present in the rule. These allow
     * guidance towards anonymous variable specialisation.
     * 
     * @return A map of unbound variables to type predicate names.
     */
    public MultiMap<String, String> getUnboundTypeConditions() {
        if (unboundTypeMap_ != null)
            return unboundTypeMap_;

        // Initialise the type map.
        unboundTypeMap_ = MultiMap.createSortedSetMultiMap();
        for (RelationalPredicate condition : rawConditions_) {
            // Only types of unbound variables
            RelationalArgument[] args = condition.getRelationalArguments();
            String[] types = condition.getArgTypes();
            for (int i = 0; i < args.length; i++) {
                if (args[i].isNonActionVar())
                    unboundTypeMap_.put(args[i].toString(), types[i]);
            }
        }
        return unboundTypeMap_;
    }

    public int getUses() {
        return ruleUses_;
    }

    /**
     * Grounds this rule into a rule without modular parameters by swapping the
     * rule conditions for the parameters.
     * 
     * @param paramReplacements
     *            The replacement map for goal terms.
     * @return A cloned, but modularly grounded, rule.
     */
    public RelationalRule groundModular(Map<String, String> paramReplacements) {
        if (paramReplacements == null || paramReplacements.isEmpty()) {
            return clone(false, lced_);
        }

        List<RelationalPredicate> groundConditions = new ArrayList<RelationalPredicate>(rawConditions_.size());
        for (RelationalPredicate ruleCond : getRawConditions(true)) {
            RelationalPredicate groundCond = new RelationalPredicate(ruleCond);
            groundCond.replaceArguments(paramReplacements, true, false);
            groundConditions.add(groundCond);
        }
        RelationalPredicate groundAction = new RelationalPredicate(ruleAction_);
        groundAction.replaceArguments(paramReplacements, true, false);

        RelationalRule groundRule = new RelationalRule(groundConditions, groundAction, null, lced_);
        groundRule.expandConditions(null);
        groundRule.findConstantsAndRanges();

        return groundRule;
    }

    @Override
    public int hashCode() {
        if (ruleHash_ != null)
            return ruleHash_;

        // Calculate the rule hash.
        final int prime = 31;
        ruleHash_ = 1;
        // Rule action
        ruleHash_ = prime * ruleHash_ + ((ruleAction_ == null) ? 0 : ruleAction_.hashCode());
        int conditionResult = 0;
        // Rule conditions
        if (simplifiedConditions_ != null)
            for (RelationalPredicate condition : simplifiedConditions_)
                conditionResult += condition.hashCode();
        ruleHash_ = prime * ruleHash_ + conditionResult;
        return ruleHash_;
    }

    /**
     * Checks if this rule has spawned to the current pre-goal.
     * 
     * @param preGoalHash
     *            The hash of the pre-goal.
     * @return True if the rule has spawned to this pre-goal, false otherwise.
     */
    public boolean hasSpawned(int preGoalHash) {
        if (hasSpawned_ == null)
            return false;
        return hasSpawned_.equals(preGoalHash);
    }

    public void incrementRuleUses() {
        ruleUses_++;
    }

    /**
     * Increments the state seen counter, if necessary.
     */
    public void incrementStatesCovered() {
        if (statesSeen_ <= SETTLED_RULE_STATES)
            statesSeen_++;
    }

    public boolean isMutant() {
        return mutant_;
    }

    /**
     * If this rule was recently modified (so states seen is reset).
     * 
     * @return True if the rule has recently changed/been created.
     */
    public boolean isRecentlyModified() {
        if (statesSeen_ <= 1)
            return true;
        return false;
    }

    /**
     * Returns true if the rule has no parents.
     * 
     * @return True if the rule now has no parents.
     */
    public boolean isWithoutParents() {
        return (mutantParents_ == null) || (mutantParents_.isEmpty());
    }

    /**
     * Removes the fact that this rule is a mutant and removes any parents.
     */
    public void removeMutation() {
        mutant_ = false;
        mutantParents_ = null;
    }

    public void removeParameters() {
        parameters_ = null;
    }

    /**
     * Removes a parent rule from this rule, possibly nullifying the set of
     * parent rules for this rule.
     * 
     * @param parent
     *            The parent rule to remove.
     */
    public void removeParent(RelationalRule parent) {
        mutantParents_.remove(parent);
        if (mutantParents_.isEmpty())
            mutantParents_ = null;
    }

    /**
     * Removes a group of parents from a rule.
     * 
     * @param parents
     *            The group of parents to remove.
     */
    public void removeParents(Collection<RelationalRule> parents) {
        for (RelationalRule parent : parents)
            removeParent(parent);
    }

    /**
     * Sets the new action terms.
     * 
     * @param terms
     *            The new action terms.
     * @return True if the action changed as a result of this.
     */
    public boolean setActionTerms(RelationalArgument[] terms) {
        boolean changed = !Arrays.equals(ruleAction_.getRelationalArguments(), terms);
        if (changed) {
            statesSeen_ = 0;
            ruleAction_ = new RelationalPredicate(ruleAction_, terms);
        }
        return changed;
    }

    public void setChildren(Collection<RelationalRule> children) {
        mutantChildren_ = children;
    }

    /**
     * Sets the conditions of this guided rule, if the conditions are valid.
     * 
     * @param conditions
     *            The conditions for the guided rule.
     * @return True if the newly set conditions are different from the old.
     */
    public boolean setConditions(Collection<RelationalPredicate> conditions) {
        // If the conditions are the same, return true.
        if (conditions.equals(rawConditions_)) {
            return false;
        }

        // Reset the states seen, as the rule has changed.
        hasSpawned_ = null;
        rawConditions_ = new ArrayList<RelationalPredicate>(conditions);
        expandConditions(null);
        findConstantsAndRanges();
        return true;
    }

    /**
     * Sets the parameters of the the query parameters (given by the value) to
     * the values.
     * 
     * @param parameterMap
     *            The map of parameters (a -> ?G_0).
     */
    @Override
    public void setParameters(BidiMap parameterMap) {
        if (parameterMap == null || parameterMap.isEmpty()) {
            parameters_ = null;
            return;
        }

        // Creating new query params if necessary (shouldn't be)
        boolean hasQueryParams = queryParams_ != null;
        if (!hasQueryParams) {
            queryParams_ = new ArrayList<RelationalArgument>(parameterMap.size());
            for (int i = 0; i < parameterMap.size(); i++)
                queryParams_.add(RelationalArgument.createGoalTerm(i));
        }

        // Setting the parameters
        parameters_ = new ArrayList<RelationalArgument>(parameterMap.size());
        for (RelationalArgument queryParam : queryParams_)
            parameters_.add((RelationalArgument) parameterMap.getKey(queryParam));

        if (!hasQueryParams)
            findConstantsAndRanges();
    }

    /**
     * Sets this rule's query parameters (replaceable variables). Generally
     * these are just goal args.
     * 
     * @param queryParameters
     *            The parameters to set.
     * @param True
     *            if the query parameters changed as a result of this call.
     */
    public boolean setQueryParams(List<RelationalArgument> queryParameters) {
        boolean changed = false;
        if (queryParameters != null) {
            if (!queryParameters.equals(queryParams_))
                changed = true;
            queryParams_ = new ArrayList<RelationalArgument>(queryParameters);
        } else {
            queryParams_ = null;
            changed = true;
        }

        if (changed)
            statesSeen_ = 0;

        return changed;
    }

    public void setSlot(Slot slot) {
        slot_ = slot;
    }

    /**
     * Sets the rules spawned to this pre-goal hash, or null if unspawned.
     * 
     * @param preGoalHash
     *            The hash of the pre-goal the rules spawned to.
     */
    public void setSpawned(Integer preGoalHash) {
        hasSpawned_ = preGoalHash;
    }

    @Override
    public boolean shouldRegenerate() {
        // Never regenerate.
        return false;
    }

    @Override
    public int size() {
        return 1;
    }

    /**
     * Outputs the rule in a simplified, but essentially equivalent (assuming
     * inequality and type definitions) format.
     * 
     * @return A nice, shortened version of the rule.
     */
    @Override
    public String toNiceString() {
        return toNiceString(null);
    }

    /**
     * Outputs the rule in a simplified, but essentially equivalent (assuming
     * inequality and type definitions) format. Includes a replacement map to
     * modify the goal parameters.
     * 
     * @param paramReplacements
     *            A (possibly null) replacement map for replacing goal
     *            variables.
     * @return A nice, shortened version of the rule.
     */
    public String toNiceString(Map<RelationalArgument, RelationalArgument> paramReplacements) {
        StringBuffer niceString = new StringBuffer();

        // Run through each condition, adding regular conditions and
        // non-standard type predicates
        Collection<RelationalPredicate> standardType = new HashSet<RelationalPredicate>();
        for (RelationalPredicate stringFact : simplifiedConditions_) {

            if (StateSpec.getInstance().isTypePredicate(stringFact.getFactName())) {
                // If a type predicate, only add it if it's non-standard
                if (!standardType.contains(stringFact))
                    niceString.append(stringFact.toNiceString(paramReplacements) + " ");
            } else if (!stringFact.getFactName().equals("test")) {
                // If not a type or test, add the fact.
                niceString.append(stringFact.toNiceString(paramReplacements) + " ");

                // Scan the arguments and extract the standard type preds
                for (int i = 0; i < stringFact.getArgTypes().length; i++) {
                    // If the type isn't a number and isn't anonymous, add it to
                    // the standards
                    if (!stringFact.getArguments()[i].equals("?")
                            && !StateSpec.isNumberType(stringFact.getArgTypes()[i])) {
                        RelationalPredicate type = new RelationalPredicate(
                                StateSpec.getInstance().getPredicateByName(stringFact.getArgTypes()[i]),
                                new String[] { stringFact.getArguments()[i] });
                        standardType.add(type);
                    }
                }
            }
        }
        niceString.append("=> " + ruleAction_.toNiceString(paramReplacements));

        return niceString.toString();
    }

    @Override
    public String toString() {
        return getStringConditions() + " => " + ruleAction_;
    }

    /**
     * Updates the internal value of this rule, adjusting the rule mean and SD
     * appropriately.
     * 
     * @param value
     *            The value the rule attained as part of a policy.
     */
    public void updateInternalValue(double value) {
        internalCount_++;

        if (internalCount_ == 1) {
            internalMean_ = value;
            internalS_ = 0;
        } else {
            double newMean = internalMean_ + (value - internalMean_) / (internalCount_);
            double newS = internalS_ + (value - internalMean_) * (value - newMean);
            internalMean_ = newMean;
            internalS_ = newS;
        }
    }

    /**
     * Splits a conditions string into individual facts.
     * 
     * @param conditionString
     *            The condition string to be split.
     * @return The facts of the string in segments.
     */
    public static List<RelationalPredicate> splitConditions(String conditionString, RelationalPredicate action) {
        List<RelationalPredicate> conds = null;
        conds = new ArrayList<RelationalPredicate>();
        Pattern p = Pattern.compile("\\(.+?\\)(?=( (\\(|$))|$)");
        Matcher m = p.matcher(conditionString);
        while (m.find()) {
            RelationalPredicate cond = StateSpec.toRelationalPredicate(m.group());
            if (cond.isNumerical() && action != null)
                cond.configureRangeContexts(action);
            conds.add(cond);
        }
        return conds;
    }

    public boolean isLegal() {
        return !simplifiedConditions_.isEmpty();
    }
}