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.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Set; import java.util.Map.Entry; import util.gdl.grammar.Gdl; import util.gdl.grammar.GdlConstant; import util.gdl.grammar.GdlDistinct; import util.gdl.grammar.GdlLiteral; import util.gdl.grammar.GdlNot; import util.gdl.grammar.GdlPool; import util.gdl.grammar.GdlRelation; import util.gdl.grammar.GdlRule; import util.gdl.grammar.GdlSentence; import util.gdl.grammar.GdlVariable; import util.gdl.model.SentenceModel; import util.gdl.model.SentenceModel.SentenceForm; import util.gdl.transforms.CommonTransforms; import util.gdl.transforms.Relationizer; import util.gdl.transforms.SimpleCondensationIsolator; import util.gdl.transforms.CrudeSplitter; import util.gdl.transforms.DeORer; import util.gdl.transforms.GdlCleaner; import util.gdl.transforms.CondensationIsolator; import util.gdl.transforms.ConstantFinder; import util.gdl.transforms.VariableConstrainer; import util.gdl.transforms.ConstantFinder.ConstantChecker; import util.propnet.architecture.Component; import util.propnet.architecture.PropNet; import util.propnet.architecture.components.And; import util.propnet.architecture.components.Constant; import util.propnet.architecture.components.Not; import util.propnet.architecture.components.Or; import util.propnet.architecture.components.Proposition; import util.propnet.architecture.components.Transition; import util.propnet.factory.Assignments.AssignmentIterator; import util.propnet.factory.Assignments.ConstantForm; import util.statemachine.Role; /* * A propnet factory meant to optimize the propnet before it's even built, * mostly through transforming the GDL. (The transformations identify certain * classes of rules that have poor performance and replace them with equivalent * rules that have better performance, with performance measured by the size of * the propnet.) * * Known issues: * - Does not work on games with many advanced forms of recursion. These include: * - Anything that breaks the SentenceModel * - Multiple sentence forms which reference one another in rules * - Not 100% confirmed to work on games where recursive rules have multiple * recursive conjuncts * - Currently runs some of the transformations multiple times. A Description * object containing information about the description and its properties would * alleviate this. * - Its current solution to the "unaffected piece rule" problem is somewhat * clumsy and ungeneralized, relying on the combined behaviors of CrudeSplitter * and CondensationIsolator. * - The mutex finder in particular is very ungeneralized. It should be replaced * with a more general mutex finder. * - Depending on the settings and the situation, the behavior of the * CondensationIsolator can be either too aggressive or not aggressive enough. * Both result in excessively large games. A more sophisticated version of the * CondensationIsolator could solve these problems. A stopgap alternative is to * try both settings and use the smaller propnet (or the first to be created, * if multithreading). * */ public class OptimizingPropNetFactory { static final private GdlConstant LEGAL = GdlPool.getConstant("legal"); static final private GdlConstant NEXT = GdlPool.getConstant("next"); static final private GdlConstant TRUE = GdlPool.getConstant("true"); static final private GdlConstant DOES = GdlPool.getConstant("does"); static final private GdlConstant GOAL = GdlPool.getConstant("goal"); static final private GdlConstant INIT = GdlPool.getConstant("init"); static final private GdlConstant INIT_CAPS = GdlPool.getConstant("INIT"); @SuppressWarnings("unused") static final private GdlConstant BASE = GdlPool.getConstant("base"); @SuppressWarnings("unused") static final private GdlConstant INPUT = GdlPool.getConstant("input"); static final private GdlConstant TEMP = GdlPool.getConstant("TEMP"); public static PropNet create(List<Gdl> description) { return create(description, false); } //These heuristic methods work best on the vast majority of games. //Still problems with conn4, mummyMaze2p_2007, sudoku2; // possibly others? public static PropNet create(List<Gdl> description, boolean verbose) { return create(description, verbose, true, false, false, false, true, true); } public static PropNet create(List<Gdl> description, boolean verbose, boolean useAdvancedCondensers, boolean moreRestraint, boolean useCrudeSplitter, boolean constConstraint, boolean useHeuristic, boolean analyticFunctionOrdering) { System.out.println("Building propnet..."); long startTime = System.currentTimeMillis(); description = GdlCleaner.run(description); description = DeORer.run(description); description = VariableConstrainer.replaceFunctionValuedVariables(description); description = Relationizer.run(description); if(useCrudeSplitter) { description = CrudeSplitter.run(description); description = CondensationIsolator.run(description, true, true, true, constConstraint, useHeuristic, analyticFunctionOrdering); } else { if(!useAdvancedCondensers) description = SimpleCondensationIsolator.run(description, false); if(useAdvancedCondensers) description = CondensationIsolator.run(description, true, moreRestraint, true, constConstraint, useHeuristic, analyticFunctionOrdering); } if(verbose) for(Gdl gdl : description) System.out.println(gdl); //We want to start with a rule graph and follow the rule graph. //Start with the constants, etc. SentenceModel model = new SentenceModel(description); //Restrict domains to values that could actually come up in rules. //See chinesecheckers4's "count" relation for an example of why this //could be useful. In most situations, this has no effect. model.restrictDomainsToUsefulValues(); boolean usingBase = model.getSentenceNames().contains("base"); boolean usingInput = model.getSentenceNames().contains("input"); //Is this a good place to get the constants? if(verbose) System.out.println("Setting constants..."); ConstantChecker constantChecker = ConstantFinder.getConstants(description); if(verbose) System.out.println("Done setting constants"); //For now, we're going to build this to work on those with a //particular restriction on the dependency graph: //Recursive loops may only contain one sentence form. //This describes most games, but not all legal games. Map<SentenceForm, Set<SentenceForm>> dependencyGraph = model.getDependencyGraph(); if(verbose) { System.out.print("Computing topological ordering... "); System.out.flush(); } List<SentenceForm> topologicalOrdering = getTopologicalOrdering(model.getSentenceForms(), dependencyGraph, usingBase, usingInput); if(verbose) System.out.println("done"); //Now what? //PropNet propnet = new PropNet(); This is actually the last step List<Role> roles = Role.computeRoles(description); Map<GdlSentence, Component> components = new HashMap<GdlSentence, Component>(); Map<GdlSentence, Component> negations = new HashMap<GdlSentence, Component>(); Constant trueComponent = new Constant(true); Constant falseComponent = new Constant(false); Map<SentenceForm, ConstantForm> constantForms = new HashMap<SentenceForm, ConstantForm>(); Map<SentenceForm, Collection<GdlSentence>> completedSentenceFormValues = new HashMap<SentenceForm, Collection<GdlSentence>>(); for(SentenceForm form : topologicalOrdering) { if(verbose) { System.out.print("Adding sentence form " + form); System.out.flush(); } if(constantChecker.isConstantForm(form)) { if(verbose) System.out.println(" (constant)"); //Only add it if it's important if(form.getName().equals(LEGAL) || form.getName().equals(GOAL) || form.getName().equals(INIT)) { //Add it Iterator<GdlSentence> sentenceItr = constantChecker.getTrueSentences(form); if(!sentenceItr.hasNext()) System.out.println("Empty sentence iterator"); while(sentenceItr.hasNext()) { GdlSentence trueSentence = sentenceItr.next(); //System.out.println("Adding prop for sentence " + trueSentence); Proposition trueProp = new Proposition(trueSentence.toTerm()); trueProp.addInput(trueComponent); trueComponent.addOutput(trueProp); //components.put(trueSentence, trueProp); components.put(trueSentence, trueComponent); } } if(verbose) System.out.println("Checking whether " + form + " is a functional constant..."); addToConstants(form, constantChecker, constantForms); addFormToCompletedValues(form, completedSentenceFormValues, constantChecker); continue; } if(verbose) System.out.println(); //TODO: Adjust "recursive forms" appropriately //Add a temporary sentence form thingy? ... Map<GdlSentence, Component> temporaryComponents = new HashMap<GdlSentence, Component>(); Map<GdlSentence, Component> temporaryNegations = new HashMap<GdlSentence, Component>(); addSentenceForm(form, model, description, components, negations, trueComponent, falseComponent, usingBase, usingInput, Collections.singleton(form), temporaryComponents, temporaryNegations, constantForms, constantChecker, completedSentenceFormValues); //TODO: Pass these over groups of multiple sentence forms if(verbose && !temporaryComponents.isEmpty()) System.out.println("Processing temporary components..."); processTemporaryComponents(temporaryComponents, temporaryNegations, components, negations, trueComponent, falseComponent); addFormToCompletedValues(form, completedSentenceFormValues, components); //if(verbose) //TODO: Add this, but with the correct total number of components (not just Propositions) //System.out.println(" "+completedSentenceFormValues.get(form).size() + " components added"); } //Connect "next" to "true" if(verbose) System.out.println("Adding transitions..."); addTransitions(components); //Set up "init" proposition if(verbose) System.out.println("Setting up 'init' proposition..."); setUpInit(components, trueComponent, falseComponent, constantChecker); if(verbose) System.out.println("Creating component set..."); Set<Component> componentSet = new HashSet<Component>(components.values()); //Try saving some memory here... components = null; negations = null; completeComponentSet(componentSet); if(verbose) System.out.println("Initializing propnet object..."); PropNet propnet = new PropNet(roles, componentSet); if(verbose) { System.out.println("Done setting up propnet; took " + (System.currentTimeMillis() - startTime) + "ms, has " + componentSet.size() + " components and " + propnet.getNumLinks() + " links"); System.out.println("Propnet has " +propnet.getNumAnds()+" ands; "+propnet.getNumOrs()+" ors; "+propnet.getNumNots()+" nots"); } //System.out.println(propnet); //constantChecker.destroy(); return propnet; } private static void addFormToCompletedValues( SentenceForm form, Map<SentenceForm, Collection<GdlSentence>> completedSentenceFormValues, ConstantChecker constantChecker) { constantChecker.getTrueSentences(form); List<GdlSentence> sentences = new ArrayList<GdlSentence>(); Iterator<GdlSentence> itr = constantChecker.getTrueSentences(form); while(itr.hasNext()) sentences.add(itr.next()); completedSentenceFormValues.put(form, sentences); } private static void addFormToCompletedValues( SentenceForm form, Map<SentenceForm, Collection<GdlSentence>> completedSentenceFormValues, Map<GdlSentence, Component> components) { //Kind of inefficient. Could do better by collecting these as we go, //then adding them back into the CSFV map once the sentence forms are complete. //completedSentenceFormValues.put(form, new ArrayList<GdlSentence>()); List<GdlSentence> sentences = new ArrayList<GdlSentence>(); for(GdlSentence sentence : components.keySet()) { if(form.matches(sentence)) { //The sentence has a node associated with it sentences.add(sentence); } } completedSentenceFormValues.put(form, sentences); } private static void addToConstants(SentenceForm form, ConstantChecker constantChecker, Map<SentenceForm, ConstantForm> constantForms) { constantForms.put(form, new ConstantForm(form, constantChecker)); } private static void processTemporaryComponents( Map<GdlSentence, Component> temporaryComponents, Map<GdlSentence, Component> temporaryNegations, Map<GdlSentence, Component> components, Map<GdlSentence, Component> negations, Component trueComponent, Component falseComponent) { //For each component in temporary components, we want to "put it back" //into the main components section. //We also want to do optimization here... //We don't want to end up with anything following from true/false. //Everything following from a temporary component (its outputs) //should instead become an output of the actual component. //If there is no actual component generated, then the statement //is necessarily FALSE and should be replaced by the false //component. for(GdlSentence sentence : temporaryComponents.keySet()) { Component tempComp = temporaryComponents.get(sentence); Component realComp = components.get(sentence); if(realComp == null) { realComp = falseComponent; } for(Component output : tempComp.getOutputs()) { //Disconnect output.removeInput(tempComp); //tempComp.removeOutput(output); //do at end //Connect output.addInput(realComp); realComp.addOutput(output); } tempComp.removeAllOutputs(); if(temporaryNegations.containsKey(sentence)) { //Should be pointing to a "not" that now gets input from realComp //Should be fine to put into negations negations.put(sentence, temporaryNegations.get(sentence)); //If this follows true/false, will get resolved by the next set of optimizations } while(hasNonessentialChildren(trueComponent) || hasNonessentialChildren(falseComponent)) { optimizeAwayTrue(components, negations, trueComponent, falseComponent); optimizeAwayFalse(components, negations, trueComponent, falseComponent); } } } private static void optimizeAwayFalse( Map<GdlSentence, Component> components, Map<GdlSentence, Component> negations, Component trueComponent, Component falseComponent) { while(hasNonessentialChildren(falseComponent)) { Iterator<Component> outputItr = falseComponent.getOutputs().iterator(); Component output = outputItr.next(); while(isEssentialProposition(output) || output instanceof Transition) output = outputItr.next(); if(output instanceof Proposition) { //Move its outputs to be outputs of false for(Component child : output.getOutputs()) { //Disconnect child.removeInput(output); //output.removeOutput(child); //do at end //Reconnect; will get children before returning, if nonessential falseComponent.addOutput(child); child.addInput(falseComponent); } output.removeAllOutputs(); if(!isEssentialProposition(output)) { Proposition prop = (Proposition) output; //Remove the proposition entirely falseComponent.removeOutput(output); output.removeInput(falseComponent); //Update its location to the trueComponent in our map components.put(prop.getName().toSentence(), falseComponent); negations.put(prop.getName().toSentence(), trueComponent); } } else if(output instanceof And) { And and = (And) output; //Attach children of and to falseComponent for(Component child : and.getOutputs()) { child.addInput(falseComponent); falseComponent.addOutput(child); child.removeInput(and); } //Disconnect and completely and.removeAllOutputs(); for(Component parent : and.getInputs()) parent.removeOutput(and); and.removeAllInputs(); } else if(output instanceof Or) { Or or = (Or) output; //Remove as input from or or.removeInput(falseComponent); falseComponent.removeOutput(or); //If or has only one input, remove it if(or.getInputs().size() == 1) { Component in = or.getSingleInput(); or.removeInput(in); in.removeOutput(or); for(Component out : or.getOutputs()) { //Disconnect from and out.removeInput(or); //or.removeOutput(out); //do at end //Connect directly to the new input out.addInput(in); in.addOutput(out); } or.removeAllOutputs(); } } else if(output instanceof Not) { Not not = (Not) output; //Disconnect from falseComponent not.removeInput(falseComponent); falseComponent.removeOutput(not); //Connect all children of the not to trueComponent for(Component child : not.getOutputs()) { //Disconnect child.removeInput(not); //not.removeOutput(child); //Do at end //Connect to trueComponent child.addInput(trueComponent); trueComponent.addOutput(child); } not.removeAllOutputs(); } else if(output instanceof Transition) { //??? System.err.println("Fix optimizeAwayFalse's case for Transitions"); } } } private static void optimizeAwayTrue( Map<GdlSentence, Component> components, Map<GdlSentence, Component> negations, Component trueComponent, Component falseComponent) { while(hasNonessentialChildren(trueComponent)) { Iterator<Component> outputItr = trueComponent.getOutputs().iterator(); Component output = outputItr.next(); while(isEssentialProposition(output) || output instanceof Transition) output = outputItr.next(); if(output instanceof Proposition) { //Move its outputs to be outputs of true for(Component child : output.getOutputs()) { //Disconnect child.removeInput(output); //output.removeOutput(child); //do at end //Reconnect; will get children before returning, if nonessential trueComponent.addOutput(child); child.addInput(trueComponent); } output.removeAllOutputs(); if(!isEssentialProposition(output)) { Proposition prop = (Proposition) output; //Remove the proposition entirely trueComponent.removeOutput(output); output.removeInput(trueComponent); //Update its location to the trueComponent in our map components.put(prop.getName().toSentence(), trueComponent); negations.put(prop.getName().toSentence(), falseComponent); } } else if(output instanceof Or) { Or or = (Or) output; //Attach children of or to trueComponent for(Component child : or.getOutputs()) { child.addInput(trueComponent); trueComponent.addOutput(child); child.removeInput(or); } //Disconnect or completely or.removeAllOutputs(); for(Component parent : or.getInputs()) parent.removeOutput(or); or.removeAllInputs(); } else if(output instanceof And) { And and = (And) output; //Remove as input from and and.removeInput(trueComponent); trueComponent.removeOutput(and); //If and has only one input, remove it if(and.getInputs().size() == 1) { Component in = and.getSingleInput(); and.removeInput(in); in.removeOutput(and); for(Component out : and.getOutputs()) { //Disconnect from and out.removeInput(and); //and.removeOutput(out); //do at end //Connect directly to the new input out.addInput(in); in.addOutput(out); } and.removeAllOutputs(); } } else if(output instanceof Not) { Not not = (Not) output; //Disconnect from trueComponent not.removeInput(trueComponent); trueComponent.removeOutput(not); //Connect all children of the not to falseComponent for(Component child : not.getOutputs()) { //Disconnect child.removeInput(not); //not.removeOutput(child); //Do at end //Connect to falseComponent child.addInput(falseComponent); falseComponent.addOutput(child); } not.removeAllOutputs(); } else if(output instanceof Transition) { //??? System.err.println("Fix optimizeAwayTrue's case for Transitions"); } } } private static boolean hasNonessentialChildren(Component trueComponent) { for(Component child : trueComponent.getOutputs()) { if(child instanceof Transition) continue; if(!isEssentialProposition(child)) return true; //We don't want any grandchildren, either if(!child.getOutputs().isEmpty()) return true; } return false; } private static boolean isEssentialProposition(Component component) { if(!(component instanceof Proposition)) return false; //We're looking for things that would be outputs of "true" or "false", //but we would still want to keep as propositions to be read by the //state machine ("init" is handled separately) Proposition prop = (Proposition) component; GdlConstant name = prop.getName().toSentence().getName(); return (name.equals(LEGAL) || name.equals(NEXT) || name.equals(GOAL)); } private static void completeComponentSet(Set<Component> componentSet) { Set<Component> newComponents = new HashSet<Component>(); Set<Component> componentsToTry = new HashSet<Component>(componentSet); while(!componentsToTry.isEmpty()) { for(Component c : componentsToTry) { for(Component out : c.getOutputs()) { if(!componentSet.contains(out)) newComponents.add(out); } for(Component in : c.getInputs()) { if(!componentSet.contains(in)) newComponents.add(in); } } componentSet.addAll(newComponents); componentsToTry = newComponents; newComponents = new HashSet<Component>(); } } private static void addTransitions(Map<GdlSentence, Component> components) { for(Entry<GdlSentence, Component> entry : components.entrySet()) { GdlSentence sentence = entry.getKey(); if(sentence.getName().equals(NEXT)) { //connect to true GdlSentence trueSentence = GdlPool.getRelation(TRUE, sentence.getBody()); Component nextComponent = entry.getValue(); Component trueComponent = components.get(trueSentence); Transition transition = new Transition(); transition.addInput(nextComponent); nextComponent.addOutput(transition); transition.addOutput(trueComponent); if(trueComponent == null) System.out.println(trueSentence); trueComponent.addInput(transition); } } } //TODO: Replace with version using constantChecker only private static void setUpInit(Map<GdlSentence, Component> components, Constant trueComponent, Constant falseComponent, ConstantChecker constantChecker) { Proposition initProposition = new Proposition(INIT_CAPS); for(Entry<GdlSentence, Component> entry : components.entrySet()) { //Is this something that will be true? if(entry.getValue() == trueComponent) { if(entry.getKey().getName().equals(INIT)) { //Find the corresponding true sentence GdlSentence trueSentence = GdlPool.getRelation(TRUE, entry.getKey().getBody()); //System.out.println("True sentence from init: " + trueSentence); Component trueSentenceComponent = components.get(trueSentence); if(trueSentenceComponent.getInputs().isEmpty()) { //Case where there is no transition input //Add the transition input, connect to init, continue loop Transition transition = new Transition(); //init goes into transition transition.addInput(initProposition); initProposition.addOutput(transition); //transition goes into component trueSentenceComponent.addInput(transition); transition.addOutput(trueSentenceComponent); } else { //The transition already exists Component transition = trueSentenceComponent.getSingleInput(); //We want to add init as a thing that precedes the transition //Disconnect existing input Component input = transition.getSingleInput(); //input and init go into or, or goes into transition input.removeOutput(transition); transition.removeInput(input); List<Component> orInputs = new ArrayList<Component>(2); orInputs.add(input); orInputs.add(initProposition); orify(orInputs, transition, falseComponent); } } } } } /** * Adds an or gate connecting the inputs to produce the output. * Handles special optimization cases like a true/false input. */ private static void orify(Collection<Component> inputs, Component output, Constant falseProp) { //TODO: Look for already-existing ors with the same inputs? //Or can this be handled with a GDL transformation? //Special case: An input is the true constant for(Component in : inputs) { if(in instanceof Constant && in.getValue()) { //True constant: connect that to the component, done in.addOutput(output); output.addInput(in); return; } } //Special case: An input is "or" //I'm honestly not sure how to handle special cases here... //What if that "or" gate has multiple outputs? Could that happen? //For reals... just skip over any false constants Or or = new Or(); for(Component in : inputs) { if(!(in instanceof Constant)) { in.addOutput(or); or.addInput(in); } } //What if they're all false? (Or inputs is empty?) Then no inputs at this point... if(or.getInputs().isEmpty()) { //Hook up to "false" falseProp.addOutput(output); output.addInput(falseProp); return; } //If there's just one, on the other hand, don't use the or gate if(or.getInputs().size() == 1) { Component in = or.getSingleInput(); in.removeOutput(or); or.removeInput(in); in.addOutput(output); output.addInput(in); return; } or.addOutput(output); output.addInput(or); } //TODO: This code is currently used by multiple classes, so perhaps it should be //factored out into the SentenceModel. private static List<SentenceForm> getTopologicalOrdering( Set<SentenceForm> forms, Map<SentenceForm, Set<SentenceForm>> dependencyGraph, boolean usingBase, boolean usingInput) { //We want each form as a key of the dependency graph to //follow all the forms in the dependency graph, except maybe itself Queue<SentenceForm> queue = new LinkedList<SentenceForm>(forms); List<SentenceForm> ordering = new ArrayList<SentenceForm>(forms.size()); Set<SentenceForm> alreadyOrdered = new HashSet<SentenceForm>(); while(!queue.isEmpty()) { SentenceForm curForm = queue.remove(); boolean readyToAdd = true; //Don't add if there are dependencies if(dependencyGraph.get(curForm) != null) { for(SentenceForm dependency : dependencyGraph.get(curForm)) { if(!dependency.equals(curForm) && !alreadyOrdered.contains(dependency)) { readyToAdd = false; break; } } } //Don't add if it's true/next/legal/does and we're waiting for base/input if(usingBase && (curForm.getName().equals(TRUE) || curForm.getName().equals(NEXT) || curForm.getName().equals(INIT))) { //Have we added the corresponding base sf yet? SentenceForm baseForm = curForm.getCopyWithName("base"); if(!alreadyOrdered.contains(baseForm)) { readyToAdd = false; } } if(usingInput && (curForm.getName().equals(DOES) || curForm.getName().equals(LEGAL))) { SentenceForm inputForm = curForm.getCopyWithName("input"); if(!alreadyOrdered.contains(inputForm)) { readyToAdd = false; } } //Add it if(readyToAdd) { ordering.add(curForm); alreadyOrdered.add(curForm); } else { queue.add(curForm); } //TODO: Add check for an infinite loop here, or stratify loops } return ordering; } private static void addSentenceForm(SentenceForm form, SentenceModel model, List<Gdl> description, Map<GdlSentence, Component> components, Map<GdlSentence, Component> negations, Constant trueComponent, Constant falseComponent, boolean usingBase, boolean usingInput, Set<SentenceForm> recursionForms, Map<GdlSentence, Component> temporaryComponents, Map<GdlSentence, Component> temporaryNegations, Map<SentenceForm, ConstantForm> constantForms, ConstantChecker constantChecker, Map<SentenceForm, Collection<GdlSentence>> completedSentenceFormValues) { //This is the meat of it (along with the entire Assignments class). //We need to enumerate the possible propositions in the sentence form... //We also need to hook up the sentence form to the inputs that can make it true. //We also try to optimize as we go, which means possibly removing the //proposition if it isn't actually possible, or replacing it with //true/false if it's a constant. Set<GdlRelation> relations = model.getRelations(form); Set<GdlRule> rules = model.getRules(form); for(GdlRelation relation : relations) { //We add the sentence as a constant if(relation.getName().equals(LEGAL) || relation.getName().equals(NEXT) || relation.getName().equals(GOAL)) { Proposition prop = new Proposition(relation.toTerm()); //Attach to true trueComponent.addOutput(prop); prop.addInput(trueComponent); //Still want the same components; //we just don't want this to be anonymized } //Assign as true components.put(relation, trueComponent); negations.put(relation, falseComponent); continue; } //For does/true, make nodes based on input/base, if available if(usingInput && form.getName().equals(DOES)) { //Add only those propositions for which there is a corresponding INPUT SentenceForm inputForm = form.getCopyWithName("input"); Iterator<GdlSentence> itr = constantChecker.getTrueSentences(inputForm); while(itr.hasNext()) { GdlSentence inputSentence = itr.next(); GdlSentence doesSentence = GdlPool.getRelation(DOES, inputSentence.getBody()); Proposition prop = new Proposition(doesSentence.toTerm()); components.put(doesSentence, prop); } return; } if(usingBase && form.getName().equals(TRUE)) { SentenceForm baseForm = form.getCopyWithName("base"); Iterator<GdlSentence> itr = constantChecker.getTrueSentences(baseForm); while(itr.hasNext()) { GdlSentence baseSentence = itr.next(); GdlSentence trueSentence = GdlPool.getRelation(TRUE, baseSentence.getBody()); Proposition prop = new Proposition(trueSentence.toTerm()); components.put(trueSentence, prop); } return; } Map<GdlSentence, Set<Component>> inputsToOr = new HashMap<GdlSentence, Set<Component>>(); for(GdlRule rule : rules) { Assignments assignments = Assignments.getAssignmentsForRule(rule, model, constantForms, completedSentenceFormValues); //Calculate vars in live (non-constant, non-distinct) conjuncts Set<GdlVariable> varsInLiveConjuncts = getVarsInLiveConjuncts(rule, constantChecker.getSentenceForms()); varsInLiveConjuncts.addAll(SentenceModel.getVariables(rule.getHead())); Set<GdlVariable> varsInRule = new HashSet<GdlVariable>(SentenceModel.getVariables(rule)); boolean preventDuplicatesFromConstants = (varsInRule.size() > varsInLiveConjuncts.size()); //System.out.println("Rule: " + rule); //Do we just pass those to the Assignments class in that case? for(AssignmentIterator asnItr = assignments.getIterator(); asnItr.hasNext(); ) { Map<GdlVariable, GdlConstant> assignment = asnItr.next(); if(assignment == null) continue; //Not sure if this will ever happen GdlSentence sentence = CommonTransforms.replaceVariables(rule.getHead(), assignment); //Now we go through the conjuncts as before, but we wait to hook them up. List<Component> componentsToConnect = new ArrayList<Component>(rule.arity()); for(GdlLiteral literal : rule.getBody()) { if(literal instanceof GdlSentence) { //Get the sentence post-substitutions GdlSentence transformed = CommonTransforms.replaceVariables((GdlSentence) literal, assignment); //Check for constant-ness SentenceForm conjunctForm = model.getSentenceForm(transformed); if(constantChecker.isConstantForm(conjunctForm)) { if(!constantChecker.isTrueConstant(transformed)) { List<GdlVariable> varsToChange = getVarsInConjunct(literal); asnItr.changeOneInNext(varsToChange, assignment); componentsToConnect.add(null); } continue; } Component conj = components.get(transformed); //If conj is null and this is a sentence form we're still handling, //hook up to a temporary sentence form if(conj == null) { conj = temporaryComponents.get(transformed); } if(conj == null && SentenceModel.inSentenceFormGroup(transformed, recursionForms)) { //Set up a temporary component Proposition tempProp = new Proposition(transformed.toTerm()); temporaryComponents.put(transformed, tempProp); conj = tempProp; } //Let's say this is false; we want to backtrack and change the right variable if(conj == null || isThisConstant(conj, falseComponent)) { List<GdlVariable> varsInConjunct = getVarsInConjunct(literal); asnItr.changeOneInNext(varsInConjunct, assignment); //These last steps just speed up the process //telling the factory to ignore this rule componentsToConnect.add(null); continue; //look at all the other restrictions we'll face } componentsToConnect.add(conj); } else if(literal instanceof GdlNot) { //Add a "not" if necessary //Look up the negation GdlSentence internal = (GdlSentence) ((GdlNot) literal).getBody(); GdlSentence transformed = CommonTransforms.replaceVariables(internal, assignment); //Add constant-checking here... SentenceForm conjunctForm = model.getSentenceForm(transformed); if(constantChecker.isConstantForm(conjunctForm)) { if(constantChecker.isTrueConstant(transformed)) { List<GdlVariable> varsToChange = getVarsInConjunct(literal); asnItr.changeOneInNext(varsToChange, assignment); componentsToConnect.add(null); } continue; } Component conj = negations.get(transformed); if(isThisConstant(conj, falseComponent)) { //We need to change one of the variables inside List<GdlVariable> varsInConjunct = getVarsInConjunct(internal); asnItr.changeOneInNext(varsInConjunct, assignment); //ignore this rule componentsToConnect.add(null); continue; } if(conj == null) { conj = temporaryNegations.get(transformed); } //Check for the recursive case: if(conj == null && SentenceModel.inSentenceFormGroup(transformed, recursionForms)) { Component positive = components.get(transformed); if(positive == null) { positive = temporaryComponents.get(transformed); } if(positive == null) { //Make the temporary proposition Proposition tempProp = new Proposition(transformed.toTerm()); temporaryComponents.put(transformed, tempProp); positive = tempProp; } //Positive is now set and in temporaryComponents //Evidently, wasn't in temporaryNegations //So we add the "not" gate and set it in temporaryNegations Not not = new Not(); //Add positive as input not.addInput(positive); positive.addOutput(not); temporaryNegations.put(transformed, not); conj = not; } if(conj == null) { Component positive = components.get(transformed); //No, because then that will be attached to "negations", which could be bad if(positive == null) { //So the positive can't possibly be true (unless we have recurstion) //and so this would be positive always //We want to just skip this conjunct, so we continue to the next continue; //to the next conjunct } //Check if we're sharing a component with another sentence with a negation //(i.e. look for "nots" in our outputs and use those instead) Not existingNotOutput = getNotOutput(positive); if(existingNotOutput != null) { componentsToConnect.add(existingNotOutput); negations.put(transformed, existingNotOutput); continue; //to the next conjunct } Not not = new Not(); not.addInput(positive); positive.addOutput(not); negations.put(transformed, not); conj = not; } if(conj == null) System.out.println("null case with negated sentence " + transformed); componentsToConnect.add(conj); } else if(literal instanceof GdlDistinct) { //Already handled; ignore } else { throw new RuntimeException("Unwanted GdlLiteral type"); } } if(!componentsToConnect.contains(null)) { //Connect all the components Proposition andComponent = new Proposition(TEMP); andify(componentsToConnect, andComponent, trueComponent); if(!isThisConstant(andComponent, falseComponent)) { if(!inputsToOr.containsKey(sentence)) inputsToOr.put(sentence, new HashSet<Component>()); inputsToOr.get(sentence).add(andComponent); //We'll want to make sure at least one of the non-constant //components is changing if(preventDuplicatesFromConstants) { asnItr.changeOneInNext(varsInLiveConjuncts, assignment); } } } } } //At the end, we hook up the conjuncts for(Entry<GdlSentence, Set<Component>> entry : inputsToOr.entrySet()) { GdlSentence sentence = entry.getKey(); Set<Component> inputs = entry.getValue(); Set<Component> realInputs = new HashSet<Component>(); for(Component input : inputs) { if(input instanceof Constant || input.getInputs().size() == 0) { realInputs.add(input); } else { realInputs.add(input.getSingleInput()); input.getSingleInput().removeOutput(input); input.removeAllInputs(); } } Proposition prop = new Proposition(sentence.toTerm()); orify(realInputs, prop, falseComponent); components.put(sentence, prop); } //True/does sentences will have none of these rules, but //still need to exist/"float" //We'll do this if we haven't used base/input as a basis if(form.getName().equals(TRUE) || form.getName().equals(DOES)) { for(GdlSentence sentence : form) { Proposition prop = new Proposition(sentence.toTerm()); components.put(sentence, prop); } } } private static Set<GdlVariable> getVarsInLiveConjuncts( GdlRule rule, Set<SentenceForm> constantSentenceForms) { Set<GdlVariable> result = new HashSet<GdlVariable>(); for(GdlLiteral literal : rule.getBody()) { if(literal instanceof GdlRelation) { if(!SentenceModel.inSentenceFormGroup((GdlRelation)literal, constantSentenceForms)) result.addAll(SentenceModel.getVariables(literal)); } else if(literal instanceof GdlNot) { GdlNot not = (GdlNot) literal; GdlSentence inner = (GdlSentence) not.getBody(); if(!SentenceModel.inSentenceFormGroup(inner, constantSentenceForms)) result.addAll(SentenceModel.getVariables(literal)); } } return result; } private static boolean isThisConstant(Component conj, Constant constantComponent) { if(conj == constantComponent) return true; return (conj instanceof Proposition && conj.getInputs().size() == 1 && conj.getSingleInput() == constantComponent); } private static Not getNotOutput(Component positive) { for(Component c : positive.getOutputs()) { if(c instanceof Not) { return (Not) c; } } return null; } private static List<GdlVariable> getVarsInConjunct(GdlLiteral literal) { return SentenceModel.getVariables(literal); } private static void andify(List<Component> inputs, Component output, Constant trueProp) { //Special case: If the inputs include false, connect false to thisComponent for(Component c : inputs) { if(c instanceof Constant && !c.getValue()) { //Connect false (c) to the output output.addInput(c); c.addOutput(output); return; } } //For reals... just skip over any true constants And and = new And(); for(Component in : inputs) { if(!(in instanceof Constant)) { in.addOutput(and); and.addInput(in); } } //What if they're all true? (Or inputs is empty?) Then no inputs at this point... if(and.getInputs().isEmpty()) { //Hook up to "true" trueProp.addOutput(output); output.addInput(trueProp); return; } //If there's just one, on the other hand, don't use the and gate if(and.getInputs().size() == 1) { Component in = and.getSingleInput(); in.removeOutput(and); and.removeInput(in); in.addOutput(output); output.addInput(in); return; } and.addOutput(output); output.addInput(and); } }