All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ggp.base.util.gdl.transforms.ConjunctDualizer Maven / Gradle / Ivy

The newest version!
package org.ggp.base.util.gdl.transforms;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import org.ggp.base.util.Immutables;
import org.ggp.base.util.gdl.GdlUtils;
import org.ggp.base.util.gdl.GdlVisitor;
import org.ggp.base.util.gdl.GdlVisitors;
import org.ggp.base.util.gdl.grammar.Gdl;
import org.ggp.base.util.gdl.grammar.GdlConstant;
import org.ggp.base.util.gdl.grammar.GdlDistinct;
import org.ggp.base.util.gdl.grammar.GdlLiteral;
import org.ggp.base.util.gdl.grammar.GdlNot;
import org.ggp.base.util.gdl.grammar.GdlOr;
import org.ggp.base.util.gdl.grammar.GdlPool;
import org.ggp.base.util.gdl.grammar.GdlRule;
import org.ggp.base.util.gdl.grammar.GdlSentence;
import org.ggp.base.util.gdl.grammar.GdlTerm;
import org.ggp.base.util.gdl.grammar.GdlVariable;
import org.ggp.base.util.gdl.model.SentenceForm;
import org.ggp.base.util.gdl.model.SimpleSentenceForm;

import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multiset;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;

/**
 * This transformation changes a game description into an equivalent
 * game description in which each rule has at most two conjuncts.
 *
 * This would normally cause lots of sentence types that are
 * duplicates of each other, so this also deduplicates those.
 *
 * This preserves the ordering in which sentences work in prover
 * implementations like the ProverStateMachine.
 *
 */
public class ConjunctDualizer {
    public static List apply(List description) {
        description = GdlCleaner.run(description);
        description = DeORer.run(description);
        description = DistinctAndNotMover.run(description);

        SentenceNameFactory snf = SentenceNameFactory.create(description);

        List newRules = Lists.newArrayList();
        for (Gdl gdl : description) {
            if (gdl instanceof GdlSentence) {
                newRules.add(gdl);
            } else {
                GdlRule rule = (GdlRule) gdl;
                newRules.addAll(splitRule(rule, snf));
            }
        }

        //Repeat deduplication until convergence
        //TODO: Perf test to see if this is slow
        while (true) {
            List mergedRules = mergeIdenticalSingletonSentences(newRules);
            if (mergedRules.equals(newRules)) {
                break;
            }
            newRules = mergedRules;
        }
        return newRules;
    }

    private static List mergeIdenticalSingletonSentences(List rules) {
        //First just identify the singleton sentences
        Set singletonSentenceNames = getSingletonSentenceNames(rules);

        //TODO: At some point, we could actually consider handling sentences with
        //more than one name... but those wouldn't have been introduced here, and
        //would instead indicate redundancy in the original game description.

        System.out.println("Singleton sentence names: " + singletonSentenceNames);

        //TODO: Actually sort out and deduplicate these rules
        //How do we know two are duplicates?
        //1) Same type: C, C-C, C-D, or C-N
        //2) Sentence forms in the body are the same
        //3) Constants in the same places in the body
        //4) Variables in the body correspond
        //5) Variables in head are same (or one is subset of other, see below)

        //So we probably want to create some kind of key object for each of these
        //and put the rules in a multimap and see what comes up...

        //If two rules have the same key, we can merge them.
        SetMultimap rulesByRuleKey = HashMultimap.create();

        for (Gdl gdl : rules) {
            if (gdl instanceof GdlRule) {
                GdlRule rule = (GdlRule) gdl;
                if (singletonSentenceNames.contains(rule.getHead().getName())) {
                    //                    System.out.println("Making key for rule: " + rule);
                    rulesByRuleKey.put(createKey(rule), rule);
                }
            }
        }

        List deduplications = Lists.newArrayList();

        //Look for rules with matching keys; so far just report them
        for (DualizedRuleKey key : rulesByRuleKey.keySet()) {
            Set ruleSet = rulesByRuleKey.get(key);
            if (ruleSet.size() > 1) {
                System.out.println("These rules can be deduplicated:");
                for (GdlRule rule : ruleSet) {
                    System.out.println("  " + rule);
                }
                deduplications.add(DeduplicationPlan.create(ruleSet));
            }
        }
        //This looks pretty good now
        //What's next is to do the actual deduplication
        //Pick one to be canonical, remove the rules for the others,
        //and replace the others' appearances in other rules

        //What's awkward is that the order in the sentence bodies could differ
        //Also need to deal with cases where the head is not the form we expect
        //(all separate variables, no functions)

        //If the number of variables in the head differ, replace the rule for the
        //one with fewer variables (if it's a subset)

        //        if (deduplications.isEmpty()) {
        //            return rules;
        //        }
        //Apply the deduplications...
        for (DeduplicationPlan deduplication : deduplications) {
            rules = deduplication.apply(rules);
        }
        return rules;
    }

    private static class DeduplicationPlan {
        private final ImmutableSet removedSentenceNames;
        private final SentenceForm newSentenceForm;
        private final ImmutableMap, List>>
        tupleTransformations;

