/** * */ package org.ggp.base.util.gdl.model.assignments; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.PriorityQueue; import java.util.Set; import org.ggp.base.util.gdl.GdlUtils; import org.ggp.base.util.gdl.grammar.GdlConstant; import org.ggp.base.util.gdl.grammar.GdlDistinct; import org.ggp.base.util.gdl.grammar.GdlFunction; import org.ggp.base.util.gdl.grammar.GdlLiteral; import org.ggp.base.util.gdl.grammar.GdlPool; import org.ggp.base.util.gdl.grammar.GdlProposition; import org.ggp.base.util.gdl.grammar.GdlRelation; 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 org.ggp.base.util.gdl.transforms.CommonTransforms; import org.ggp.base.util.gdl.transforms.ConstantChecker; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; public class AssignmentsImpl implements Assignments { private boolean empty; private boolean allDone = false; //Contains all the assignments of variables we could make private Map<GdlVariable, GdlConstant> headAssignment; private List<GdlVariable> varsToAssign; private List<ImmutableList<GdlConstant>> valuesToIterate; private List<AssignmentFunction> valuesToCompute; private List<Integer> indicesToChangeWhenNull; //See note below private List<GdlDistinct> distincts; private List<GdlVariable> varsToChangePerDistinct; //indexing same as distincts /* * What does indicesToChangeWhenNull do? Well, sometimes after incrementing * part of the iterator, we find that a function being used to define a slot * in the tuple has no value corresponding to its inputs (the inputs are * outside the function's domain). In that case, we set the value to null, * then leave it to the makeNextAssignmentValid() method to deal with it. * We want to increment something in the input, but we need to know what * in the input we should increment (i.e. which is the rightmost slot in * the function's input). This is recorded in indicesToChangeWhenNull. If * a slot is not defined by a function, then presumably it will not be null, * so its value here is unimportant. Setting its value to -1 would help * catch errors. */ private List<ImmutableList<ImmutableList<GdlConstant>>> tuplesBySource; //indexed by conjunct private List<Integer> sourceDefiningSlot; //indexed by var slot private List<ImmutableList<Integer>> varsChosenBySource; //indexed by conjunct, then slot private List<ImmutableList<Boolean>> putDontCheckBySource; //indexed by conjunct, then slot /** * Creates an Assignments object that generates AssignmentIterators. * These can be used to efficiently iterate over all possible assignments * for variables in a given rule. * * @param headAssignment An assignment of variables whose values should be * fixed. May be empty. * @param rule The rule whose assignments we want to iterate over. * @param varDomains A map containing the possible values for each variable * in the rule. (All such values are GdlConstants.) * @param functionInfoMap * @param completedSentenceFormValues */ public AssignmentsImpl(Map<GdlVariable, GdlConstant> headAssignment, GdlRule rule, Map<GdlVariable, Set<GdlConstant>> varDomains, Map<SentenceForm, ? extends FunctionInfo> functionInfoMap, Map<SentenceForm, ? extends Collection<GdlSentence>> completedSentenceFormValues) { empty = false; this.headAssignment = headAssignment; //We first have to find the remaining variables in the body varsToAssign = GdlUtils.getVariables(rule); //Remove all the duplicates; we do, however, want to keep the ordering List<GdlVariable> newVarsToAssign = new ArrayList<GdlVariable>(); for(GdlVariable v : varsToAssign) if(!newVarsToAssign.contains(v)) newVarsToAssign.add(v); varsToAssign = newVarsToAssign; varsToAssign.removeAll(headAssignment.keySet()); //varsToAssign is set at this point //We see if iterating over entire tuples will give us a //better result, and we look for the best way of doing that. //Let's get the domains of the variables //Map<GdlVariable, Set<GdlConstant>> varDomains = model.getVarDomains(rule); //Since we're looking at a particular rule, we can do this one step better //by looking at the domain of the head, which may be more restrictive //and taking the intersections of the two domains where applicable //Map<GdlVariable, Set<GdlConstant>> headVarDomains = model.getVarDomainsInSentence(rule.getHead()); //We can run the A* search for a good set of source conjuncts //at this point, then use the result to build the rest. Map<SentenceForm, Integer> completedSentenceFormSizes = new HashMap<SentenceForm, Integer>(); if(completedSentenceFormValues != null) for(SentenceForm form : completedSentenceFormValues.keySet()) completedSentenceFormSizes.put(form, completedSentenceFormValues.get(form).size()); Map<GdlVariable, Integer> varDomainSizes = new HashMap<GdlVariable, Integer>(); for(GdlVariable var : varDomains.keySet()) varDomainSizes.put(var, varDomains.get(var).size()); IterationOrderCandidate bestOrdering; bestOrdering = getBestIterationOrderCandidate(rule, varDomains,/*model,*/ functionInfoMap, completedSentenceFormSizes, headAssignment, false); //TODO: True here? //Want to replace next few things with order //Need a few extra things to handle the use of iteration over existing tuples varsToAssign = bestOrdering.getVariableOrdering(); //For each of these vars, we have to find one or the other. //Let's start by finding all the domains, a task already done. valuesToIterate = Lists.newArrayListWithCapacity(varsToAssign.size()); for(GdlVariable var : varsToAssign) { if(varDomains.containsKey(var)) { if(!varDomains.get(var).isEmpty()) valuesToIterate.add(ImmutableList.copyOf(varDomains.get(var))); else valuesToIterate.add(ImmutableList.of(GdlPool.getConstant("0"))); } else { valuesToIterate.add(ImmutableList.of(GdlPool.getConstant("0"))); } } //Okay, the iteration-over-domain is done. //Now let's look at sourced iteration. sourceDefiningSlot = new ArrayList<Integer>(varsToAssign.size()); for(int i = 0; i < varsToAssign.size(); i++) { sourceDefiningSlot.add(-1); } //We also need to convert values into tuples //We should do so while constraining to any constants in the conjunct //Let's convert the conjuncts List<GdlSentence> sourceConjuncts = bestOrdering.getSourceConjuncts(); tuplesBySource = Lists.newArrayListWithCapacity(sourceConjuncts.size());//new ArrayList<List<List<GdlConstant>>>(sourceConjuncts.size()); varsChosenBySource = Lists.newArrayListWithCapacity(sourceConjuncts.size());//new ArrayList<List<Integer>>(sourceConjuncts.size()); putDontCheckBySource = Lists.newArrayListWithCapacity(sourceConjuncts.size());//new ArrayList<List<Boolean>>(sourceConjuncts.size()); for(int j = 0; j < sourceConjuncts.size(); j++) { GdlSentence sourceConjunct = sourceConjuncts.get(j); SentenceForm form = SimpleSentenceForm.create(sourceConjunct); //flatten into a tuple List<GdlTerm> conjunctTuple = GdlUtils.getTupleFromSentence(sourceConjunct); //Go through the vars/constants in the tuple List<Integer> constraintSlots = new ArrayList<Integer>(); List<GdlConstant> constraintValues = new ArrayList<GdlConstant>(); List<Integer> varsChosen = new ArrayList<Integer>(); List<Boolean> putDontCheck = new ArrayList<Boolean>(); for(int i = 0; i < conjunctTuple.size(); i++) { GdlTerm term = conjunctTuple.get(i); if(term instanceof GdlConstant) { constraintSlots.add(i); constraintValues.add((GdlConstant) term); //TODO: What if tuple size ends up being 0? //Need to keep that in mind } else if(term instanceof GdlVariable) { int varIndex = varsToAssign.indexOf(term); varsChosen.add(varIndex); if(sourceDefiningSlot.get(varIndex) == -1) { //We define it sourceDefiningSlot.set(varIndex, j); putDontCheck.add(true); } else { //It's an overlap; we just check for consistency putDontCheck.add(false); } } else { throw new RuntimeException("Function returned in tuple"); } } varsChosenBySource.add(ImmutableList.copyOf(varsChosen)); putDontCheckBySource.add(ImmutableList.copyOf(putDontCheck)); //Now we put the tuples together //We use constraintSlots and constraintValues to check that the //tuples have compatible values Collection<GdlSentence> sentences = completedSentenceFormValues.get(form); List<ImmutableList<GdlConstant>> tuples = Lists.newArrayList(); byTuple: for(GdlSentence sentence : sentences) { //Check that it doesn't conflict with our headAssignment if (!headAssignment.isEmpty()) { Map<GdlVariable, GdlConstant> tupleAssignment = GdlUtils.getAssignmentMakingLeftIntoRight(sourceConjunct, sentence); for (GdlVariable var : headAssignment.keySet()) { if (tupleAssignment.containsKey(var) && tupleAssignment.get(var) != headAssignment.get(var)) { continue byTuple; } } } List<GdlConstant> longTuple = GdlUtils.getTupleFromGroundSentence(sentence); List<GdlConstant> shortTuple = new ArrayList<GdlConstant>(varsChosen.size()); for(int c = 0; c < constraintSlots.size(); c++) { int slot = constraintSlots.get(c); GdlConstant value = constraintValues.get(c); if(!longTuple.get(slot).equals(value)) continue byTuple; } int c = 0; for(int s = 0; s < longTuple.size(); s++) { //constraintSlots is sorted in ascending order if(c < constraintSlots.size() && constraintSlots.get(c) == s) c++; else shortTuple.add(longTuple.get(s)); } //The tuple fits the source conjunct tuples.add(ImmutableList.copyOf(shortTuple)); } //sortTuples(tuples); //Needed? Useful? Not sure. Probably not? tuplesBySource.add(ImmutableList.copyOf(tuples)); } //We now want to see which we can give assignment functions to valuesToCompute = new ArrayList<AssignmentFunction>(varsToAssign.size()); for(@SuppressWarnings("unused") GdlVariable var : varsToAssign) { valuesToCompute.add(null); } indicesToChangeWhenNull = new ArrayList<Integer>(varsToAssign.size()); for(int i = 0; i < varsToAssign.size(); i++) { //Change itself, why not? //Actually, instead let's try -1, to catch bugs better indicesToChangeWhenNull.add(-1); } //Now we have our functions already selected by the ordering //bestOrdering.functionalConjunctIndices; //Make AssignmentFunctions out of the ordering List<GdlSentence> functionalConjuncts = bestOrdering.getFunctionalConjuncts(); // System.out.println("functionalConjuncts: " + functionalConjuncts); for(int i = 0; i < functionalConjuncts.size(); i++) { GdlSentence functionalConjunct = functionalConjuncts.get(i); if(functionalConjunct != null) { //These are the only ones that could be constant functions SentenceForm conjForm = SimpleSentenceForm.create(functionalConjunct); FunctionInfo functionInfo = null; if(functionInfoMap != null) functionInfo = functionInfoMap.get(conjForm); if(functionInfo != null) { //Now we need to figure out which variables are involved //and which are suitable as functional outputs. //1) Which vars are in this conjunct? List<GdlVariable> varsInSentence = GdlUtils.getVariables(functionalConjunct); //2) Of these vars, which is "rightmost"? GdlVariable rightmostVar = getRightmostVar(varsInSentence); //3) Is it only used once in the relation? if(Collections.frequency(varsInSentence, rightmostVar) != 1) continue; //Can't use it //4) Which slot is it used in in the relation? //5) Build an AssignmentFunction if appropriate. // This should be able to translate from values of // the other variables to the value of the wanted // variable. AssignmentFunction function = AssignmentFunction.create((GdlRelation)functionalConjunct, functionInfo, rightmostVar, varsToAssign, headAssignment); //We don't guarantee that this works until we check if(!function.functional()) continue; int index = varsToAssign.indexOf(rightmostVar); valuesToCompute.set(index, function); Set<GdlVariable> remainingVarsInSentence = new HashSet<GdlVariable>(varsInSentence); remainingVarsInSentence.remove(rightmostVar); GdlVariable nextRightmostVar = getRightmostVar(remainingVarsInSentence); indicesToChangeWhenNull.set(index, varsToAssign.indexOf(nextRightmostVar)); } } } //We now have the remainingVars also assigned their domains //We also cover the distincts here //Assume these are just variables and constants distincts = new ArrayList<GdlDistinct>(); for(GdlLiteral literal : rule.getBody()) { if(literal instanceof GdlDistinct) distincts.add((GdlDistinct) literal); } computeVarsToChangePerDistinct(); //Need to add "distinct" restrictions to head assignment, too... checkDistinctsAgainstHead(); //We are ready for iteration // System.out.println("headAssignment: " + headAssignment); // System.out.println("varsToAssign: " + varsToAssign); // System.out.println("valuesToCompute: " + valuesToCompute); // System.out.println("sourceDefiningSlot: " + sourceDefiningSlot); } private GdlVariable getRightmostVar(Collection<GdlVariable> vars) { GdlVariable rightmostVar = null; for(GdlVariable var : varsToAssign) if(vars.contains(var)) rightmostVar = var; return rightmostVar; } public AssignmentsImpl() { //The assignment is impossible; return nothing empty = true; } @SuppressWarnings("unchecked") public AssignmentsImpl(GdlRule rule, /*SentenceModel model,*/ Map<GdlVariable, Set<GdlConstant>> varDomains, Map<SentenceForm, ? extends FunctionInfo> functionInfoMap, Map<SentenceForm, ? extends Collection<GdlSentence>> completedSentenceFormValues) { this(Collections.EMPTY_MAP, rule, varDomains, functionInfoMap, completedSentenceFormValues); } private void checkDistinctsAgainstHead() { for(GdlDistinct distinct : distincts) { GdlTerm term1 = CommonTransforms.replaceVariables(distinct.getArg1(), headAssignment); GdlTerm term2 = CommonTransforms.replaceVariables(distinct.getArg2(), headAssignment); if(term1.equals(term2)) { //This fails empty = true; allDone = true; } } } @Override public Iterator<Map<GdlVariable, GdlConstant>> iterator() { return new AssignmentIteratorImpl(getPlan()); } @Override public AssignmentIterator getIterator() { return new AssignmentIteratorImpl(getPlan()); } private AssignmentIterationPlan getPlan() { return AssignmentIterationPlan.create(varsToAssign, tuplesBySource, headAssignment, indicesToChangeWhenNull, distincts, varsToChangePerDistinct, valuesToCompute, sourceDefiningSlot, valuesToIterate, varsChosenBySource, putDontCheckBySource, empty, allDone); } private void computeVarsToChangePerDistinct() { //remember that iterators must be set up first varsToChangePerDistinct = new ArrayList<GdlVariable>(varsToAssign.size()); for(GdlDistinct distinct : distincts) { //For two vars, we want to record the later of the two //For one var, we want to record the one //For no vars, we just put null List<GdlVariable> varsInDistinct = new ArrayList<GdlVariable>(2); if(distinct.getArg1() instanceof GdlVariable) varsInDistinct.add((GdlVariable) distinct.getArg1()); if(distinct.getArg2() instanceof GdlVariable) varsInDistinct.add((GdlVariable) distinct.getArg2()); GdlVariable varToChange = null; if(varsInDistinct.size() == 1) { varToChange = varsInDistinct.get(0); } else if(varsInDistinct.size() == 2) { varToChange = getRightmostVar(varsInDistinct); } varsToChangePerDistinct.add(varToChange); } } public static Assignments getAssignmentsProducingSentence( GdlRule rule, GdlSentence sentence, /*SentenceModel model,*/ Map<GdlVariable, Set<GdlConstant>> varDomains, Map<SentenceForm, FunctionInfo> functionInfoMap, Map<SentenceForm, ? extends Collection<GdlSentence>> completedSentenceFormValues) { //First, we see which variables must be set according to the rule head //(and see if there's any contradiction) Map<GdlVariable, GdlConstant> headAssignment = new HashMap<GdlVariable, GdlConstant>(); if(!setVariablesInHead(rule.getHead(), sentence, headAssignment)) { return new AssignmentsImpl();//Collections.emptySet(); } //Then we come up with all the assignments of the rest of the variables //We need to look for functions we can make use of return new AssignmentsImpl(headAssignment, rule, varDomains, functionInfoMap, completedSentenceFormValues); } //returns true if all variables were set successfully private static boolean setVariablesInHead(GdlSentence head, GdlSentence sentence, Map<GdlVariable, GdlConstant> assignment) { if(head instanceof GdlProposition) return true; return setVariablesInHead(head.getBody(), sentence.getBody(), assignment); } private static boolean setVariablesInHead(List<GdlTerm> head, List<GdlTerm> sentence, Map<GdlVariable, GdlConstant> assignment) { for(int i = 0; i < head.size(); i++) { GdlTerm headTerm = head.get(i); GdlTerm refTerm = sentence.get(i); if(headTerm instanceof GdlConstant) { if(!refTerm.equals(headTerm)) //The rule can't produce this sentence return false; } else if(headTerm instanceof GdlVariable) { GdlVariable var = (GdlVariable) headTerm; GdlConstant curValue = assignment.get(var); if(curValue != null && !curValue.equals(refTerm)) { //inconsistent assignment (e.g. head is (rel ?x ?x), sentence is (rel 1 2)) return false; } assignment.put(var, (GdlConstant)refTerm); } else if(headTerm instanceof GdlFunction) { //Recurse on the body GdlFunction headFunction = (GdlFunction) headTerm; GdlFunction refFunction = (GdlFunction) refTerm; if(!setVariablesInHead(headFunction.getBody(), refFunction.getBody(), assignment)) return false; } } return true; } /** * Finds the iteration order (including variables, functions, and * source conjuncts) that is expected to result in the fastest iteration. * * The value that is compared for each ordering is the product of: * - For each source conjunct, the number of tuples offered by the conjunct; * - For each variable not defined by a function, the size of its domain. * * @param functionInfoMap * @param completedSentenceFormSizes For each sentence form, this may optionally * contain the number of possible sentences of this form. This is useful if the * number of sentences is much lower than the product of its variables' domain * sizes; however, if this contains sentence forms where the set of sentences * is unknown, then it may return an ordering that is unusable. */ protected static IterationOrderCandidate getBestIterationOrderCandidate(GdlRule rule, /*SentenceModel model,*/ Map<GdlVariable, Set<GdlConstant>> varDomains, Map<SentenceForm, ? extends FunctionInfo> functionInfoMap, Map<SentenceForm, Integer> completedSentenceFormSizes, Map<GdlVariable, GdlConstant> preassignment, boolean analyticFunctionOrdering) { //Here are the things we need to pass into the first IOC constructor List<GdlSentence> sourceConjunctCandidates = new ArrayList<GdlSentence>(); //What is a source conjunct candidate? //- It is a positive conjunct in the rule (i.e. a GdlSentence in the body). //- It has already been fully defined; i.e. it is not recursively defined in terms of the current form. //Furthermore, we know the number of potentially true tuples in it. List<GdlVariable> varsToAssign = GdlUtils.getVariables(rule); List<GdlVariable> newVarsToAssign = new ArrayList<GdlVariable>(); for(GdlVariable var : varsToAssign) if(!newVarsToAssign.contains(var)) newVarsToAssign.add(var); varsToAssign = newVarsToAssign; if(preassignment != null) varsToAssign.removeAll(preassignment.keySet()); //Calculate var domain sizes Map<GdlVariable, Integer> varDomainSizes = getVarDomainSizes(varDomains/*rule, model*/); List<Integer> sourceConjunctSizes = new ArrayList<Integer>(); for(GdlLiteral conjunct : rule.getBody()) { if(conjunct instanceof GdlRelation) { SentenceForm form = SimpleSentenceForm.create((GdlRelation)conjunct); if(completedSentenceFormSizes != null && completedSentenceFormSizes.containsKey(form)) { int size = completedSentenceFormSizes.get(form); //New: Don't add if it will be useless as a source //For now, we take a strict definition of that //Compare its size with the product of the domains //of the variables it defines //In the future, we could require a certain ratio //to decide that this is worthwhile GdlRelation relation = (GdlRelation) conjunct; int maxSize = 1; Set<GdlVariable> vars = new HashSet<GdlVariable>(GdlUtils.getVariables(relation)); for(GdlVariable var : vars) { int domainSize = varDomainSizes.get(var); maxSize *= domainSize; } if(size >= maxSize) continue; sourceConjunctCandidates.add(relation); sourceConjunctSizes.add(size); } } } List<GdlSentence> functionalSentences = new ArrayList<GdlSentence>(); List<FunctionInfo> functionalSentencesInfo = new ArrayList<FunctionInfo>(); for(GdlLiteral conjunct : rule.getBody()) { if(conjunct instanceof GdlSentence) { SentenceForm form = SimpleSentenceForm.create((GdlSentence) conjunct); if(functionInfoMap != null && functionInfoMap.containsKey(form)) { functionalSentences.add((GdlSentence) conjunct); functionalSentencesInfo.add(functionInfoMap.get(form)); } } } //TODO: If we have a head assignment, treat everything as already replaced //Maybe just translate the rule? Or should we keep the pool clean? IterationOrderCandidate emptyCandidate = new IterationOrderCandidate(varsToAssign, sourceConjunctCandidates, sourceConjunctSizes, functionalSentences, functionalSentencesInfo, varDomainSizes); PriorityQueue<IterationOrderCandidate> searchQueue = new PriorityQueue<IterationOrderCandidate>(); searchQueue.add(emptyCandidate); while(!searchQueue.isEmpty()) { IterationOrderCandidate curNode = searchQueue.remove(); // System.out.println("Node being checked out: " + curNode); if(curNode.isComplete()) { //This is the complete ordering with the lowest heuristic value return curNode; } searchQueue.addAll(curNode.getChildren(analyticFunctionOrdering)); } throw new RuntimeException("Found no complete iteration orderings"); } private static Map<GdlVariable, Integer> getVarDomainSizes(/*GdlRule rule, SentenceModel model*/Map<GdlVariable, Set<GdlConstant>> varDomains) { Map<GdlVariable, Integer> varDomainSizes = new HashMap<GdlVariable, Integer>(); //Map<GdlVariable, Set<GdlConstant>> varDomains = model.getVarDomains(rule); for(GdlVariable var : varDomains.keySet()) { varDomainSizes.put(var, varDomains.get(var).size()); } return varDomainSizes; } public static long getNumAssignmentsEstimate(GdlRule rule, Map<GdlVariable, Set<GdlConstant>> varDomains, ConstantChecker checker) throws InterruptedException { //First we need the best iteration order //Arguments we'll need to pass in: //- A SentenceModel //- constant forms //- completed sentence form sizes //- Variable domain sizes? Map<SentenceForm, FunctionInfo> functionInfoMap = new HashMap<SentenceForm, FunctionInfo>(); for (SentenceForm form : checker.getConstantSentenceForms()) { functionInfoMap.put(form, FunctionInfoImpl.create(form, checker)); } //Populate variable domain sizes using the constant checker Map<SentenceForm, Integer> domainSizes = new HashMap<SentenceForm, Integer>(); for(SentenceForm form : checker.getConstantSentenceForms()) { domainSizes.put(form, checker.getTrueSentences(form).size()); } //TODO: Propagate these domain sizes as estimates for other rules? //Look for literals in the body of the rule and their ancestors? //Could we possibly do this elsewhere? IterationOrderCandidate ordering = getBestIterationOrderCandidate(rule, /*model,*/varDomains, functionInfoMap, null, null, true); return ordering.getHeuristicValue(); } }