/** * */ package util.propnet.factory; 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 java.util.Map.Entry; import util.gdl.grammar.GdlConstant; import util.gdl.grammar.GdlDistinct; import util.gdl.grammar.GdlFunction; import util.gdl.grammar.GdlLiteral; import util.gdl.grammar.GdlProposition; import util.gdl.grammar.GdlRelation; import util.gdl.grammar.GdlRule; import util.gdl.grammar.GdlSentence; import util.gdl.grammar.GdlTerm; import util.gdl.grammar.GdlVariable; import util.gdl.model.SentenceModel; import util.gdl.model.SentenceModel.SentenceForm; import util.gdl.model.SentenceModel.TermModel; import util.gdl.transforms.CommonTransforms; import util.gdl.transforms.ConstantFinder.ConstantChecker; import util.propnet.architecture.Component; import util.propnet.architecture.components.Constant; public class Assignments implements Iterable<Map<GdlVariable, GdlConstant>> { boolean empty; boolean allDone = false; //Contains all the assignments of variables we could make Map<GdlVariable, GdlConstant> headAssignment = new HashMap<GdlVariable, GdlConstant>(); private List<GdlVariable> varsToAssign; private List<List<GdlConstant>> valuesToIterate; private List<AssignmentFunction> valuesToCompute; private List<Integer> indicesToChangeWhenNull; //See note below List<GdlDistinct> distincts; 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. */ List<List<List<GdlConstant>>> tuplesBySource; //indexed by conjunct List<Integer> sourceDefiningSlot; //indexed by var slot List<List<Integer>> varsChosenBySource; //indexed by conjunct, then slot List<List<Boolean>> putDontCheckBySource; //indexed by conjunct, then slot public Assignments(Map<GdlVariable, GdlConstant> headAssignment, GdlRule rule, SentenceModel model, Map<SentenceForm, ConstantForm> constantForms, Map<SentenceForm, ? extends Collection<GdlSentence>> completedSentenceFormValues) { empty = false; this.headAssignment = headAssignment; //We first have to find the remaining variables in the body varsToAssign = SentenceModel.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, List<GdlConstant>> varDomains = getVarDomains(rule, model); //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, model, constantForms, 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 = new ArrayList<List<GdlConstant>>(varsToAssign.size()); for(GdlVariable var : varsToAssign) { valuesToIterate.add(varDomains.get(var)); } //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 = new ArrayList<List<List<GdlConstant>>>(sourceConjuncts.size()); varsChosenBySource = new ArrayList<List<Integer>>(sourceConjuncts.size()); putDontCheckBySource = new ArrayList<List<Boolean>>(sourceConjuncts.size()); for(int j = 0; j < sourceConjuncts.size(); j++) { GdlSentence sourceConjunct = sourceConjuncts.get(j); SentenceForm form = model.getSentenceForm(sourceConjunct); //flatten into a tuple List<GdlTerm> conjunctTuple = SentenceModel.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(varsChosen); putDontCheckBySource.add(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<List<GdlConstant>> tuples = new ArrayList<List<GdlConstant>>(); byTuple: for(GdlSentence sentence : sentences) { List<GdlConstant> longTuple = SentenceModel.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(shortTuple); } //sortTuples(tuples); //Needed? Useful? Not sure. Probably not? tuplesBySource.add(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(); 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 = model.getSentenceForm(functionalConjunct); ConstantForm constForm = null; if(constantForms != null) constForm = constantForms.get(conjForm); if(constForm != 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 = SentenceModel.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 = new AssignmentFunction((GdlRelation)functionalConjunct, constForm, 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 } private static Map<GdlVariable, List<GdlConstant>> getVarDomains(GdlRule rule, SentenceModel model) { Set<GdlVariable> vars = new HashSet<GdlVariable>(SentenceModel.getVariables(rule)); Map<GdlVariable, List<GdlConstant>> varDomains = new HashMap<GdlVariable, List<GdlConstant>>(); for(GdlLiteral conjunct : rule.getBody()) { if(conjunct instanceof GdlRelation) { //This is where variables must be assigned for(GdlVariable var : vars) { Set<GdlConstant> domain = getDomainInRelation((GdlRelation) conjunct, var, model); if(domain != null) { //Add to the domain if(varDomains.get(var) == null) { varDomains.put(var, new ArrayList<GdlConstant>()); varDomains.get(var).addAll(domain); } else { varDomains.get(var).retainAll(domain); if(varDomains.get(var).isEmpty()) { //The game probably has an error in this rule //Why? Because with an empty domain, the rule does nothing System.out.println("Warning: Probable error in rule " + rule + ": check domains for variable " + var); } } } } } } return varDomains; } private GdlVariable getRightmostVar(Collection<GdlVariable> vars) { GdlVariable rightmostVar = null; for(GdlVariable var : varsToAssign) if(vars.contains(var)) rightmostVar = var; return rightmostVar; } public Assignments() { //The assignment is impossible; return nothing empty = true; } @SuppressWarnings("unchecked") public Assignments(GdlRule rule, SentenceModel model, Map<SentenceForm, ConstantForm> constantForms, Map<SentenceForm, ? extends Collection<GdlSentence>> completedSentenceFormValues) { this(Collections.EMPTY_MAP, rule, model, constantForms, 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; } } } private static Set<GdlConstant> getDomainInRelation(GdlRelation relation, GdlVariable var, SentenceModel model) { //Traverse the model and relation together Set<GdlConstant> domain = new HashSet<GdlConstant>(); setDomainInRelation(domain, relation.getBody(), model.getBody(relation.getName().getValue()), var); if(domain.isEmpty()) return null; return domain; } private static void setDomainInRelation(Set<GdlConstant> domain, List<GdlTerm> gdlBody, List<TermModel> modelBody, GdlVariable var) { for(int i = 0; i < gdlBody.size(); i++) { GdlTerm term = gdlBody.get(i); TermModel termModel = modelBody.get(i); if(term.equals(var)) { if(domain.isEmpty()) { domain.addAll(termModel.getConstants()); } else { domain.retainAll(termModel.getConstants()); } } else if(term instanceof GdlFunction) { GdlFunction function = (GdlFunction) term; List<TermModel> functionModelBody = termModel.getFunction(function); setDomainInRelation(domain, function.getBody(), functionModelBody, var); } } } @Override public Iterator<Map<GdlVariable, GdlConstant>> iterator() { return new AssignmentIterator(); } public AssignmentIterator getIterator() { return new AssignmentIterator(); } 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 class AssignmentIterator implements Iterator<Map<GdlVariable, GdlConstant>> { List<Integer> sourceTupleIndices = null; //This time we just have integers to deal with List<Integer> valueIndices = null; List<GdlConstant> nextAssignment = new ArrayList<GdlConstant>(); Map<GdlVariable, GdlConstant> assignmentMap = new HashMap<GdlVariable, GdlConstant>(); boolean headOnly = false; boolean done = false; public AssignmentIterator() { if(varsToAssign == null) { headOnly = true; return; } //Set up source tuple... sourceTupleIndices = new ArrayList<Integer>(tuplesBySource.size()); for(int i = 0; i < tuplesBySource.size(); i++) { sourceTupleIndices.add(0); } //Set up... valueIndices = new ArrayList<Integer>(varsToAssign.size()); for(int i = 0; i < varsToAssign.size(); i++) { valueIndices.add(0); nextAssignment.add(null); } assignmentMap.putAll(headAssignment); //Update "nextAssignment" according to the values of the //value indices updateNextAssignment(); //Keep updating it until something really works makeNextAssignmentValid(); } private void makeNextAssignmentValid() { if(nextAssignment == null) return; //Something new that can pop up with functional constants... for(int i = 0; i < nextAssignment.size(); i++) { if(nextAssignment.get(i) == null) { //Some function doesn't agree with the answer here //So what do we increment? incrementIndex(indicesToChangeWhenNull.get(i)); if(nextAssignment == null) return; i = -1; } } //Find all the unsatisfied distincts //Find the pair with the earliest var. that needs to be changed List<GdlVariable> varsToChange = new ArrayList<GdlVariable>(); for(int d = 0; d < distincts.size(); d++) { GdlDistinct distinct = distincts.get(d); //The assignments must use the assignments implied by nextAssignment GdlConstant term1 = replaceVariables(distinct.getArg1()); GdlConstant term2 = replaceVariables(distinct.getArg2()); if(term1.equals(term2)) { //need to change one of these varsToChange.add(varsToChangePerDistinct.get(d)); } } if(!varsToChange.isEmpty()) { GdlVariable varToChange = getLeftmostVar(varsToChange); //We want just the one, as it is a full restriction on its //own behalf changeOneInNext(Collections.singleton(varToChange)); } } private GdlVariable getLeftmostVar(List<GdlVariable> vars) { for(GdlVariable var : varsToAssign) if(vars.contains(var)) return var; return null; } private GdlConstant replaceVariables(GdlTerm term) { if(term instanceof GdlFunction) throw new RuntimeException("Function in the distinct... not handled"); //Use the assignments implied by nextAssignment if(headAssignment.containsKey(term)) return headAssignment.get(term); //Translated in head assignment if(term instanceof GdlConstant) return (GdlConstant) term; int index = varsToAssign.indexOf(term); return nextAssignment.get(index); } private void incrementIndex(int index) { if(index < 0) { //Trash the iterator nextAssignment = null; return; } if(valuesToCompute != null && valuesToCompute.get(index) != null) { //The constant at this index is functionally computed incrementIndex(index - 1); return; } if(sourceDefiningSlot.get(index) != -1) { //This is set by a source; increment the source incrementSource(sourceDefiningSlot.get(index)); return; } //We try increasing the var at index by 1. //Everything to the right of it gets reset. //If it can't be increased, increase the number //to the left instead. If nothing can be //increased, trash the iterator. int curValue = valueIndices.get(index); if(curValue == valuesToIterate.get(index).size() - 1) { //We have no room to increase the value incrementIndex(index - 1); return; } //Increment the current value valueIndices.set(index, curValue + 1); //Reset everything to the right of the current value for(int i = index + 1; i < valueIndices.size(); i++) valueIndices.set(i, 0); //Update the assignment updateNextAssignment(); } private void incrementSource(int source) { if(source < 0) { //Trash the iterator nextAssignment = null; return; } //If we can't increase this source, increase the one to the left instead int curValue = sourceTupleIndices.get(source); if(curValue == tuplesBySource.get(source).size() - 1) { incrementSource(source - 1); return; } //Increment the current source sourceTupleIndices.set(source, curValue + 1); //Reset all the sources to the right of it for(int i = source + 1; i < sourceTupleIndices.size(); i++) sourceTupleIndices.set(i, 0); //Reset all the values set by iteration over domains for(int i = 0; i < valueIndices.size(); i++) valueIndices.set(i, 0); //Update the assignment updateNextAssignment(); } private void updateNextAssignment() { //Let's set according to the sources before we get to the remainder for(int s = 0; s < sourceTupleIndices.size(); s++) { List<List<GdlConstant>> tuples = tuplesBySource.get(s); int curIndex = sourceTupleIndices.get(s); if(tuples.size() == 0) { System.out.println("number of sources: " + sourceTupleIndices.size()); //Might we have to do this on occasion? nextAssignment = null; return; } List<GdlConstant> tuple = tuples.get(curIndex); List<Integer> varsChosen = varsChosenBySource.get(s); List<Boolean> putDontCheckTuple = putDontCheckBySource.get(s); for(int i = 0; i < tuple.size(); i++) { GdlConstant value = tuple.get(i); boolean putDontCheck = putDontCheckTuple.get(i); int varSlotChosen = varsChosen.get(i); if(putDontCheck) { nextAssignment.set(varSlotChosen, value); } else { //It's only at this point that we get to check... if(!nextAssignment.get(varSlotChosen).equals(value)) { //We need to correct the value incrementSourceToGetValueInSlot(s, nextAssignment.get(varSlotChosen), i); //updateNextAssignment(); (should be included at end of calling function) return; } } } } for(int i = 0; i < valueIndices.size(); i++) { if((valuesToCompute == null || valuesToCompute.get(i) == null) && sourceDefiningSlot.get(i) == -1) { nextAssignment.set(i, valuesToIterate.get(i).get(valueIndices.get(i))); } else if(sourceDefiningSlot.get(i) == -1) { //Fill in based on a function //Note that the values on the left must already be filled in nextAssignment.set(i, valuesToCompute.get(i).getValue(nextAssignment)); } } } private void incrementSourceToGetValueInSlot(int source, GdlConstant value, int slot) { //Increments the source until it gets the right value or rolls over int newIndex = sourceTupleIndices.get(source) + 1; List<List<GdlConstant>> tuples = tuplesBySource.get(source); while(true) { if(newIndex == tuples.size()) { //We have to stop here; we have to increment the previous source incrementSource(source - 1); //nextAssignment will have been updated return; } if(tuples.get(newIndex).get(slot).equals(value)) { //Success! sourceTupleIndices.set(source, newIndex); updateNextAssignment(); return; } newIndex++; } } public void changeOneInNext(Collection<GdlVariable> vars) { //Basically, we want to increment the rightmost one... //Corner cases: if(nextAssignment == null) return; if(vars.isEmpty()) { if(headOnly) { done = true; return; } else { //Something currently constant is false //The assignment is done done = true; return; } } if(varsToAssign == null) System.out.println("headOnly: " + headOnly); GdlVariable rightmostVar = getRightmostVar(vars); incrementIndex(varsToAssign.indexOf(rightmostVar)); makeNextAssignmentValid(); } public void changeOneInNext(Collection<GdlVariable> varsToChange, Map<GdlVariable, GdlConstant> assignment) { if(nextAssignment == null) return; //First, we stop and see if any of these have already been //changed (in nextAssignment) for(GdlVariable varToChange : varsToChange) { int index = varsToAssign.indexOf(varToChange); if(index != -1) { GdlConstant assignedValue = assignment.get(varToChange); if(assignedValue == null) { System.out.println("assignedValue is null"); System.out.println("varToChange is " + varToChange); System.out.println("assignment is " + assignment); } if(nextAssignment == null) System.out.println("nextAssignment is null"); if(!assignedValue.equals(nextAssignment.get(index))) //We've already changed one of these return; } } //Okay, we actually need to change one of these changeOneInNext(varsToChange); } @Override public boolean hasNext() { if(empty) return false; if(headOnly) return (!allDone && !done); return (nextAssignment != null); } @Override public Map<GdlVariable, GdlConstant> next() { if(headOnly) { if(allDone || done) throw new RuntimeException("Asking for next when all done"); done = true; return headAssignment; } updateMap(); //Sets assignmentMap //Adds one to the nextAssignment incrementIndex(valueIndices.size() - 1); makeNextAssignmentValid(); return assignmentMap; } private void updateMap() { //Sets the map to match the nextAssignment for(int i = 0; i < varsToAssign.size(); i++) { assignmentMap.put(varsToAssign.get(i), nextAssignment.get(i)); } } @Override public void remove() { //Not implemented } } public static Assignments getAssignmentsWithRecursiveInput(GdlRule rule, SentenceModel model, SentenceForm form, GdlSentence input, Map<SentenceForm, ConstantForm> constantForms, boolean useConstForms, Map<SentenceForm, ? extends Collection<GdlSentence>> completedSentenceFormValues) { //Look for the literal(s) in the rule with the sentence form of the //recursive input. This can be tricky if there are multiple matching //literals. List<GdlSentence> matchingLiterals = new ArrayList<GdlSentence>(); for(GdlLiteral literal : rule.getBody()) if(literal instanceof GdlSentence) if(form.matches((GdlSentence) literal)) matchingLiterals.add((GdlSentence) literal); List<Assignments> assignmentsList = new ArrayList<Assignments>(); for(GdlSentence matchingLiteral : matchingLiterals) { //left has the variables, right has the constants Map<GdlVariable, GdlConstant> preassignment = getAssignmentMakingLeftIntoRight(matchingLiteral, input); if(preassignment != null) { Assignments assignments = new Assignments(preassignment, rule, model, constantForms, completedSentenceFormValues); assignmentsList.add(assignments); } } if(assignmentsList.size() == 0) return new Assignments(); if(assignmentsList.size() == 1) return assignmentsList.get(0); throw new RuntimeException("Not yet implemented: assignments for recursive functions with multiple recursive conjuncts"); //TODO: Plan to implement by subclassing Assignments into something //that contains and iterates over multiple Assignments } public static Map<GdlVariable, GdlConstant> getAssignmentMakingLeftIntoRight( GdlSentence left, GdlSentence right) { Map<GdlVariable, GdlConstant> assignment = new HashMap<GdlVariable, GdlConstant>(); if(!left.getName().equals(right.getName())) return null; if(left.arity() != right.arity()) return null; if(left.arity() == 0) return Collections.emptyMap(); if(!fillAssignmentBody(assignment, left.getBody(), right.getBody())) return null; return assignment; } private static boolean fillAssignmentBody( Map<GdlVariable, GdlConstant> assignment, List<GdlTerm> leftBody, List<GdlTerm> rightBody) { //left body contains variables; right body shouldn't if(leftBody.size() != rightBody.size()) { return false; } for(int i = 0; i < leftBody.size(); i++) { GdlTerm leftTerm = leftBody.get(i); GdlTerm rightTerm = rightBody.get(i); if(leftTerm instanceof GdlConstant) { if(!leftTerm.equals(rightTerm)) { return false; } } else if(leftTerm instanceof GdlVariable) { if(assignment.containsKey(leftTerm)) { if(!assignment.get(leftTerm).equals(rightTerm)) { return false; } } else { if(!(rightTerm instanceof GdlConstant)) { return false; } assignment.put((GdlVariable)leftTerm, (GdlConstant)rightTerm); } } else if(leftTerm instanceof GdlFunction) { if(!(rightTerm instanceof GdlFunction)) return false; GdlFunction leftFunction = (GdlFunction) leftTerm; GdlFunction rightFunction = (GdlFunction) rightTerm; if(!leftFunction.getName().equals(rightFunction.getName())) return false; if(!fillAssignmentBody(assignment, leftFunction.getBody(), rightFunction.getBody())) return false; } } return true; } public static Assignments getAssignmentsProducingSentence( GdlRule rule, GdlSentence sentence, SentenceModel model, Map<SentenceForm, ConstantForm> constantForms, 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 Assignments();//Collections.emptySet(); } //Then we come up with all the assignments of the rest of the variables if(headAssignment == null) System.out.println("headAssignment is null over here, too"); //We need to look for functions we can make use of return new Assignments(headAssignment, rule, model, constantForms, completedSentenceFormValues); } public static Assignments getAssignmentsForRule(GdlRule rule, SentenceModel model, Map<SentenceForm, ConstantForm> constantForms, Map<SentenceForm, ? extends Collection<GdlSentence>> completedSentenceFormValues) { return new Assignments(rule, model, constantForms, 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; } //Represents information about a sentence form that is constant. public static class ConstantForm { private SentenceForm form; private int numSlots; //True iff the slot has at most one value given the other slots' values private List<Boolean> dependentSlots = new ArrayList<Boolean>(); private List<Map<List<GdlConstant>, GdlConstant>> valueMaps = new ArrayList<Map<List<GdlConstant>, GdlConstant>>();; public ConstantForm(SentenceForm form, Map<GdlSentence, Component> values) { this.form = form; numSlots = form.getTupleSize(); List<List<GdlConstant>> tuples = getTrueTuples(form, values); for(int i = 0; i < numSlots; i++) { //We want to establish whether or not this is a constant... Map<List<GdlConstant>, GdlConstant> functionMap = new HashMap<List<GdlConstant>, GdlConstant>(); boolean functional = true; for(List<GdlConstant> tuple : tuples) { List<GdlConstant> tuplePart = new ArrayList<GdlConstant>(tuple.size() - 1); tuplePart.addAll(tuple.subList(0, i)); tuplePart.addAll(tuple.subList(i + 1, tuple.size())); if(functionMap.containsKey(tuplePart)) { //We have two tuples with different values in just this slot functional = false; break; } //Otherwise, we record it functionMap.put(tuplePart, tuple.get(i)); } if(functional) { //Record the function dependentSlots.add(true); valueMaps.add(functionMap); } else { //Forget it dependentSlots.add(false); valueMaps.add(null); } } } public ConstantForm(SentenceForm form, ConstantChecker constantChecker) { this.form = form; numSlots = form.getTupleSize(); for(int i = 0; i < numSlots; i++) { //We want to establish whether or not this is a constant... Map<List<GdlConstant>, GdlConstant> functionMap = new HashMap<List<GdlConstant>, GdlConstant>(); boolean functional = true; Iterator<List<GdlConstant>> itr = constantChecker.getTrueTuples(form); while(itr.hasNext()) { List<GdlConstant> tuple = itr.next(); List<GdlConstant> tuplePart = new ArrayList<GdlConstant>(tuple.size() - 1); tuplePart.addAll(tuple.subList(0, i)); tuplePart.addAll(tuple.subList(i + 1, tuple.size())); if(functionMap.containsKey(tuplePart)) { //We have two tuples with different values in just this slot functional = false; break; } //Otherwise, we record it functionMap.put(tuplePart, tuple.get(i)); } if(functional) { //Record the function dependentSlots.add(true); valueMaps.add(functionMap); } else { //Forget it dependentSlots.add(false); valueMaps.add(null); } } } private List<List<GdlConstant>> getTrueTuples(SentenceForm form, Map<GdlSentence, Component> values) { List<List<GdlConstant>> tuples = new ArrayList<List<GdlConstant>>(); //Working on the assumption that this will be faster than //going through all the keys in "values". I suppose I could //actually CHECK that this is the case, if it becomes an issue. for(GdlSentence sentence : form) { if(values.containsKey(sentence)) { Component value = values.get(sentence); if(value instanceof Constant && value.getValue()) { //It's a true constant //Add the tuple tuples.add(SentenceModel.getTupleFromGroundSentence(sentence)); } } } return tuples; } public Map<List<GdlConstant>, GdlConstant> getValueMap(int index) { return valueMaps.get(index); } public List<Boolean> getDependentSlots() { return dependentSlots; } /** * Given a sentence of the constant form's sentence form, finds all * the variables in the sentence that can be produced functionally. * * Note the corner case: If a variable appears twice in a sentence, * it CANNOT be produced in this way. */ public Set<GdlVariable> getProducibleVars(GdlSentence sentence) { if(!form.matches(sentence)) throw new RuntimeException("Sentence "+sentence+" does not match constant form"); List<GdlTerm> tuple = SentenceModel.getTupleFromSentence(sentence); Set<GdlVariable> candidateVars = new HashSet<GdlVariable>(); //Variables that appear multiple times go into multipleVars Set<GdlVariable> multipleVars = new HashSet<GdlVariable>(); //...which, of course, means we have to spot non-candidate vars Set<GdlVariable> nonCandidateVars = new HashSet<GdlVariable>(); for(int i = 0; i < tuple.size(); i++) { GdlTerm term = tuple.get(i); if(term instanceof GdlVariable && !multipleVars.contains(term)) { GdlVariable var = (GdlVariable) term; if(candidateVars.contains(var) || nonCandidateVars.contains(var)) { multipleVars.add(var); candidateVars.remove(var); } else if(dependentSlots.get(i)) { candidateVars.add(var); } else { nonCandidateVars.add(var); } } } return candidateVars; } } static class AssignmentFunction { //How is the AssignmentFunction going to operate? //Well, some of the variables are going to be //specified as having one or more of these functions //apply to them. (If multiple apply, they all have //to agree.) //We pass in the current value of the tuple and //it gives us the value desired (or null). //This means it just has to know which indices in //the tuple (i.e. which variables) correspond to //which slots in its native tuple. //Used when multiple assignment functions are relevant //to the same variable. In this case we call these other //functions with the same arguments and return null if //any of the answers differ. List<AssignmentFunction> internalFunctions; int querySize; List<Boolean> isInputConstant; List<GdlConstant> queryConstants; List<Integer> queryInputIndices; Map<List<GdlConstant>, GdlConstant> function = null; //Some sort of trie might work better here... public AssignmentFunction(GdlRelation conjunct, ConstantForm constForm, GdlVariable rightmostVar, List<GdlVariable> varOrder, Map<GdlVariable, GdlConstant> preassignment) { //We have to set up the things mentioned above... internalFunctions = new ArrayList<AssignmentFunction>(); //We can traverse the conjunct for the list of variables/constants... List<GdlTerm> terms = new ArrayList<GdlTerm>(); gatherVars(conjunct.getBody(), terms); //Note that we assume here that the var of interest only //appears once in the relation... int varIndex = terms.indexOf(rightmostVar); if(varIndex == -1) { System.out.println("conjunct is: " + conjunct); System.out.println("terms are: " + terms); System.out.println("righmostVar is: " + rightmostVar); } terms.remove(rightmostVar); function = constForm.getValueMap(varIndex); //Set up inputs and such, using terms querySize = terms.size(); isInputConstant = new ArrayList<Boolean>(terms.size()); queryConstants = new ArrayList<GdlConstant>(terms.size()); queryInputIndices = new ArrayList<Integer>(terms.size()); for(GdlTerm term : terms) { if(term instanceof GdlConstant) { isInputConstant.add(true); queryConstants.add((GdlConstant) term); queryInputIndices.add(-1); } else if(term instanceof GdlVariable) { //Is it in the head assignment? if(preassignment.containsKey(term)) { isInputConstant.add(true); queryConstants.add(preassignment.get(term)); queryInputIndices.add(-1); } else { isInputConstant.add(false); queryConstants.add(null); //What value do we put here? //We want to grab some value out of the //input tuple, which uses functional ordering //Index of the relevant variable, by the //assignment's ordering queryInputIndices.add(varOrder.indexOf(term)); } } } } public boolean functional() { return (function != null); } private void gatherVars(List<GdlTerm> body, List<GdlTerm> terms) { for(GdlTerm term : body) { if(term instanceof GdlConstant || term instanceof GdlVariable) terms.add(term); else if(term instanceof GdlFunction) gatherVars(((GdlFunction)term).getBody(), terms); } } public GdlConstant getValue(List<GdlConstant> remainingTuple) { //We have a map from a tuple of GdlConstants //to the GdlConstant we need, provided by the ConstantForm. //We need to make the tuple for this map. List<GdlConstant> queryTuple = new ArrayList<GdlConstant>(querySize); //Now we have to fill in the query for(int i = 0; i < querySize; i++) { if(isInputConstant.get(i)) { queryTuple.add(queryConstants.get(i)); } else { queryTuple.add(remainingTuple.get(queryInputIndices.get(i))); } } //The query is filled; we ask the map GdlConstant answer = function.get(queryTuple); for(AssignmentFunction internalFunction : internalFunctions) { if(internalFunction.getValue(remainingTuple) != answer) return null; } return answer; } } /** * 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 constantForms * @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<SentenceForm, ConstantForm> constantForms, 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 = SentenceModel.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(rule, model); List<Integer> sourceConjunctSizes = new ArrayList<Integer>(); for(GdlLiteral conjunct : rule.getBody()) { if(conjunct instanceof GdlRelation) { SentenceForm form = model.getSentenceForm((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>(SentenceModel.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> constantFormSentences = new ArrayList<GdlSentence>(); List<ConstantForm> constantFormsAsList = new ArrayList<ConstantForm>(); for(GdlLiteral conjunct : rule.getBody()) { if(conjunct instanceof GdlSentence) { SentenceForm form = model.getSentenceForm((GdlSentence) conjunct); if(constantForms != null && constantForms.containsKey(form)) { constantFormSentences.add((GdlSentence) conjunct); constantFormsAsList.add(constantForms.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, constantFormSentences, constantFormsAsList, varDomainSizes); PriorityQueue<IterationOrderCandidate> searchQueue = new PriorityQueue<IterationOrderCandidate>(); searchQueue.add(emptyCandidate); while(!searchQueue.isEmpty()) { IterationOrderCandidate curNode = searchQueue.remove(); 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, Integer> varDomainSizes = new HashMap<GdlVariable, Integer>(); Map<GdlVariable, List<GdlConstant>> varDomains = getVarDomains(rule, model); for(GdlVariable var : varDomains.keySet()) { varDomainSizes.put(var, varDomains.get(var).size()); } return varDomainSizes; } private static class IterationOrderCandidate implements Comparable<IterationOrderCandidate> { //Information specific to this ordering private List<Integer> sourceConjunctIndices; //Which conjuncts are we using as sources, and in what order? private List<GdlVariable> varOrdering; //In what order do we assign variables? private List<Integer> functionalConjunctIndices; //Same size as varOrdering //Index of conjunct if functional, -1 otherwise private List<Integer> varSources; //Same size as varOrdering //For each variable: Which source conjunct //originally contributes it? -1 if none //Becomes sourceResponsibleForVar //Information shared by the orderings //Presumably, this will also be used to construct the iterator to be used... private List<GdlVariable> varsToAssign; private List<GdlSentence> sourceConjunctCandidates; private List<Integer> sourceConjunctSizes; //same indexing as candidates private List<GdlSentence> constantFormSentences; private List<ConstantForm> constantForms; //Indexing same as constantFormSentences private Map<GdlVariable, Integer> varDomainSizes; /** * This constructor is for creating the start node of the * search. No part of the ordering is specified. * * @param sourceConjunctCandidates * @param sourceConjunctSizes * @param constantFormSentences * @param constantForms * @param allVars * @param varDomainSizes */ public IterationOrderCandidate( List<GdlVariable> varsToAssign, List<GdlSentence> sourceConjunctCandidates, List<Integer> sourceConjunctSizes, List<GdlSentence> constantFormSentences, List<ConstantForm> constantForms, Map<GdlVariable, Integer> varDomainSizes) { sourceConjunctIndices = new ArrayList<Integer>(); varOrdering = new ArrayList<GdlVariable>(); functionalConjunctIndices = new ArrayList<Integer>(); varSources = new ArrayList<Integer>(); this.varsToAssign = varsToAssign; this.sourceConjunctCandidates = sourceConjunctCandidates; this.sourceConjunctSizes = sourceConjunctSizes; this.constantFormSentences = constantFormSentences; this.constantForms = constantForms; this.varDomainSizes = varDomainSizes; } public List<GdlSentence> getFunctionalConjuncts() { //Returns, for each var, the conjunct defining it (if any) List<GdlSentence> functionalConjuncts = new ArrayList<GdlSentence>(functionalConjunctIndices.size()); for(int index : functionalConjunctIndices) { if(index == -1) functionalConjuncts.add(null); else functionalConjuncts.add(constantFormSentences.get(index)); } return functionalConjuncts; } public List<GdlSentence> getSourceConjuncts() { //These are the selected source conjuncts, not just the candidates. List<GdlSentence> sourceConjuncts = new ArrayList<GdlSentence>(sourceConjunctIndices.size()); for(int index : sourceConjunctIndices) { sourceConjuncts.add(sourceConjunctCandidates.get(index)); } return sourceConjuncts; } public List<GdlVariable> getVariableOrdering() { return varOrdering; } /** * This constructor is for "completing" the ordering by * adding all remaining variables, in some arbitrary order. * No source conjuncts or functions are added. */ public IterationOrderCandidate( IterationOrderCandidate parent) { //Shared rules this.varsToAssign = parent.varsToAssign; this.sourceConjunctCandidates = parent.sourceConjunctCandidates; this.sourceConjunctSizes = parent.sourceConjunctSizes; this.constantFormSentences = parent.constantFormSentences; this.constantForms = parent.constantForms; this.varDomainSizes = parent.varDomainSizes; //Individual rules: //We can share this because we won't be adding to it sourceConjunctIndices = parent.sourceConjunctIndices; //These others we'll be adding to varOrdering = new ArrayList<GdlVariable>(parent.varOrdering); functionalConjunctIndices = new ArrayList<Integer>(parent.functionalConjunctIndices); varSources = new ArrayList<Integer>(parent.varSources); //Fill out the ordering with all remaining variables: Easy enough for(GdlVariable var : varsToAssign) { if(!varOrdering.contains(var)) { varOrdering.add(var); functionalConjunctIndices.add(-1); varSources.add(-1); } } } /** * This constructor is for adding a source conjunct to an * ordering. * @param i The index of the source conjunct being added. */ public IterationOrderCandidate( IterationOrderCandidate parent, int i) { //Shared rules: this.varsToAssign = parent.varsToAssign; this.sourceConjunctCandidates = parent.sourceConjunctCandidates; this.sourceConjunctSizes = parent.sourceConjunctSizes; this.constantFormSentences = parent.constantFormSentences; this.constantForms = parent.constantForms; this.varDomainSizes = parent.varDomainSizes; //Individual rules: sourceConjunctIndices = new ArrayList<Integer>(parent.sourceConjunctIndices); varOrdering = new ArrayList<GdlVariable>(parent.varOrdering); functionalConjunctIndices = new ArrayList<Integer>(parent.functionalConjunctIndices); varSources = new ArrayList<Integer>(parent.varSources); //Add the new source conjunct sourceConjunctIndices.add(i); GdlSentence sourceConjunctCandidate = sourceConjunctCandidates.get(i); List<GdlVariable> varsFromConjunct = SentenceModel.getVariables(sourceConjunctCandidate); //Ignore both previously added vars and duplicates //Oh, but we need to be careful here, at some point. //i.e., what if there are multiple of the same variable //in a single statement? //That should probably be handled later. for(GdlVariable var : varsFromConjunct) { if(!varOrdering.contains(var)) { varOrdering.add(var); varSources.add(i); functionalConjunctIndices.add(-1); } } } /** * This constructor is for adding a function to the ordering. * @param constantForm * @param bestVariable */ public IterationOrderCandidate( IterationOrderCandidate parent, GdlSentence constantFormSentence, int constantFormIndex, GdlVariable functionOutput) { //Shared rules: this.varsToAssign = parent.varsToAssign; this.sourceConjunctCandidates = parent.sourceConjunctCandidates; this.sourceConjunctSizes = parent.sourceConjunctSizes; this.constantFormSentences = parent.constantFormSentences; this.constantForms = parent.constantForms; this.varDomainSizes = parent.varDomainSizes; //Individual rules: sourceConjunctIndices = new ArrayList<Integer>(parent.sourceConjunctIndices); varOrdering = new ArrayList<GdlVariable>(parent.varOrdering); functionalConjunctIndices = new ArrayList<Integer>(parent.functionalConjunctIndices); varSources = new ArrayList<Integer>(parent.varSources); //And we add the function List<GdlVariable> varsInFunction = SentenceModel.getVariables(constantFormSentence); //First, add the remaining arguments for(GdlVariable var : varsInFunction) { if(!varOrdering.contains(var) && !var.equals(functionOutput) && varsToAssign.contains(var)) { varOrdering.add(var); functionalConjunctIndices.add(-1); varSources.add(-1); } } //Then the output varOrdering.add(functionOutput); functionalConjunctIndices.add(constantFormIndex); varSources.add(-1); } public long getHeuristicValue() { long heuristic = 1; for(int sourceIndex : sourceConjunctIndices) { heuristic *= sourceConjunctSizes.get(sourceIndex); } for(int v = 0; v < varOrdering.size(); v++) { if(varSources.get(v) == -1 && functionalConjunctIndices.get(v) == -1) { //It's not set by a source conjunct or a function heuristic *= varDomainSizes.get(varOrdering.get(v)); } } //We want complete orderings to show up faster //so we add a little incentive to pick them //Add 1 to the value of non-complete orderings if(varOrdering.size() < varsToAssign.size()) heuristic++; return heuristic; } public boolean isComplete() { return varOrdering.containsAll(varsToAssign); } public List<IterationOrderCandidate> getChildren(boolean analyticFunctionOrdering) { List<IterationOrderCandidate> allChildren = new ArrayList<IterationOrderCandidate>(); allChildren.addAll(getSourceConjunctChildren()); allChildren.addAll(getFunctionAddedChildren(analyticFunctionOrdering)); return allChildren; } private List<IterationOrderCandidate> getSourceConjunctChildren() { List<IterationOrderCandidate> children = new ArrayList<IterationOrderCandidate>(); //If we are already using functions, short-circuit to cut off //repetition of the search space for(int index : functionalConjunctIndices) if(index != -1) return Collections.emptyList(); //This means we want a reference to the original list of conjuncts. int lastSourceConjunctIndex = -1; if(!sourceConjunctIndices.isEmpty()) lastSourceConjunctIndex = sourceConjunctIndices.get(sourceConjunctIndices.size() - 1); for(int i = lastSourceConjunctIndex + 1; i < sourceConjunctCandidates.size(); i++) { children.add(new IterationOrderCandidate(this, i)); } return children; } private List<IterationOrderCandidate> getFunctionAddedChildren(boolean analyticFunctionOrdering) { //We can't just add those functions that //are "ready" to be added. We should be adding all those variables //"leading up to" the functions and then applying the functions. //We can even take this one step further by only adding one child //per remaining constant function; we choose as our function output the //variable that is a candidate for functionhood that has the //largest domain, or one that is tied for largest. //New criterion: Must also NOT be in preassignment. List<IterationOrderCandidate> children = new ArrayList<IterationOrderCandidate>(); //It would be really nice here to just analytically choose //the set of functions we're going to use. //Here's one approach for doing that: //For each variable, get a list of the functions that could //potentially produce it. //For all the variables with no functions, add them. //Then repeatedly find the function with the fewest //number of additional variables (hopefully 0!) needed to //specify it and add it as a function. //The goal here is not to be optimal, but to be efficient! //Certain games (e.g. Pentago) break the old complete search method! //TODO: Eventual possible optimization here: //If something is dependent on a connected component that it is //not part of, wait until the connected component is resolved //(or something like that...) if(analyticFunctionOrdering && constantForms.size() > 8) { //For each variable, a list of functions //(refer to functions by their indices) //and the set of outstanding vars they depend on... Map<GdlVariable, Set<Integer>> functionsProducingVars = new HashMap<GdlVariable, Set<Integer>>(); //We start by adding to the varOrdering the vars not produced by functions //First, we have to find them for(int i = 0; i < constantForms.size(); i++) { GdlSentence cfs = constantFormSentences.get(i); ConstantForm cf = constantForms.get(i); Set<GdlVariable> producibleVars = cf.getProducibleVars(cfs); for(GdlVariable producibleVar : producibleVars) { if(!functionsProducingVars.containsKey(producibleVar)) functionsProducingVars.put(producibleVar, new HashSet<Integer>()); functionsProducingVars.get(producibleVar).add(i); } } //Non-producible vars get iterated over before we start //deciding which functions to add for(GdlVariable var : varsToAssign) { if(!varOrdering.contains(var)) { if(!functionsProducingVars.containsKey(var)) { //Add var to the ordering varOrdering.add(var); functionalConjunctIndices.add(-1); varSources.add(-1); } } } //Map is from potential set of dependencies to function indices Map<Set<GdlVariable>, Set<Integer>> functionsHavingDependencies = new HashMap<Set<GdlVariable>, Set<Integer>>(); //Create this map... for(int i = 0; i < constantForms.size(); i++) { GdlSentence cfs = constantFormSentences.get(i); ConstantForm cf = constantForms.get(i); Set<GdlVariable> producibleVars = cf.getProducibleVars(cfs); Set<GdlVariable> allVars = new HashSet<GdlVariable>(SentenceModel.getVariables(cfs)); //Variables already in varOrdering don't go in dependents list producibleVars.removeAll(varOrdering); allVars.removeAll(varOrdering); for(GdlVariable producibleVar : producibleVars) { Set<GdlVariable> dependencies = new HashSet<GdlVariable>(); dependencies.addAll(allVars); dependencies.remove(producibleVar); if(!functionsHavingDependencies.containsKey(dependencies)) functionsHavingDependencies.put(dependencies, new HashSet<Integer>()); functionsHavingDependencies.get(dependencies).add(i); } } //Now, we can keep creating functions to generate the remaining variables while(varOrdering.size() < varsToAssign.size()) { if(functionsHavingDependencies.isEmpty()) throw new RuntimeException("We should not run out of functions we could use"); //Find the smallest set of dependencies Set<GdlVariable> dependencySetToUse = null; if(functionsHavingDependencies.containsKey(Collections.emptySet())) { dependencySetToUse = Collections.emptySet(); } else { int smallestSize = Integer.MAX_VALUE; for(Set<GdlVariable> dependencySet : functionsHavingDependencies.keySet()) { if(dependencySet.size() < smallestSize) { smallestSize = dependencySet.size(); dependencySetToUse = dependencySet; } } } //See if any of the functions are applicable Set<Integer> functions = functionsHavingDependencies.get(dependencySetToUse); int functionToUse = -1; GdlVariable varProduced = null; for(int function : functions) { GdlSentence cfs = constantFormSentences.get(function); ConstantForm cf = constantForms.get(function); Set<GdlVariable> producibleVars = cf.getProducibleVars(cfs); producibleVars.removeAll(dependencySetToUse); producibleVars.removeAll(varOrdering); if(!producibleVars.isEmpty()) { functionToUse = function; varProduced = producibleVars.iterator().next(); break; } } if(functionToUse == -1) { //None of these functions were actually useful now? //Dump the dependency set functionsHavingDependencies.remove(dependencySetToUse); } else { //Apply the function //1) Add the remaining dependencies as iterated variables for(GdlVariable var : dependencySetToUse) { varOrdering.add(var); functionalConjunctIndices.add(-1); varSources.add(-1); } //2) Add the function's produced variable (varProduced) varOrdering.add(varProduced); functionalConjunctIndices.add(functionToUse); varSources.add(-1); //3) Remove all vars added this way from all dependency sets Set<GdlVariable> addedVars = new HashSet<GdlVariable>(); addedVars.addAll(dependencySetToUse); addedVars.add(varProduced); //Tricky, because we have to merge sets //Easier to use a new map Map<Set<GdlVariable>, Set<Integer>> newFunctionsHavingDependencies = new HashMap<Set<GdlVariable>, Set<Integer>>(); for(Entry<Set<GdlVariable>, Set<Integer>> entry : functionsHavingDependencies.entrySet()) { Set<GdlVariable> newKey = new HashSet<GdlVariable>(entry.getKey()); newKey.removeAll(addedVars); if(!newFunctionsHavingDependencies.containsKey(newKey)) newFunctionsHavingDependencies.put(newKey, new HashSet<Integer>()); newFunctionsHavingDependencies.get(newKey).addAll(entry.getValue()); } functionsHavingDependencies = newFunctionsHavingDependencies; //4) Remove this function from the lists? for(Set<Integer> functionSet : functionsHavingDependencies.values()) functionSet.remove(functionToUse); } } //Now we need to actually return the ordering in a list //Here's the quick way to do that... //(since we've added all the new stuff to ourself already) return Collections.singletonList(new IterationOrderCandidate(this)); } else { //Let's try a new technique for restricting the space of possibilities... //We already have an ordering on the functions //Let's try to constrain things to that order //Namely, if i<j and constant form j is already used as a function, //we cannot use constant form i UNLESS constant form j supplies //as its variable something used by constant form i. //We might also try requiring that c.f. i NOT provide a variable //used by c.f. j, though there may be multiple possibilities as //to what it could provide. int lastFunctionUsedIndex = -1; if(!functionalConjunctIndices.isEmpty()) lastFunctionUsedIndex = Collections.max(functionalConjunctIndices); Set<GdlVariable> varsProducedByFunctions = new HashSet<GdlVariable>(); for(int i = 0; i < functionalConjunctIndices.size(); i++) if(functionalConjunctIndices.get(i) != -1) varsProducedByFunctions.add(varOrdering.get(i)); for(int i = 0; i < constantForms.size(); i++) { GdlSentence constantFormSentence = constantFormSentences.get(i); ConstantForm constantForm = constantForms.get(i); if(i < lastFunctionUsedIndex) { //We need to figure out whether i could use any of the //vars we're producing with functions //TODO: Try this with a finer grain //i.e., see if i needs a var from a function that is after //it, not one that might be before it List<GdlVariable> varsInSentence = SentenceModel.getVariables(constantFormSentence); if(Collections.disjoint(varsInSentence, varsProducedByFunctions)) continue; } //What is the best variable to grab from this form, if there are any? GdlVariable bestVariable = getBestVariable(constantFormSentence, constantForm); if(bestVariable == null) continue; IterationOrderCandidate newCandidate = new IterationOrderCandidate(this, constantFormSentence, i, bestVariable); children.add(newCandidate); } //If there are no more functions to add, add the completed version if(children.isEmpty()) { children.add(new IterationOrderCandidate(this)); } return children; } } private GdlVariable getBestVariable(GdlSentence constantFormSentence, ConstantForm constantForm) { //If all the variables that can be set by the constant form are in //the varOrdering, we return null. Otherwise, we return one of //those with the largest domain. //The ConstantForm is sentence-independent, so we need the context //of the sentence (which has variables in it). List<GdlTerm> tuple = SentenceModel.getTupleFromSentence(constantFormSentence); List<Boolean> dependentSlots = constantForm.getDependentSlots(); if(tuple.size() != dependentSlots.size()) throw new RuntimeException("Mismatched sentence " + constantFormSentence + " and constant form " + constantForm); Set<GdlVariable> candidateVars = new HashSet<GdlVariable>(); for(int i = 0; i < tuple.size(); i++) { GdlTerm term = tuple.get(i); if(term instanceof GdlVariable && dependentSlots.get(i) && !varOrdering.contains(term) && varsToAssign.contains(term)) candidateVars.add((GdlVariable) term); } //Now we look at the domains, trying to find the largest GdlVariable bestVar = null; int bestDomainSize = 0; for(GdlVariable var : candidateVars) { int domainSize = varDomainSizes.get(var); if(domainSize > bestDomainSize) { bestVar = var; bestDomainSize = domainSize; } } return bestVar; //null if none are usable } //This class has a natural ordering that is inconsistent with equals. @Override public int compareTo(IterationOrderCandidate o) { long diff = getHeuristicValue() - o.getHeuristicValue(); if(diff < 0) return -1; else if(diff == 0) return 0; else return 1; } @Override public String toString() { return varOrdering.toString() + " with sources " + getSourceConjuncts().toString() + "; functional?: " + functionalConjunctIndices; } } public static long getNumAssignmentsEstimate(GdlRule rule, SentenceModel model, ConstantChecker checker, boolean analyticFunctionOrdering) { //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, ConstantForm> constantForms = new HashMap<SentenceForm, ConstantForm>(); for(SentenceForm form : model.getConstantSentenceForms()) constantForms.put(form, new ConstantForm(form, checker)); //Populate variable domain sizes using the constant checker Map<SentenceForm, Integer> domainSizes = new HashMap<SentenceForm, Integer>(); for(SentenceForm form : checker.getSentenceForms()) { domainSizes.put(form, checker.getNumTrueTuples(form)); } //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, constantForms, null, null, analyticFunctionOrdering); return ordering.getHeuristicValue(); } }