        private DeduplicationPlan(ImmutableSet removedSentenceNames, SentenceForm newSentenceForm,
                ImmutableMap, List>> tupleTransformations) {
            Preconditions.checkArgument(tupleTransformations.keySet().equals(removedSentenceNames));
            this.removedSentenceNames = removedSentenceNames;
            this.newSentenceForm = newSentenceForm;
            this.tupleTransformations = tupleTransformations;
        }

        public static DeduplicationPlan create(Set ruleSet) {
            //Pick one arbitrarily to be the new canonical one
            Set oldRules = Sets.newHashSet(ruleSet);
            GdlRule newRule = oldRules.iterator().next();
            oldRules.remove(newRule);
            System.out.println("Will prefer " + newRule.getHead().getName());
            return create(oldRules, newRule);
        }

        private static DeduplicationPlan create(Set oldRules, GdlRule newRule) {
            SentenceForm newSentenceForm = SimpleSentenceForm.create(newRule.getHead());
            ImmutableList headToBodyMap = getHeadToBodyMap(newRule);

            Set removedSentenceNames = Sets.newHashSet();
            Map, List>>
            tupleTransformations = Maps.newHashMap();
            for (GdlRule oldRule : oldRules) {
                GdlConstant name = oldRule.getHead().getName();
                removedSentenceNames.add(name);

                Function, List> tupleTransformation =
                        getTupleTransformation(headToBodyMap,
                                getBodyToHeadMap(oldRule));

                tupleTransformations.put(name, tupleTransformation);
            }


            return new DeduplicationPlan(
                    ImmutableSet.copyOf(removedSentenceNames),
                    newSentenceForm,
                    ImmutableMap.copyOf(tupleTransformations));
        }

        private static ImmutableList getHeadToBodyMap(GdlRule rule) {
            Map termMap = getTermMap(rule);

            List head = GdlUtils.getTupleFromSentence(rule.getHead());
            return head.stream()
                    .map(termMap::get)
                    .collect(Immutables.collectList());
        }

        private static ImmutableMap getBodyToHeadMap(GdlRule rule) {
            Map termMap = Maps.newHashMap();

            for (int conjunctNum = 0; conjunctNum < rule.getBody().size(); conjunctNum++) {
                GdlLiteral conjunct = rule.get(conjunctNum);
                if (conjunct instanceof GdlSentence || conjunct instanceof GdlNot) {
                    GdlSentence sentence = getSentence(rule, conjunctNum);
                    List bodyTuple = GdlUtils.getTupleFromSentence(sentence);
                    for (int i = 0; i < bodyTuple.size(); i++) {
                        GdlTerm term = bodyTuple.get(i);
                        TermLocator termLocator = new TermLocator(conjunctNum, i);
                        termMap.put(termLocator, term);
                    }
                } else if (conjunct instanceof GdlDistinct) {
                    GdlDistinct distinct = (GdlDistinct) conjunct;
                    termMap.put(new TermLocator(conjunctNum, 0), distinct.getArg1());
                    termMap.put(new TermLocator(conjunctNum, 1), distinct.getArg2());
                } else {
                    throw new RuntimeException();
                }
            }
            List head = GdlUtils.getTupleFromSentence(rule.getHead());

            return ImmutableMap.copyOf(Maps.transformValues(termMap, head::indexOf));
        }

        private static Map getTermMap(GdlRule rule) {
            Map termMap = Maps.newHashMap();

            for (int conjunctNum = 0; conjunctNum < rule.getBody().size(); conjunctNum++) {
                GdlLiteral conjunct = rule.get(conjunctNum);
                if (conjunct instanceof GdlSentence || conjunct instanceof GdlNot) {
                    GdlSentence sentence = getSentence(rule, conjunctNum);
                    List bodyTuple = GdlUtils.getTupleFromSentence(sentence);
                    for (int i = 0; i < bodyTuple.size(); i++) {
                        GdlTerm term = bodyTuple.get(i);
                        TermLocator termLocator = new TermLocator(conjunctNum, i);
                        termMap.putIfAbsent(term, termLocator);
                    }
                } else if (conjunct instanceof GdlDistinct) {
                    GdlDistinct distinct = (GdlDistinct) conjunct;
                    termMap.putIfAbsent(distinct.getArg1(), new TermLocator(conjunctNum, 0));
                    termMap.putIfAbsent(distinct.getArg2(), new TermLocator(conjunctNum, 1));
                } else {
                    throw new RuntimeException();
                }
            }
            return termMap;
        }

        //Require the inputs to be immutable because they're referenced by the
        //returned function, which ends up in an immutable object...
        //(Could precompute, but not worth the effort)
        private static Function, List> getTupleTransformation(
                ImmutableList headToBodyMap,
                ImmutableMap oldRuleMap) {
            return new Function, List>() {
                @Override
                public List apply(List input) {
                    List output = Lists.newArrayList();
                    for (int i = 0; i < headToBodyMap.size(); i++) {
                        TermLocator termLocator = headToBodyMap.get(i);
                        Preconditions.checkState(termLocator != null);
                        Integer oldIndex = oldRuleMap.get(termLocator);
                        Preconditions.checkState(oldIndex != null);
                        output.add(input.get(oldIndex));
                    }
                    return output;
                }
            };
        }

