package util.propnet.factory.flattener; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import util.game.GameRepository; import util.gdl.grammar.Gdl; import util.gdl.grammar.GdlConstant; import util.gdl.grammar.GdlDistinct; import util.gdl.grammar.GdlFunction; import util.gdl.grammar.GdlLiteral; import util.gdl.grammar.GdlNot; import util.gdl.grammar.GdlOr; import util.gdl.grammar.GdlPool; 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.logging.GamerLogger; /** * PropNetFlattener is an implementation of a GDL flattener using fixed-point * analysis of the rules. This flattener works on many small and medium-sized * games, but can fail on very large games. * * To use this class: * PropNetFlattener PF = new PropNetFlattener(description); * List<GdlRule> flatDescription = PF.flatten(); * return converter.convert(flatDescription); * * @author Ethan Dreyfuss * @author Sam Schreiber (comments) */ public class PropNetFlattener { private List<Gdl> description; private class Assignment extends ArrayList<GdlConstant> { private static final long serialVersionUID = 1L; } private class Assignments extends HashSet<Assignment> { private static final long serialVersionUID = 1L; } private class Index extends HashMap<GdlConstant, Assignments> { private static final long serialVersionUID = 1L; } private class Condition { public Condition(GdlTerm template) { this.template = getConstantAndVariableList(template); key = findGenericForm(template); updateDom(); } public void updateDom() { if(!domains.containsKey(key)) dom = null; else dom = domains.get(key); } public List<GdlTerm> template; public Domain dom; GdlTerm key; @Override public String toString() { return template.toString(); } } private class RuleReference { public List<GdlTerm> productionTemplate; //The template from the rule head, contains only variables and constants public List<Condition> conditions = new ArrayList<Condition>(); //the conditions (right hand side of the rule) public Gdl originalRule; public RuleReference(GdlRule originalRule) { this.originalRule = originalRule; } @Override public String toString() { return "\n\tProduction: "+(productionTemplate!=null ? productionTemplate.toString() : "null")+" conditions: "+(conditions!=null ? conditions.toString() : "null"); } @Override public boolean equals(Object other) { if(!(other instanceof RuleReference)) return false; RuleReference rhs = (RuleReference)other; return rhs.productionTemplate==this.productionTemplate && rhs.conditions.equals(this.conditions); } @Override public int hashCode() { return productionTemplate.hashCode()+conditions.hashCode(); } } private class Domain { public Domain(GdlTerm name, GdlTerm name2) {this.name = name; this.name2 = name2;} public Assignments assignments = new Assignments(); public List<Index> indices = new ArrayList<Index>(); public Set<RuleReference> ruleRefs = new HashSet<RuleReference>(); @SuppressWarnings("unused") public GdlTerm name, name2; @Override public String toString() { return "\nName: "+name+"\nvalues: "+assignments;//+"\nruleRefs: "+ruleRefs; } public void buildIndices() { for(Assignment assignment : assignments) { addAssignmentToIndex(assignment); } for(RuleReference ruleRef : ruleRefs) { List<Condition> newConditions = new ArrayList<Condition>(); for(Condition c : ruleRef.conditions) { if(c.dom == null) c.updateDom(); if(c.dom != null) newConditions.add(c); } if(newConditions.size() != ruleRef.conditions.size()) //Remove reference to constant terms ruleRef.conditions = newConditions; } } public void addAssignmentToIndex(Assignment assignment) { for(int i=0; i<assignment.size(); i++) { GdlConstant c = assignment.get(i); if(indices.size() <= i) indices.add(new Index()); Index index = indices.get(i); if(!index.containsKey(c)) index.put(c, new Assignments()); Assignments val = index.get(c); val.add(assignment); } } } private GdlVariable fillerVar = GdlPool.getVariable("?#*#"); HashMap<GdlTerm,Domain> domains = new HashMap<GdlTerm, Domain>(); private List<RuleReference> extraRefs = new ArrayList<RuleReference>(); public PropNetFlattener(List<Gdl> description) { this.description = description; } public List<GdlRule> flatten() { //Find universe and initial domains for(Gdl gdl : description) { initializeDomains(gdl); } for(Domain d : domains.values()) d.buildIndices(); //Compute the actual domains of everything updateDomains(); //printDomains(); //printDomainRefs(); return getAllInstantiations(); } private List<GdlRule> getAllInstantiations() { List<GdlRule> rval = new ArrayList<GdlRule>(); for(Gdl gdl : description) { if(gdl instanceof GdlRelation) { GdlRelation relation = (GdlRelation) gdl; String name = relation.getName().toString(); if(name.equalsIgnoreCase("base")) continue; rval.add(GdlPool.getRule(relation)); } } for(Domain d : domains.values()) { for(RuleReference r : d.ruleRefs) { Set<Map<GdlVariable,GdlConstant>> varInstantiations = findSatisfyingInstantiations(r); for(Map<GdlVariable,GdlConstant> varInstantiation : varInstantiations){ if(varInstantiation.containsValue(null)) throw new RuntimeException("Shouldn't instantiate anything to null."); rval.add(getInstantiation(r.originalRule, varInstantiation)); if(rval.get(rval.size()-1).toString().contains("null")) throw new RuntimeException("Shouldn't instantiate anything to null: "+rval.get(rval.size()-1).toString()); } } } for(RuleReference ruleRef : extraRefs) { List<Condition> newConditions = new ArrayList<Condition>(); for(Condition c : ruleRef.conditions) { if(c.dom == null) c.updateDom(); if(c.dom != null) newConditions.add(c); } if(newConditions.size() != ruleRef.conditions.size()) //Remove reference to constant terms ruleRef.conditions = newConditions; } for(RuleReference r : extraRefs) { Set<Map<GdlVariable,GdlConstant>> varInstantiations = findSatisfyingInstantiations(r); for(Map<GdlVariable,GdlConstant> varInstantiation : varInstantiations){ if(varInstantiation.containsValue(null)) throw new RuntimeException("Shouldn't instantiate anything to null."); rval.add(getInstantiation(r.originalRule, varInstantiation)); if(rval.get(rval.size()-1).toString().contains("null")) throw new RuntimeException("Shouldn't instantiate anything to null."); } if(varInstantiations.size() == 0) rval.add(getInstantiation(r.originalRule, new HashMap<GdlVariable,GdlConstant>())); } return rval; } private GdlRule getInstantiation(Gdl gdl, Map<GdlVariable, GdlConstant> varInstantiation) { Gdl instant = getInstantiationAux(gdl, varInstantiation); return (GdlRule)instant; } private Gdl getInstantiationAux(Gdl gdl, Map<GdlVariable, GdlConstant> varInstantiation) { if(gdl instanceof GdlRelation) { GdlRelation relation = (GdlRelation) gdl; List<GdlTerm> body = new ArrayList<GdlTerm>(); for(int i=0; i<relation.arity(); i++) { body.add((GdlTerm)getInstantiationAux(relation.get(i), varInstantiation)); } return GdlPool.getRelation(relation.getName(), body); } else if(gdl instanceof GdlRule) { GdlRule rule = (GdlRule)gdl; GdlSentence head = (GdlSentence)getInstantiationAux(rule.getHead(), varInstantiation); List<GdlLiteral> body = new ArrayList<GdlLiteral>(); for(int i=0; i<rule.arity(); i++) { body.add((GdlLiteral)getInstantiationAux(rule.get(i), varInstantiation)); } return GdlPool.getRule(head, body); } else if(gdl instanceof GdlDistinct) { GdlDistinct distinct = (GdlDistinct)gdl; GdlTerm arg1 = (GdlTerm)getInstantiationAux(distinct.getArg1(), varInstantiation); GdlTerm arg2 = (GdlTerm)getInstantiationAux(distinct.getArg2(), varInstantiation); return GdlPool.getDistinct(arg1, arg2); } else if(gdl instanceof GdlNot) { GdlNot not = (GdlNot)gdl; GdlLiteral body = (GdlLiteral)getInstantiationAux(not.getBody(), varInstantiation); return GdlPool.getNot(body); } else if(gdl instanceof GdlOr) { GdlOr or = (GdlOr)gdl; List<GdlLiteral> body = new ArrayList<GdlLiteral>(); for(int i=0; i<or.arity(); i++) { body.add((GdlLiteral)getInstantiationAux(or.get(i), varInstantiation)); } return GdlPool.getOr(body); } else if(gdl instanceof GdlProposition) { return gdl; } else if(gdl instanceof GdlConstant) { return gdl; } else if(gdl instanceof GdlFunction) { GdlFunction func = (GdlFunction)gdl; List<GdlTerm> body = new ArrayList<GdlTerm>(); for(int i=0; i<func.arity(); i++) { body.add((GdlTerm)getInstantiationAux(func.get(i), varInstantiation)); } return GdlPool.getFunction(func.getName(), body); } else if(gdl instanceof GdlVariable) { GdlVariable variable = (GdlVariable)gdl; return varInstantiation.get(variable); } else throw new RuntimeException("Someone went and extended the GDL hierarchy without updating this code."); } void initializeDomains(Gdl gdl) { if(gdl instanceof GdlRelation) { GdlRelation relation = (GdlRelation) gdl; String name = relation.getName().toString(); if(!name.equalsIgnoreCase("base")) { GdlTerm term = relation.toTerm(); GdlTerm generified = findGenericForm(term); Assignment instantiation = getConstantList(term); if(!domains.containsKey(generified)) domains.put(generified, new Domain(generified, term)); Domain dom = domains.get(generified); dom.assignments.add(instantiation); } } else if(gdl instanceof GdlRule) { GdlRule rule = (GdlRule)gdl; GdlSentence head = rule.getHead(); if(head instanceof GdlRelation) { GdlRelation rel = (GdlRelation)head; GdlTerm term = rel.toTerm(); GdlTerm generified = findGenericForm(term); if(!domains.containsKey(generified)) domains.put(generified, new Domain(generified, term)); Domain dom = domains.get(generified); List<GdlTerm> productionTemplate = getConstantAndVariableList(term); List<List<GdlLiteral>> newRHSs = deOr(rule.getBody()); for(List<GdlLiteral> RHS : newRHSs) { RuleReference ruleRef = new RuleReference(GdlPool.getRule(head, RHS)); ruleRef.productionTemplate = productionTemplate; for(GdlLiteral lit : RHS) { if(lit instanceof GdlSentence) { GdlTerm t = ((GdlSentence)lit).toTerm(); Condition cond = new Condition(t); ruleRef.conditions.add(cond); } } dom.ruleRefs.add(ruleRef); } } else { List<List<GdlLiteral>> newRHSs = deOr(rule.getBody()); for(List<GdlLiteral> RHS : newRHSs) { RuleReference ruleRef = new RuleReference(GdlPool.getRule(head, RHS)); for(GdlLiteral lit : RHS) { if(lit instanceof GdlSentence) { GdlTerm t = ((GdlSentence)lit).toTerm(); Condition cond = new Condition(t); ruleRef.conditions.add(cond); } } extraRefs.add(ruleRef); } } } } private Assignment getConstantList(GdlTerm term) { Assignment rval = new Assignment(); if(term instanceof GdlConstant) { rval.add((GdlConstant)term); return rval; } else if(term instanceof GdlVariable) throw new RuntimeException("Called getConstantList on something containing a variable."); GdlFunction func = (GdlFunction)term; for(GdlTerm t : func.getBody()) rval.addAll(getConstantList(t)); return rval; } private List<GdlTerm> getConstantAndVariableList(GdlTerm term) { List<GdlTerm> rval = new ArrayList<GdlTerm>(); if(term instanceof GdlConstant) { rval.add(term); return rval; } else if(term instanceof GdlVariable) { rval.add(term); return rval; } GdlFunction func = (GdlFunction)term; for(GdlTerm t : func.getBody()) rval.addAll(getConstantAndVariableList(t)); return rval; } GdlConstant legalConst = GdlPool.getConstant("legal"); GdlConstant trueConst = GdlPool.getConstant("true"); GdlConstant doesConst = GdlPool.getConstant("does"); GdlConstant nextConst = GdlPool.getConstant("next"); GdlConstant initConst = GdlPool.getConstant("init"); private GdlTerm findGenericForm(GdlTerm term) { if(term instanceof GdlConstant) return fillerVar; else if(term instanceof GdlVariable) return fillerVar; GdlFunction func = (GdlFunction)term; List<GdlTerm> newBody = new ArrayList<GdlTerm>(); for(GdlTerm t : func.getBody()) newBody.add(findGenericForm(t)); GdlConstant name = func.getName(); if(name==legalConst) name=doesConst; else if(name==nextConst) name=trueConst; else if(name==initConst) name=trueConst; return GdlPool.getFunction(name, newBody); } private List<List<GdlLiteral>> deOr(List<GdlLiteral> rhs) { List<List<GdlLiteral>> wrapped = new ArrayList<List<GdlLiteral>>(); wrapped.add(rhs); return deOr2(wrapped); } private List<List<GdlLiteral>> deOr2(List<List<GdlLiteral>> rhsList) { List<List<GdlLiteral>> rval = new ArrayList<List<GdlLiteral>>(); boolean expandedSomething = false; for(List<GdlLiteral> rhs : rhsList) { int i=0; if(!expandedSomething) { for(GdlLiteral lit : rhs) { if(!expandedSomething) { List<Gdl> expandedList = expandFirstOr(lit); if(expandedList.size() > 1) { for(Gdl replacement : expandedList) { List<GdlLiteral> newRhs = new ArrayList<GdlLiteral>(rhs); if(!(replacement instanceof GdlLiteral)) throw new RuntimeException("Top level return value is different type of gdl."); GdlLiteral newLit = (GdlLiteral)replacement; newRhs.set(i, newLit); rval.add(newRhs); } expandedSomething = true; break; } } i++; } if(!expandedSomething) //If I didn't find anything to expand rval.add(rhs); } else rval.add(rhs); //If I've already expanded this function call } if(!expandedSomething) return rhsList; else return deOr2(rval); } private List<Gdl> expandFirstOr(Gdl gdl) { List<Gdl> rval; List<Gdl> expandedChild; if(gdl instanceof GdlDistinct) { //Can safely be ignored, won't contain 'or' rval = new ArrayList<Gdl>(); rval.add(gdl); return rval; } else if(gdl instanceof GdlNot) { GdlNot not = (GdlNot)gdl; expandedChild = expandFirstOr(not.getBody()); rval = new ArrayList<Gdl>(); for(Gdl g : expandedChild) { if(!(g instanceof GdlLiteral)) throw new RuntimeException("Not must have literal child."); GdlLiteral lit = (GdlLiteral)g; rval.add(GdlPool.getNot(lit)); } return rval; } else if(gdl instanceof GdlOr) { GdlOr or = (GdlOr)gdl; rval = new ArrayList<Gdl>(); for(int i=0; i<or.arity(); i++) { rval.add(or.get(i)); } return rval; } else if(gdl instanceof GdlProposition) { //Can safely be ignored, won't contain 'or' rval = new ArrayList<Gdl>(); rval.add(gdl); return rval; } else if(gdl instanceof GdlRelation) { //Can safely be ignored, won't contain 'or' rval = new ArrayList<Gdl>(); rval.add(gdl); return rval; } else if(gdl instanceof GdlRule) { throw new RuntimeException("This should be used to remove 'or's from the body of a rule, and rules can't be nested"); } else if(gdl instanceof GdlConstant) { //Can safely be ignored, won't contain 'or' rval = new ArrayList<Gdl>(); rval.add(gdl); return rval; } else if(gdl instanceof GdlFunction) { //Can safely be ignored, won't contain 'or' rval = new ArrayList<Gdl>(); rval.add(gdl); return rval; } else if(gdl instanceof GdlVariable) { //Can safely be ignored, won't contain 'or' rval = new ArrayList<Gdl>(); rval.add(gdl); return rval; } else { throw new RuntimeException("Uh oh, gdl hierarchy must have been extended without updating this code."); } } void updateDomains() { boolean changedSomething = true; int itrNum = 0; Set<Domain> lastUpdatedDomains = new HashSet<Domain>(domains.values()); while(changedSomething) { GamerLogger.log("StateMachine", "Beginning domain finding iteration: "+itrNum); Set<Domain> currUpdatedDomains = new HashSet<Domain>(); changedSomething = false; int rulesConsidered = 0; for(Domain d : domains.values()) { for(RuleReference ruleRef : d.ruleRefs) { boolean containsUpdatedDomain = false; for(Condition c : ruleRef.conditions) if(lastUpdatedDomains.contains(c.dom)) { containsUpdatedDomain = true; break; } if(!containsUpdatedDomain) continue; rulesConsidered++; Set<Map<GdlVariable,GdlConstant>> instantiations = findSatisfyingInstantiations(ruleRef); for(Map<GdlVariable,GdlConstant> instantiation : instantiations) { Assignment a = new Assignment(); for(GdlTerm t : ruleRef.productionTemplate) { if(t instanceof GdlConstant) a.add((GdlConstant)t); else { GdlVariable var = (GdlVariable)t; a.add(instantiation.get(var)); } } if(!d.assignments.contains(a)) { currUpdatedDomains.add(d); d.assignments.add(a); changedSomething = true; d.addAssignmentToIndex(a); } } if(instantiations.size() == 0) { //There might just be no variables in the rule Assignment a = new Assignment(); findSatisfyingInstantiations(ruleRef); //just for debugging boolean isVar = false; for(GdlTerm t : ruleRef.productionTemplate) { if(t instanceof GdlConstant) a.add((GdlConstant)t); else { //There's a variable and we didn't find an instantiation isVar = true; break; } } if(!isVar && !d.assignments.contains(a)) { currUpdatedDomains.add(d); d.assignments.add(a); changedSomething = true; d.addAssignmentToIndex(a); } } } } itrNum++; lastUpdatedDomains = currUpdatedDomains; GamerLogger.log("StateMachine", "\tDone with iteration. Considered "+rulesConsidered+" rules."); } } private Set<Map<GdlVariable, GdlConstant>> findSatisfyingInstantiations(RuleReference ruleRef) { Map<GdlVariable, GdlConstant> emptyInstantiation = new HashMap<GdlVariable, GdlConstant>(); return findSatisfyingInstantiations(ruleRef.conditions, 0, emptyInstantiation); } //Coolest method in this whole thing, does the real work of the JOIN stuff private Set<Map<GdlVariable, GdlConstant>> findSatisfyingInstantiations( List<Condition> conditions, int idx, Map<GdlVariable, GdlConstant> instantiation) { Set<Map<GdlVariable, GdlConstant>> rval = new HashSet<Map<GdlVariable,GdlConstant>>(); if(idx==conditions.size()) { rval.add(instantiation); return rval; } Condition cond = conditions.get(idx); Domain dom = cond.dom; Assignments assignments = null; for(int i=0; i<cond.template.size(); i++) { GdlTerm t = cond.template.get(i); GdlConstant c = null; if(t instanceof GdlVariable) { GdlVariable v = (GdlVariable)t; if(instantiation.containsKey(v)) c = instantiation.get(v); } else if(t instanceof GdlConstant) c = (GdlConstant)t; if(c != null) { if(assignments == null) { assignments = new Assignments(); if(dom.indices.size() > i) //if this doesn't hold it is because there are no assignments and the indices haven't been set up yet { Index index = dom.indices.get(i); if(index.containsKey(c)) //Might be no assignment which satisfies this condition assignments.addAll(index.get(c)); } } else { if(dom.indices.size()>i) { Index index = dom.indices.get(i); if(index.containsKey(c)) //Might be no assignment which satisfies this condition assignments.retainAll(index.get(c)); } else //This is when we've tried to find an assignment for a form that doesn't have any assignments yet. Pretend it returned an empty set assignments.clear(); } } } if(assignments == null) //case where there are no constants to be consistent with { assignments = dom.assignments; } for(Assignment a : assignments) { Map<GdlVariable, GdlConstant> newInstantiation = new HashMap<GdlVariable, GdlConstant>(instantiation); for(int i=0; i<a.size(); i++) { GdlTerm t = cond.template.get(i); if(t instanceof GdlVariable) { GdlVariable var = (GdlVariable)t; if(!instantiation.containsKey(var)) newInstantiation.put(var, a.get(i)); } } rval.addAll(findSatisfyingInstantiations(conditions, idx+1, newInstantiation)); } return rval; } /** * @param args */ public static void main(String[] args) { List<Gdl> description = GameRepository.getDefaultRepository().getGame("conn4").getRules(); PropNetFlattener flattener = new PropNetFlattener(description); List<GdlRule> flattened = flattener.flatten(); System.out.println("Flattened description for connect four contains: \n" + flattened.size() + "\n\n"); List<String> strings = new ArrayList<String>(); for(GdlRule rule : flattened) strings.add(rule.toString()); Collections.sort(strings); for(String s : strings) System.out.println(s); } }