package org.ggp.base.util.propnet.architecture; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStreamWriter; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.ggp.base.util.gdl.grammar.GdlConstant; import org.ggp.base.util.gdl.grammar.GdlPool; import org.ggp.base.util.gdl.grammar.GdlProposition; import org.ggp.base.util.gdl.grammar.GdlRelation; import org.ggp.base.util.gdl.grammar.GdlSentence; import org.ggp.base.util.gdl.grammar.GdlTerm; import org.ggp.base.util.logging.GamerLogger; import org.ggp.base.util.propnet.architecture.components.And; import org.ggp.base.util.propnet.architecture.components.Not; import org.ggp.base.util.propnet.architecture.components.Or; import org.ggp.base.util.propnet.architecture.components.Proposition; import org.ggp.base.util.propnet.architecture.components.Transition; import org.ggp.base.util.statemachine.Role; /** * The PropNet class is designed to represent Propositional Networks. * * A propositional network (also known as a "propnet") is a way of representing * a game as a logic circuit. States of the game are represented by assignments * of TRUE or FALSE to "base" propositions, each of which represents a single * fact that can be true about the state of the game. For example, in a game of * Tic-Tac-Toe, the fact (cell 1 1 x) indicates that the cell (1,1) has an 'x' * in it. That fact would correspond to a base proposition, which would be set * to TRUE to indicate that the fact is true in the current state of the game. * Likewise, the base corresponding to the fact (cell 1 1 o) would be false, * because in that state of the game there isn't an 'o' in the cell (1,1). * * A state of the game is uniquely determined by the assignment of truth values * to the base propositions in the propositional network. Every assignment of * truth values to base propositions corresponds to exactly one unique state of * the game. * * Given the values of the base propositions, you can use the connections in * the network (AND gates, OR gates, NOT gates) to determine the truth values * of other propositions. For example, you can determine whether the terminal * proposition is true: if that proposition is true, the game is over when it * reaches this state. Otherwise, if it is false, the game isn't over. You can * also determine the value of the goal propositions, which represent facts * like (goal xplayer 100). If that proposition is true, then that fact is true * in this state of the game, which means that xplayer has 100 points. * * You can also use a propositional network to determine the next state of the * game, given the current state and the moves for each player. First, you set * the input propositions which correspond to each move to TRUE. Once that has * been done, you can determine the truth value of the transitions. Each base * proposition has a "transition" component going into it. This transition has * the truth value that its base will take on in the next state of the game. * * For further information about propositional networks, see: * * "Decomposition of Games for Efficient Reasoning" by Eric Schkufza. * "Factoring General Games using Propositional Automata" by Evan Cox et al. * * @author Sam Schreiber */ public final class PropNet { /** References to every component in the PropNet. */ private final Set<Component> components; /** References to every Proposition in the PropNet. */ private final Set<Proposition> propositions; /** References to every BaseProposition in the PropNet, indexed by name. */ private final Map<GdlSentence, Proposition> basePropositions; /** References to every InputProposition in the PropNet, indexed by name. */ private final Map<GdlSentence, Proposition> inputPropositions; /** References to every LegalProposition in the PropNet, indexed by role. */ private final Map<Role, Set<Proposition>> legalPropositions; /** References to every GoalProposition in the PropNet, indexed by role. */ private final Map<Role, Set<Proposition>> goalPropositions; /** A reference to the single, unique, InitProposition. */ private final Proposition initProposition; /** A reference to the single, unique, TerminalProposition. */ private final Proposition terminalProposition; /** A helper mapping between input/legal propositions. */ private final Map<Proposition, Proposition> legalInputMap; /** A helper list of all of the roles. */ private final List<Role> roles; public void addComponent(Component c) { components.add(c); if (c instanceof Proposition) propositions.add((Proposition)c); } /** * Creates a new PropNet from a list of Components, along with indices over * those components. * * @param components * A list of Components. */ public PropNet(List<Role> roles, Set<Component> components) { this.roles = roles; this.components = components; this.propositions = recordPropositions(); this.basePropositions = recordBasePropositions(); this.inputPropositions = recordInputPropositions(); this.legalPropositions = recordLegalPropositions(); this.goalPropositions = recordGoalPropositions(); this.initProposition = recordInitProposition(); this.terminalProposition = recordTerminalProposition(); this.legalInputMap = makeLegalInputMap(); } public List<Role> getRoles() { return roles; } public Map<Proposition, Proposition> getLegalInputMap() { return legalInputMap; } private Map<Proposition, Proposition> makeLegalInputMap() { Map<Proposition, Proposition> legalInputMap = new HashMap<Proposition, Proposition>(); // Create a mapping from Body->Input. Map<List<GdlTerm>, Proposition> inputPropsByBody = new HashMap<List<GdlTerm>, Proposition>(); for(Proposition inputProp : inputPropositions.values()) { List<GdlTerm> inputPropBody = (inputProp.getName()).getBody(); inputPropsByBody.put(inputPropBody, inputProp); } // Use that mapping to map Input->Legal and Legal->Input // based on having the same Body proposition. for(Set<Proposition> legalProps : legalPropositions.values()) { for(Proposition legalProp : legalProps) { List<GdlTerm> legalPropBody = (legalProp.getName()).getBody(); if (inputPropsByBody.containsKey(legalPropBody)) { Proposition inputProp = inputPropsByBody.get(legalPropBody); legalInputMap.put(inputProp, legalProp); legalInputMap.put(legalProp, inputProp); } } } return legalInputMap; } /** * Getter method. * * @return References to every BaseProposition in the PropNet, indexed by * name. */ public Map<GdlSentence, Proposition> getBasePropositions() { return basePropositions; } /** * Getter method. * * @return References to every Component in the PropNet. */ public Set<Component> getComponents() { return components; } /** * Getter method. * * @return References to every GoalProposition in the PropNet, indexed by * player name. */ public Map<Role, Set<Proposition>> getGoalPropositions() { return goalPropositions; } /** * Getter method. A reference to the single, unique, InitProposition. * * @return */ public Proposition getInitProposition() { return initProposition; } /** * Getter method. * * @return References to every InputProposition in the PropNet, indexed by * name. */ public Map<GdlSentence, Proposition> getInputPropositions() { return inputPropositions; } /** * Getter method. * * @return References to every LegalProposition in the PropNet, indexed by * player name. */ public Map<Role, Set<Proposition>> getLegalPropositions() { return legalPropositions; } /** * Getter method. * * @return References to every Proposition in the PropNet. */ public Set<Proposition> getPropositions() { return propositions; } /** * Getter method. * * @return A reference to the single, unique, TerminalProposition. */ public Proposition getTerminalProposition() { return terminalProposition; } /** * Returns a representation of the PropNet in .dot format. * * @see java.lang.Object#toString() */ @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("digraph propNet\n{\n"); for ( Component component : components ) { sb.append("\t" + component.toString() + "\n"); } sb.append("}"); return sb.toString(); } /** * Outputs the propnet in .dot format to a particular file. * This can be viewed with tools like Graphviz and ZGRViewer. * * @param filename the name of the file to output to */ public void renderToFile(String filename) { try { File f = new File(filename); FileOutputStream fos = new FileOutputStream(f); OutputStreamWriter fout = new OutputStreamWriter(fos, "UTF-8"); fout.write(toString()); fout.close(); fos.close(); } catch(Exception e) { GamerLogger.logStackTrace("StateMachine", e); } } /** * Builds an index over the BasePropositions in the PropNet. * * This is done by going over every single-input proposition in the network, * and seeing whether or not its input is a transition, which would mean that * by definition the proposition is a base proposition. * * @return An index over the BasePropositions in the PropNet. */ private Map<GdlSentence, Proposition> recordBasePropositions() { Map<GdlSentence, Proposition> basePropositions = new HashMap<GdlSentence, Proposition>(); for (Proposition proposition : propositions) { // Skip all propositions without exactly one input. if (proposition.getInputs().size() != 1) continue; Component component = proposition.getSingleInput(); if (component instanceof Transition) { basePropositions.put(proposition.getName(), proposition); } } return basePropositions; } /** * Builds an index over the GoalPropositions in the PropNet. * * This is done by going over every function proposition in the network * where the name of the function is "goal", and extracting the name of the * role associated with that goal proposition, and then using those role * names as keys that map to the goal propositions in the index. * * @return An index over the GoalPropositions in the PropNet. */ private Map<Role, Set<Proposition>> recordGoalPropositions() { Map<Role, Set<Proposition>> goalPropositions = new HashMap<Role, Set<Proposition>>(); for (Proposition proposition : propositions) { // Skip all propositions that aren't GdlRelations. if (!(proposition.getName() instanceof GdlRelation)) continue; GdlRelation relation = (GdlRelation) proposition.getName(); if (!relation.getName().getValue().equals("goal")) continue; Role theRole = new Role((GdlConstant) relation.get(0)); if (!goalPropositions.containsKey(theRole)) { goalPropositions.put(theRole, new HashSet<Proposition>()); } goalPropositions.get(theRole).add(proposition); } return goalPropositions; } /** * Returns a reference to the single, unique, InitProposition. * * @return A reference to the single, unique, InitProposition. */ private Proposition recordInitProposition() { for (Proposition proposition : propositions) { // Skip all propositions that aren't GdlPropositions. if (!(proposition.getName() instanceof GdlProposition)) continue; GdlConstant constant = ((GdlProposition) proposition.getName()).getName(); if (constant.getValue().toUpperCase().equals("INIT")) { return proposition; } } return null; } /** * Builds an index over the InputPropositions in the PropNet. * * @return An index over the InputPropositions in the PropNet. */ private Map<GdlSentence, Proposition> recordInputPropositions() { Map<GdlSentence, Proposition> inputPropositions = new HashMap<GdlSentence, Proposition>(); for (Proposition proposition : propositions) { // Skip all propositions that aren't GdlFunctions. if (!(proposition.getName() instanceof GdlRelation)) continue; GdlRelation relation = (GdlRelation) proposition.getName(); if (relation.getName().getValue().equals("does")) { inputPropositions.put(proposition.getName(), proposition); } } return inputPropositions; } /** * Builds an index over the LegalPropositions in the PropNet. * * @return An index over the LegalPropositions in the PropNet. */ private Map<Role, Set<Proposition>> recordLegalPropositions() { Map<Role, Set<Proposition>> legalPropositions = new HashMap<Role, Set<Proposition>>(); for (Proposition proposition : propositions) { // Skip all propositions that aren't GdlRelations. if (!(proposition.getName() instanceof GdlRelation)) continue; GdlRelation relation = (GdlRelation) proposition.getName(); if (relation.getName().getValue().equals("legal")) { GdlConstant name = (GdlConstant) relation.get(0); Role r = new Role(name); if (!legalPropositions.containsKey(r)) { legalPropositions.put(r, new HashSet<Proposition>()); } legalPropositions.get(r).add(proposition); } } return legalPropositions; } /** * Builds an index over the Propositions in the PropNet. * * @return An index over Propositions in the PropNet. */ private Set<Proposition> recordPropositions() { Set<Proposition> propositions = new HashSet<Proposition>(); for (Component component : components) { if (component instanceof Proposition) { propositions.add((Proposition) component); } } return propositions; } /** * Records a reference to the single, unique, TerminalProposition. * * @return A reference to the single, unqiue, TerminalProposition. */ private Proposition recordTerminalProposition() { for ( Proposition proposition : propositions ) { if ( proposition.getName() instanceof GdlProposition ) { GdlConstant constant = ((GdlProposition) proposition.getName()).getName(); if ( constant.getValue().equals("terminal") ) { return proposition; } } } return null; } public int getSize() { return components.size(); } public int getNumAnds() { int andCount = 0; for(Component c : components) { if(c instanceof And) andCount++; } return andCount; } public int getNumOrs() { int orCount = 0; for(Component c : components) { if(c instanceof Or) orCount++; } return orCount; } public int getNumNots() { int notCount = 0; for(Component c : components) { if(c instanceof Not) notCount++; } return notCount; } public int getNumLinks() { int linkCount = 0; for(Component c : components) { linkCount += c.getOutputs().size(); } return linkCount; } /** * Removes a component from the propnet. Be very careful when using * this method, as it is not thread-safe. It is highly recommended * that this method only be used in an optimization period between * the propnet's creation and its initial use, during which it * should only be accessed by a single thread. * * The INIT and terminal components cannot be removed. */ public void removeComponent(Component c) { //Go through all the collections it could appear in if(c instanceof Proposition) { Proposition p = (Proposition) c; GdlSentence name = p.getName(); if(basePropositions.containsKey(name)) { basePropositions.remove(name); } else if(inputPropositions.containsKey(name)) { inputPropositions.remove(name); //The map goes both ways... Proposition partner = legalInputMap.get(p); if(partner != null) { legalInputMap.remove(partner); legalInputMap.remove(p); } } else if(name == GdlPool.getProposition(GdlPool.getConstant("INIT"))) { throw new RuntimeException("The INIT component cannot be removed. Consider leaving it and ignoring it."); } else if(name == GdlPool.getProposition(GdlPool.TERMINAL)) { throw new RuntimeException("The terminal component cannot be removed."); } else { for(Set<Proposition> propositions : legalPropositions.values()) { if(propositions.contains(p)) { propositions.remove(p); Proposition partner = legalInputMap.get(p); if(partner != null) { legalInputMap.remove(partner); legalInputMap.remove(p); } } } for(Set<Proposition> propositions : goalPropositions.values()) { propositions.remove(p); } } propositions.remove(p); } components.remove(c); //Remove all the local links to the component for(Component parent : c.getInputs()) parent.removeOutput(c); for(Component child : c.getOutputs()) child.removeInput(c); //These are actually unnecessary... //c.removeAllInputs(); //c.removeAllOutputs(); } }