        public List apply(List oldRules) {
            List newRules = Lists.newArrayList();
            for (Gdl gdl : oldRules) {
                if (gdl instanceof GdlRule) {
                    GdlRule rule = (GdlRule) gdl;
                    apply(rule).ifPresent(newRules::add);
                } else {
                    newRules.add(gdl);
                }
            }
            return newRules;
        }

        private Optional apply(GdlRule rule) {
            //If the head is a removed type, get rid of the rule
            if (removedSentenceNames.contains(rule.getHead().getName())) {
                return Optional.empty();
            }
            //Otherwise, we have to transform body elements if necessary...
            List newBody = rule.getBody().stream()
                    .map(this::applyToConjunct)
                    .collect(Collectors.toList());
            return Optional.of(GdlPool.getRule(rule.getHead(), newBody));
        }

        private GdlLiteral applyToConjunct(GdlLiteral literal) {
            if (literal instanceof GdlSentence) {
                return applyToSentence((GdlSentence) literal);
            } else if (literal instanceof GdlNot) {
                GdlSentence sentence = (GdlSentence) ((GdlNot) literal).getBody();
                return GdlPool.getNot(applyToSentence(sentence));
            } else if (literal instanceof GdlDistinct) {
                return literal;
            } else if (literal instanceof GdlOr) {
                throw new RuntimeException("The DeORer was supposed to have been run");
            }
            throw new RuntimeException("Unknown GdlLiteral type for literal " + literal);
        }

        private GdlLiteral applyToSentence(GdlSentence sentence) {
            if (!removedSentenceNames.contains(sentence.getName())) {
                return sentence;
            }
            //TODO: Figure out how to replace the sentence
            List oldTuple = GdlUtils.getTupleFromSentence(sentence);
            List newTuple = applyToTuple(sentence.getName(), oldTuple);
            return newSentenceForm.getSentenceFromTuple(newTuple);
        }

        private List applyToTuple(GdlConstant oldSentenceName, List oldTuple) {
            Function, List> tupleTransformation = tupleTransformations.get(oldSentenceName);
            return tupleTransformation.apply(oldTuple);
        }

    }

    //TODO: Write more tests for this, around constants in the head especially
    private static DualizedRuleKey createKey(GdlRule rule) {
        if (rule.arity() == 1) {
            return SingleConjunctRuleKey.create(rule);
        }
        GdlLiteral secondConjunct = rule.get(1);
        if (secondConjunct instanceof GdlSentence || secondConjunct instanceof GdlNot) {
            return DoubleConjunctRuleKey.create(rule);
            //        } else if (secondConjunct instanceof GdlNot) {
            //            return NegativeConjunctRuleKey.create(rule);
        } else if (secondConjunct instanceof GdlDistinct) {
            return DistinctConjunctRuleKey.create(rule);
        }
        throw new RuntimeException ("Unexpected rule format for " + rule);
    }

    private static interface DualizedRuleKey {
        //We just care about equals/hashCode
    }

    private static class SingleConjunctRuleKey implements DualizedRuleKey {
        private final boolean isNegated;
        private final SentenceForm conjunctForm;
        private final ImmutableMap constants;
        private final ImmutableMap variableEqualities;
        //Specify which variables are or are not in the head
        private final ImmutableSet headVarFirstAppearances;

        private SingleConjunctRuleKey(boolean isNegated, SentenceForm conjunctForm,
                ImmutableMap constants, ImmutableMap variableEqualities,
                ImmutableSet headVarFirstAppearances) {
            this.isNegated = isNegated;
            this.conjunctForm = conjunctForm;
            this.constants = constants;
            this.variableEqualities = variableEqualities;
            this.headVarFirstAppearances = headVarFirstAppearances;
        }

        public static DualizedRuleKey create(GdlRule rule) {
            Preconditions.checkArgument(rule.arity() == 1);
            Set headVariables = GdlUtils.getVariablesSet(rule.getHead());
            boolean isNegated = (rule.get(0) instanceof GdlNot);
            GdlSentence sentence = getSentence(rule, 0);
            SimpleSentenceForm form = SimpleSentenceForm.create(sentence);
            List tuple = GdlUtils.getTupleFromSentence(sentence);
            Map firstAppearances = Maps.newHashMap();
            Map constants = Maps.newHashMap();
            Map variableEqualities = Maps.newHashMap();
            Set headVarFirstAppearances = Sets.newHashSet();
            for (int i = 0; i < tuple.size(); i++) {
                GdlTerm term = tuple.get(i);
                if (term instanceof GdlConstant) {
                    constants.put(i, (GdlConstant) term);
                } else {
                    GdlVariable variable = (GdlVariable) term;
                    if (firstAppearances.containsKey(variable)) {
                        int firstAppearance = firstAppearances.get(variable);
                        variableEqualities.put(i, firstAppearance);
                    } else {
                        firstAppearances.put(variable, i);
                        if (headVariables.contains(variable)) {
                            headVarFirstAppearances.add(i);
                        }
                    }
                }
            }

            return new SingleConjunctRuleKey(isNegated,
                    form,
                    ImmutableMap.copyOf(constants),
                    ImmutableMap.copyOf(variableEqualities),
                    ImmutableSet.copyOf(headVarFirstAppearances));
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((conjunctForm == null) ? 0 : conjunctForm.hashCode());
            result = prime * result + ((constants == null) ? 0 : constants.hashCode());
            result = prime * result + ((headVarFirstAppearances == null) ? 0 : headVarFirstAppearances.hashCode());
            result = prime * result + (isNegated ? 1231 : 1237);
            result = prime * result + ((variableEqualities == null) ? 0 : variableEqualities.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            SingleConjunctRuleKey other = (SingleConjunctRuleKey) obj;
            if (conjunctForm == null) {
                if (other.conjunctForm != null) {
                    return false;
                }
            } else if (!conjunctForm.equals(other.conjunctForm)) {
                return false;
            }
            if (constants == null) {
                if (other.constants != null) {
                    return false;
                }
            } else if (!constants.equals(other.constants)) {
                return false;
            }
            if (headVarFirstAppearances == null) {
                if (other.headVarFirstAppearances != null) {
                    return false;
                }
            } else if (!headVarFirstAppearances.equals(other.headVarFirstAppearances)) {
                return false;
            }
            if (isNegated != other.isNegated) {
                return false;
            }
            if (variableEqualities == null) {
                if (other.variableEqualities != null) {
                    return false;
                }
            } else if (!variableEqualities.equals(other.variableEqualities)) {
                return false;
            }
            return true;
        }

        @Override
        public String toString() {
            return "SingleConjunctRuleKey [conjunctForm=" + conjunctForm + ", constants=" + constants
                    + ", variableEqualities=" + variableEqualities + ", headVarFirstAppearances="
                    + headVarFirstAppearances + "]";
        }
    }

    private static class TermLocator {
        private final int conjunctNum;
        private final int tupleIndex;

        private TermLocator(int conjunctNum, int tupleIndex) {
            this.conjunctNum = conjunctNum;
            this.tupleIndex = tupleIndex;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + conjunctNum;
            result = prime * result + tupleIndex;
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            TermLocator other = (TermLocator) obj;
            if (conjunctNum != other.conjunctNum) {
                return false;
            }
            if (tupleIndex != other.tupleIndex) {
                return false;
            }
            return true;
        }
    }

    private static GdlSentence getSentence(GdlRule rule, int i) {
        GdlLiteral literal = rule.get(i);
        if (literal instanceof GdlSentence) {
            return (GdlSentence) literal;
        } else if (literal instanceof GdlNot) {
            return (GdlSentence) ((GdlNot) literal).getBody();
        }
        throw new RuntimeException();
    }

    private static class DoubleConjunctRuleKey implements DualizedRuleKey {
        private final boolean isFirstNegated;
        private final boolean isSecondNegated;
        private final SentenceForm firstForm;
        private final SentenceForm secondForm;
        private final ImmutableMap constants;
        private final ImmutableMap variableEqualities;
        //Specify which variables are or are not in the head
        private final ImmutableSet headVarFirstAppearances;

        private DoubleConjunctRuleKey(boolean isFirstNegated, boolean isSecondNegated, SentenceForm firstForm,
                SentenceForm secondForm, ImmutableMap constants,
                ImmutableMap variableEqualities,
                ImmutableSet headVarFirstAppearances) {
            this.isFirstNegated = isFirstNegated;
            this.isSecondNegated = isSecondNegated;
            this.firstForm = firstForm;
            this.secondForm = secondForm;
            this.constants = constants;
            this.variableEqualities = variableEqualities;
            this.headVarFirstAppearances = headVarFirstAppearances;
        }

        public static DualizedRuleKey create(GdlRule rule) {
            boolean isFirstNegated = (rule.get(0) instanceof GdlNot);
            boolean isSecondNegated = (rule.get(1) instanceof GdlNot);
            Set headVariables = GdlUtils.getVariablesSet(rule.getHead());
            SimpleSentenceForm firstForm = SimpleSentenceForm.create(getSentence(rule, 0));
            SimpleSentenceForm secondForm = SimpleSentenceForm.create(getSentence(rule, 1));
            Map firstAppearances = Maps.newHashMap();
            Map constants = Maps.newHashMap();
            Map variableEqualities = Maps.newHashMap();
            Set headVarFirstAppearances = Sets.newHashSet();
            for (int conjunctNum = 0; conjunctNum < 2; conjunctNum++) {
                GdlSentence sentence = getSentence(rule, conjunctNum);
                List tuple = GdlUtils.getTupleFromSentence(sentence);
                for (int i = 0; i < tuple.size(); i++) {
                    GdlTerm term = tuple.get(i);
                    TermLocator locator = new TermLocator(conjunctNum, i);
                    if (term instanceof GdlConstant) {
                        constants.put(locator, (GdlConstant) term);
                    } else {
                        GdlVariable variable = (GdlVariable) term;
                        if (firstAppearances.containsKey(variable)) {
                            TermLocator firstAppearance = firstAppearances.get(variable);
                            variableEqualities.put(locator, firstAppearance);
                        } else {
                            firstAppearances.put(variable, locator);
                            if (headVariables.contains(variable)) {
                                headVarFirstAppearances.add(locator);
                            }
                        }
                    }
                }

            }

            return new DoubleConjunctRuleKey(isFirstNegated,
                    isSecondNegated,
                    firstForm,
                    secondForm,
                    ImmutableMap.copyOf(constants),
                    ImmutableMap.copyOf(variableEqualities),
                    ImmutableSet.copyOf(headVarFirstAppearances));
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((constants == null) ? 0 : constants.hashCode());
            result = prime * result + ((firstForm == null) ? 0 : firstForm.hashCode());
            result = prime * result + ((headVarFirstAppearances == null) ? 0 : headVarFirstAppearances.hashCode());
            result = prime * result + (isFirstNegated ? 1231 : 1237);
            result = prime * result + (isSecondNegated ? 1231 : 1237);
            result = prime * result + ((secondForm == null) ? 0 : secondForm.hashCode());
            result = prime * result + ((variableEqualities == null) ? 0 : variableEqualities.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            DoubleConjunctRuleKey other = (DoubleConjunctRuleKey) obj;
            if (constants == null) {
                if (other.constants != null) {
                    return false;
                }
            } else if (!constants.equals(other.constants)) {
                return false;
            }
            if (firstForm == null) {
                if (other.firstForm != null) {
                    return false;
                }
            } else if (!firstForm.equals(other.firstForm)) {
                return false;
            }
            if (headVarFirstAppearances == null) {
                if (other.headVarFirstAppearances != null) {
                    return false;
                }
            } else if (!headVarFirstAppearances.equals(other.headVarFirstAppearances)) {
                return false;
            }
            if (isFirstNegated != other.isFirstNegated) {
                return false;
            }
            if (isSecondNegated != other.isSecondNegated) {
                return false;
            }
            if (secondForm == null) {
                if (other.secondForm != null) {
                    return false;
                }
            } else if (!secondForm.equals(other.secondForm)) {
                return false;
            }
            if (variableEqualities == null) {
                if (other.variableEqualities != null) {
                    return false;
                }
            } else if (!variableEqualities.equals(other.variableEqualities)) {
                return false;
            }
            return true;
        }

    }

    //Distinct is weird because we don't care about the order of the 'distinct' entries...
    private static class DistinctConjunctRuleKey implements DualizedRuleKey {
        private final SentenceForm firstForm;
        private final ImmutableMap constants;
        private final ImmutableMap variableEqualities;
        //Specify which variables are or are not in the head
        private final ImmutableSet headVarFirstAppearances;

        private DistinctConjunctRuleKey(SentenceForm firstForm,
                ImmutableMap constants,
                ImmutableMap variableEqualities,
                ImmutableSet headVarFirstAppearances) {
            this.firstForm = firstForm;
            this.constants = constants;
            this.variableEqualities = variableEqualities;
            this.headVarFirstAppearances = headVarFirstAppearances;
        }

        public static DualizedRuleKey create(GdlRule rule) {
            Set headVariables = GdlUtils.getVariablesSet(rule.getHead());
            SimpleSentenceForm firstForm = SimpleSentenceForm.create((GdlSentence) rule.get(0));
            Map firstAppearances = Maps.newHashMap();
            Map constants = Maps.newHashMap();
            Map variableEqualities = Maps.newHashMap();
            Set headVarFirstAppearances = Sets.newHashSet();
            for (int conjunctNum = 0; conjunctNum < 2; conjunctNum++) {
                //                GdlSentence sentence = (GdlSentence) rule.get(conjunctNum);
                List tuple;// = GdlUtils.getTupleFromSentence(sentence);
                if (conjunctNum == 0) {
                    GdlSentence sentence = (GdlSentence) rule.get(conjunctNum);
                    tuple = GdlUtils.getTupleFromSentence(sentence);
                } else {
                    GdlDistinct distinct = (GdlDistinct) rule.get(conjunctNum);
                    tuple = ImmutableList.of(distinct.getArg1(), distinct.getArg2());
                }
                for (int i = 0; i < tuple.size(); i++) {
                    GdlTerm term = tuple.get(i);
                    TermLocator locator;
                    if (conjunctNum == 0) {
                        locator = new TermLocator(conjunctNum, i);
                    } else {
                        //Ignore location within the distinct "tuple" because
                        //swapping the locations doesn't matter
                        locator = new TermLocator(conjunctNum, 0);
                    }
                    if (term instanceof GdlConstant) {
                        constants.put(locator, (GdlConstant) term);
                    } else {
                        GdlVariable variable = (GdlVariable) term;
                        if (firstAppearances.containsKey(variable)) {
                            TermLocator firstAppearance = firstAppearances.get(variable);
                            variableEqualities.put(locator, firstAppearance);
                        } else {
                            //This shouldn't happen in a distinct...
                            Preconditions.checkState(conjunctNum == 0);
                            firstAppearances.put(variable, locator);
                            if (headVariables.contains(variable)) {
                                headVarFirstAppearances.add(locator);
                            }
                        }
                    }
                }

            }

            return new DistinctConjunctRuleKey(firstForm,
                    ImmutableMap.copyOf(constants),
                    ImmutableMap.copyOf(variableEqualities),
                    ImmutableSet.copyOf(headVarFirstAppearances));
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((constants == null) ? 0 : constants.hashCode());
            result = prime * result + ((firstForm == null) ? 0 : firstForm.hashCode());
            result = prime * result + ((headVarFirstAppearances == null) ? 0 : headVarFirstAppearances.hashCode());
            result = prime * result + ((variableEqualities == null) ? 0 : variableEqualities.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            DistinctConjunctRuleKey other = (DistinctConjunctRuleKey) obj;
            if (constants == null) {
                if (other.constants != null) {
                    return false;
                }
            } else if (!constants.equals(other.constants)) {
                return false;
            }
            if (firstForm == null) {
                if (other.firstForm != null) {
                    return false;
                }
            } else if (!firstForm.equals(other.firstForm)) {
                return false;
            }
            if (headVarFirstAppearances == null) {
                if (other.headVarFirstAppearances != null) {
                    return false;
                }
            } else if (!headVarFirstAppearances.equals(other.headVarFirstAppearances)) {
                return false;
            }
            if (variableEqualities == null) {
                if (other.variableEqualities != null) {
                    return false;
                }
            } else if (!variableEqualities.equals(other.variableEqualities)) {
                return false;
            }
            return true;
        }

    }

    //Nearly identical to the DoubleConjunctRuleKey
    //    private static class NegativeConjunctRuleKey implements DualizedRuleKey {
    //        private final SentenceForm firstForm;
    //        private final SentenceForm secondForm;
    //        private final ImmutableMap constants;
    //        private final ImmutableMap variableEqualities;
    //        //Specify which variables are or are not in the head
    //        private final ImmutableSet headVarFirstAppearances;
    //
    //        private NegativeConjunctRuleKey(SentenceForm firstForm, SentenceForm secondForm,
    //                ImmutableMap constants,
    //                ImmutableMap variableEqualities,
    //                ImmutableSet headVarFirstAppearances) {
    //            this.firstForm = firstForm;
    //            this.secondForm = secondForm;
    //            this.constants = constants;
    //            this.variableEqualities = variableEqualities;
    //            this.headVarFirstAppearances = headVarFirstAppearances;
    //        }
    //
    //        public static DualizedRuleKey create(GdlRule rule) {
    //            Set headVariables = GdlUtils.getVariablesSet(rule.getHead());
    //            SimpleSentenceForm firstForm = SimpleSentenceForm.create((GdlSentence) rule.get(0));
    //            SimpleSentenceForm secondForm = SimpleSentenceForm.create((GdlSentence) ((GdlNot) rule.get(1)).getBody());
    //            Map firstAppearances = Maps.newHashMap();
    //            Map constants = Maps.newHashMap();
    //            Map variableEqualities = Maps.newHashMap();
    //            Set headVarFirstAppearances = Sets.newHashSet();
    //            for (int conjunctNum = 0; conjunctNum < 2; conjunctNum++) {
    //                GdlSentence sentence;
    //                if (conjunctNum == 0) {
    //                    sentence = (GdlSentence) rule.get(conjunctNum);
    //                } else {
    //                    sentence = (GdlSentence) ((GdlNot) rule.get(conjunctNum)).getBody();
    //                }
    //                List tuple = GdlUtils.getTupleFromSentence(sentence);
    //                for (int i = 0; i < tuple.size(); i++) {
    //                    GdlTerm term = tuple.get(i);
    //                    TermLocator locator = new TermLocator(conjunctNum, i);
    //                    if (term instanceof GdlConstant) {
    //                        constants.put(locator, (GdlConstant) term);
    //                    } else {
    //                        GdlVariable variable = (GdlVariable) term;
    //                        if (firstAppearances.containsKey(variable)) {
    //                            TermLocator firstAppearance = firstAppearances.get(variable);
    //                            variableEqualities.put(locator, firstAppearance);
    //                        } else {
    //                            firstAppearances.put(variable, locator);
    //                            if (headVariables.contains(variable)) {
    //                                headVarFirstAppearances.add(locator);
    //                            }
    //                        }
    //                    }
    //                }
    //
    //            }
    //
    //            return new NegativeConjunctRuleKey(firstForm,
    //                    secondForm,
    //                    ImmutableMap.copyOf(constants),
    //                    ImmutableMap.copyOf(variableEqualities),
    //                    ImmutableSet.copyOf(headVarFirstAppearances));
    //        }
    //
    //        @Override
    //        public int hashCode() {
    //            final int prime = 31;
    //            int result = 1;
    //            result = prime * result + ((constants == null) ? 0 : constants.hashCode());
    //            result = prime * result + ((firstForm == null) ? 0 : firstForm.hashCode());
    //            result = prime * result + ((headVarFirstAppearances == null) ? 0 : headVarFirstAppearances.hashCode());
    //            result = prime * result + ((secondForm == null) ? 0 : secondForm.hashCode());
    //            result = prime * result + ((variableEqualities == null) ? 0 : variableEqualities.hashCode());
    //            return result;
    //        }
    //
    //        @Override
    //        public boolean equals(Object obj) {
    //            if (this == obj) {
    //                return true;
    //            }
    //            if (obj == null) {
    //                return false;
    //            }
    //            if (getClass() != obj.getClass()) {
    //                return false;
    //            }
    //            DoubleConjunctRuleKey other = (DoubleConjunctRuleKey) obj;
    //            if (constants == null) {
    //                if (other.constants != null) {
    //                    return false;
    //                }
    //            } else if (!constants.equals(other.constants)) {
    //                return false;
    //            }
    //            if (firstForm == null) {
    //                if (other.firstForm != null) {
    //                    return false;
    //                }
    //            } else if (!firstForm.equals(other.firstForm)) {
    //                return false;
    //            }
    //            if (headVarFirstAppearances == null) {
    //                if (other.headVarFirstAppearances != null) {
    //                    return false;
    //                }
    //            } else if (!headVarFirstAppearances.equals(other.headVarFirstAppearances)) {
    //                return false;
    //            }
    //            if (secondForm == null) {
    //                if (other.secondForm != null) {
    //                    return false;
    //                }
    //            } else if (!secondForm.equals(other.secondForm)) {
    //                return false;
    //            }
    //            if (variableEqualities == null) {
    //                if (other.variableEqualities != null) {
    //                    return false;
    //                }
    //            } else if (!variableEqualities.equals(other.variableEqualities)) {
    //                return false;
    //            }
    //            return true;
    //        }
    //    }

    public static Set getSingletonSentenceNames(List rules) {
        Multiset sentenceNameCounts = HashMultiset.create();
        Set nonRuleSentenceNames = Sets.newHashSet();
        for (Gdl gdl : rules) {
            if (gdl instanceof GdlRule) {
                GdlRule rule = (GdlRule) gdl;
                sentenceNameCounts.add(rule.getHead().getName());
            } else {
                GdlSentence sentence = (GdlSentence) gdl;
                nonRuleSentenceNames.add(sentence.getName());
            }
        }

        Set singletonSentenceNames = Sets.newHashSet();
        for (GdlConstant name : sentenceNameCounts.elementSet()) {
            if (sentenceNameCounts.count(name) == 1
                    && !nonRuleSentenceNames.contains(name)) {
                singletonSentenceNames.add(name);
            }
        }
        singletonSentenceNames.remove(GdlPool.BASE);
        singletonSentenceNames.remove(GdlPool.DOES);
        singletonSentenceNames.remove(GdlPool.GOAL);
        singletonSentenceNames.remove(GdlPool.INIT);
        singletonSentenceNames.remove(GdlPool.INPUT);
        singletonSentenceNames.remove(GdlPool.LEGAL);
        singletonSentenceNames.remove(GdlPool.NEXT);
        singletonSentenceNames.remove(GdlPool.ROLE);
        singletonSentenceNames.remove(GdlPool.TERMINAL);
        singletonSentenceNames.remove(GdlPool.TRUE);
        return singletonSentenceNames;
    }

    //    private static List mergeIdenticalRules(List oldRules, SentenceNameFactory snf) {
    //        //Note: Order of the conjuncts is important! (probably?)
    //        SetMultimap, GdlRule> rulesByNormalizedConjuncts =
    //                groupRulesByNormalizedConjuncts(oldRules);
    //
    //        Set newRules = Sets.newHashSet();
    //        newRules.addAll(GdlUtils.getSentences(oldRules));
    //        //First, where there's overlap, create a new one-off rule that has the normalized
    //        //conjuncts, plus some single-conjunct mapping rules from the old _ to the new _
    //        Map replacementsToApply = Maps.newHashMap();
    //        for (ImmutableList key : rulesByNormalizedConjuncts.keySet()) {
    //            Set matchingRules = rulesByNormalizedConjuncts.get(key);
    //            //Matching rules?
    //            if (matchingRules.size() == 1) {
    //                newRules.add(Iterables.getOnlyElement(matchingRules));
    //            } else {
    //                GdlConstant newName = snf.getNewName("anon");
    ////              List newRuleBody = key;
    //                //Steal the name and variables from one of the ___
    //
    ////              for () {
    ////
    ////              }
    //            }
    //        }
    //
    //        //Second, identify and replace any one-off rules that just have a single conjunct
    //        //(and the head is not a keyword)
    //        //What do we mean by "one-off"? We mean the head has only one appearance... (?)
    //        //Or that the body conjunct has only one appearance in the body of a rule?
    //        //Which is the right approach?
    //        //I'm pretty sure we're looking for cases where the BODY is a one-off; appears in one
    //        //rule in the body, appears in one rule in the head, and the rule where it appears in the
    //        //body doesn't have a keyword for the head
    //        //(but what about potentially excessive "forks"?)
    //
    //        return getRulesInMostlyOriginalOrder(oldRules, newRules);
    //    }
    //
    //    private static SetMultimap, GdlRule> groupRulesByNormalizedConjuncts(
    //            List oldRules) {
    //        // TODO: Implement
    //        return null;
    //    }
    //
    //    private static List getRulesInMostlyOriginalOrder(List oldRules,
    //            Set result) {
    //        List finalResult = Lists.newArrayList();
    //        for (Gdl gdl : oldRules) {
    //            if (result.contains(gdl)) {
    //                finalResult.add(gdl);
    //            }
    //        }
    //        Set oldRulesSet = Sets.newHashSet(oldRules);
    //        for (Gdl gdl : result) {
    //            if (!oldRulesSet.contains(gdl)) {
    //                finalResult.add(gdl);
    //            }
    //        }
    //        return finalResult;
    //    }
    //
    //    private static List splitRule(GdlRule rule, SentenceNameFactory snf) {
    //        //Basically do the following operations in order...
    //        //Add the current conjunct...
    //        //If at any time we're "holding onto" a variable that is not used later, get rid of it
    //        //Can we reverse that? Start at the end, work backwards
    //        //When we see a variable that isn't used in the head or a later conjunct, turn everything
    //        //before that point into its own rule
    //        Set varsInUse = Sets.newHashSet();
    //        varsInUse.addAll(GdlUtils.getVariables(rule.getHead()));
    //        varsInUse.addAll(GdlUtils.getVariables(rule.get(rule.getBody().size() - 1)));
    //        for (int i = rule.getBody().size() - 2; i >= 0; i--) {
    //            List varsInConjunct = GdlUtils.getVariables(rule.get(i));
    //            if (!varsInUse.containsAll(varsInConjunct)) {
    //                //Split the rule!
    //                GdlConstant newSentenceName = snf.getNewName(rule);
    //                Set varsUpToConjunct = Sets.newHashSet();
    //                for (int j = 0; j <= i; j++) {
    //                    varsUpToConjunct.addAll(GdlUtils.getVariables(rule.get(j)));
    //                }
    //                //Fix the ordering
    //                List varsToOutput = Lists.newArrayList(
    //                        Sets.intersection(varsInUse, varsUpToConjunct));
    //                //Head of the new rule, first sentence of the old rule
    //                GdlSentence intermediarySentence = GdlPool.getRelation(newSentenceName, varsToOutput);
    //                List oldRuleBody = Lists.newArrayList();
    //                List newRuleBody = Lists.newArrayList();
    //                oldRuleBody.add(intermediarySentence);
    //                for (int j = 0; j < rule.getBody().size(); j++) {
    //                    if (j <= i) {
    //                        newRuleBody.add(rule.get(j));
    //                    } else {
    //                        oldRuleBody.add(rule.get(j));
    //                    }
    //                }
    //                GdlRule oldRule = GdlPool.getRule(rule.getHead(), oldRuleBody);
    //                GdlRule newRule = GdlPool.getRule(intermediarySentence, newRuleBody);
    //                List splitRules = splitRule(newRule, snf);
    //                splitRules.add(oldRule);
    //                return splitRules;
    //            }
    //        }
    //        return Lists.newArrayList(rule);
    //    }

    private static List splitRule(GdlRule rule, SentenceNameFactory snf) {
        List rules = Lists.newArrayList();

        while (rule.arity() > 2) {
            //Rewrite in terms of other stuff...
            //"rule" is going to become 0 through n - 1
            //and we're going to add to "rules" new head and n => old head
            int prefixArity = rule.arity() - 1;
            List prefixBody = ImmutableList.copyOf(Iterables.limit(rule.getBody(), prefixArity));
            GdlLiteral lastConjunct = rule.get(prefixArity);
            //Variables to put in the new head are (in prefix body) and (in old head or last conjunct)
            Set varsInPrefixBody = GdlUtils.getVariables(prefixBody);
            Set varsInOldHead = GdlUtils.getVariablesSet(rule.getHead());
            Set varsInLastConjunct = GdlUtils.getVariablesSet(lastConjunct);
            Set newHeadVars = Sets.intersection(varsInPrefixBody, Sets.union(varsInOldHead, varsInLastConjunct));
            List sortedNewHeadVars = ImmutableList.copyOf(newHeadVars);

            GdlConstant newName = snf.getNewName(rule);
            GdlSentence newHead = createSentence(newName, sortedNewHeadVars);

            //Add the new 2-arity rule
            GdlRule newSmallRule = GdlPool.getRule(rule.getHead(), ImmutableList.of(newHead, lastConjunct));
            rules.add(newSmallRule);

            //And iterate on the remainder of the rule
            rule = GdlPool.getRule(newHead, prefixBody);
        }
        rules.add(rule);
        return rules;
    }

    private static GdlSentence createSentence(GdlConstant name, List body) {
        if (body.isEmpty()) {
            return GdlPool.getProposition(name);
        } else {
            return GdlPool.getRelation(name, body);
        }
    }

    private static class SentenceNameFactory {
        private final Set existingNames;

        private SentenceNameFactory(Set existingNames) {
            this.existingNames = existingNames;
        }

        public GdlConstant getNewName(GdlRule rule) {
            //TODO: Incorporate all the constants in the rule head
            return getNewName(rule.getHead().getName().toString());
        }

        private GdlConstant getNewName(String baseName) {
            GdlConstant newName;
            int i = 0;
            while (true) {
                newName = GdlPool.getConstant(baseName + "_tmp" + i);
                if (!existingNames.contains(newName)) {
                    break;
                }
                i++;
            }

            existingNames.add(newName);
            return newName;
        }

        public static SentenceNameFactory create(List rules) {
            Set existingNames = Sets.newHashSet();
            GdlVisitors.visitAll(rules, new GdlVisitor() {
                @Override
                public void visitSentence(GdlSentence sentence) {
                    existingNames.add(sentence.getName());
                }
            });
            return new SentenceNameFactory(existingNames);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy