/******************************************************************************* * Copyright (C) 2008-2012 Dominik Jain. * * This file is part of ProbCog. * * ProbCog is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ProbCog is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ProbCog. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ package probcog.srl.directed.bln; import java.util.Collection; import java.util.HashMap; import java.util.Set; import probcog.logic.Formula; import probcog.logic.GroundAtom; import probcog.logic.GroundLiteral; import probcog.logic.KnowledgeBase; import probcog.logic.PossibleWorld; import probcog.logic.WorldVariables; import probcog.logic.Formula.FormulaSimplification; import probcog.srl.Database; import probcog.srl.Signature; import probcog.srl.directed.bln.coupling.VariableLogicCoupling; import edu.ksu.cis.bnj.ver3.core.BeliefNode; import edu.ksu.cis.bnj.ver3.core.CPF; import edu.ksu.cis.bnj.ver3.core.CPT; import edu.ksu.cis.bnj.ver3.core.Discrete; import edu.ksu.cis.bnj.ver3.core.Value; import edu.ksu.cis.bnj.ver3.core.values.ValueDouble; import edu.tum.cs.util.StringTool; import edu.tum.cs.util.datastruct.OrderedSet; /** * Represents a grounded Bayesian logic network (i.e. a mixed network) * @author Dominik Jain */ public class GroundBLN extends AbstractGroundBLN { protected VariableLogicCoupling coupling; /** * possible world (used only temporarily during instantiation) */ protected PossibleWorld state; /** * grounded knowledge base of hard constraints */ protected KnowledgeBase gkb; /** * whether to simplify any ground formulas as far as possible * TODO: maybe simplification should depend on the algorithm that is used */ protected boolean useFormulaSimplification = false; public GroundBLN(AbstractBayesianLogicNetwork bln, Database db) throws Exception { super(bln, db); } public GroundBLN(AbstractBayesianLogicNetwork bln, String databaseFile) throws Exception { super(bln, databaseFile); } @Override protected void init(AbstractBayesianLogicNetwork bln, Database db) throws Exception { super.init(bln, db); coupling = new VariableLogicCoupling(); this.paramHandler.add("simplifyFormulas", "setFormulaSimplification"); } public void setFormulaSimplification(boolean enabled) { useFormulaSimplification = enabled; } @Override protected void onAddGroundAtomNode(BeliefNode var, String[] params, Signature sig) { if(sig.isBoolean()) { coupling.addBooleanVariable(var, sig.functionName, params); } else { // node is non-Boolean, so add one block containing the ground atoms for each possible value coupling.addBlockVariable(var, (Discrete)var.getDomain(), sig.functionName, params); } } @Override protected void onAddAuxiliaryNode(BeliefNode var, boolean isBoolean, String functionName, String[] params) { if(isBoolean) { coupling.addBooleanVariable(var, functionName, params); } else { // node is non-Boolean, so add one block containing the ground atoms for each possible value coupling.addBlockVariable(var, (Discrete)var.getDomain(), functionName, params); } } @Override protected void onAddEvidenceVariable(String functionName, String[] params) { coupling.addEvidenceVariable(functionName, params); } public GroundLiteral getGroundLiteral(BeliefNode var, int domIdx) { return coupling.getGroundLiteral(var, domIdx); } @Override protected void groundFormulaicNodes() throws Exception { WorldVariables worldVars = coupling.getWorldVars(); state = new PossibleWorld(worldVars); BayesianLogicNetwork bln = (BayesianLogicNetwork)this.bln; gkb = bln.kb.ground(this.db, worldVars, useFormulaSimplification ? FormulaSimplification.OnDisallowFalse : FormulaSimplification.None); if(verbose) System.out.printf(" %d formulas resulted in %s ground formulas\n", bln.kb.size(), gkb.size()); HashMap<String, Value[]> cpfCache = new HashMap<String, Value[]>(); int i = 0; for(Formula gf : gkb) { // get the template from which the ground formula was instantiated (after simplification, we can't retrieve it) Integer templateID = gkb.getTemplateID(gf); assert templateID != null : "Ground formula " + gf + " has no template ID"; // if formulas weren't fully simplified, still apply basic simplification (i.e. without using the database) // note: there may still be TrueFalse instances due to equalities, e.g. !(x=y)) if(!useFormulaSimplification) gf = gf.simplify(null); // add node and connections String nodeName = "GF" + i; if(verbose) System.out.printf(" %s: %s\n", nodeName, gf.toString()); // NOTE: we use an ordered set to guarantee that the ordering of nodes is the same // across all instances of a formula template; such that (if formulas are not // simplified using the evidence) we could use the same CPF for all of the // instances of a formula Set<GroundAtom> gas = new OrderedSet<GroundAtom>(); gf.getGroundAtoms(gas); //System.out.printf(" referenced ground atoms in GF%d: %s\n", i, StringTool.join(", ", gas)); OrderedSet<BeliefNode> parents = new OrderedSet<BeliefNode>(); // use ordered set here, too, because several ground atoms may map to the same variable (e.g. foo(a,b), foo(a,c) -> foo(a)) for(GroundAtom ga : gas) { if(ga == null) throw new Exception("null ground atom encountered"); String strGA = ga.toString(); BeliefNode parent = groundBN.getNode(strGA); if(parent == null) { // if the atom cannot be found, e.g. attr(X,Value), it might be a functional, so remove the last argument and try again, e.g. attr(X) (=Value) String parentName = strGA.substring(0, strGA.lastIndexOf(",")) + ")"; parent = groundBN.getNode(parentName); if(parent == null) throw new Exception("Could not find node for ground atom " + strGA + ". If this is an evidence variable, this problem can be avoided by enabling formula simplification (e.g. by passing --simplifyFormulas=true when using BLNinfer)"); } parents.add(parent); } BeliefNode node = addHardFormulaNode(nodeName, parents); // this establishes connections and initialises the CPF // set CPF id (i.e. equivalence class id) // TODO try string transform: Two formulas are equivalent if they are the same except for the universally quantified variables String cpfid; if(useFormulaSimplification) { // treat all formulas differently cpfid = "F" + i; } else { // treat all instances of a formula template the same cpfid = "F" + templateID; } this.cpfIDs.put(node, cpfid); // set CPF Value[] values = cpfCache.get(cpfid); if(values != null) { // Note: A value from the cache can only be used if formula simplification is not applied, // because the CPF ids are never equal otherwise. // TODO Is this really 100% safe? What about the case where constants appear in the formula, e.g. in "(x=Const)"? The above-mentioned string transform might be safer. ((CPT)node.getCPF()).setValues(values); } else { fillFormulaCPF(gf, node.getCPF()); } ++i; } // clean up state = null; } /** * adds a node corresponding to a hard constraint to the network - along with the necessary edges * @param nodeName name of the node to add for the constraint * @param parentGAs collection of names of parent nodes/ground atoms * @return the node that was added * @throws Exception */ public BeliefNode addHardFormulaNode(String nodeName, Collection<BeliefNode> parents) throws Exception { BeliefNode[] domprod = new BeliefNode[1+parents.size()]; BeliefNode node = groundBN.addNode(nodeName); domprod[0] = node; hardFormulaNodes.add(node); int i = 1; for(BeliefNode parent : parents) { domprod[i++] = parent; groundBN.connect(parent, node, false); } ((CPT)node.getCPF()).buildZero(domprod, false); // ensure correct ordering in CPF return node; } /** * fills the CPF of a formulaic node * @param gf the ground formula to evaluate for all possible settings * @param cpf the CPF of the formulaic node to fill * @param parents the parents of the formulaic node * @param parentGAs the ground atom string names of the parents (in case the node names do not match them) * @throws Exception */ protected void fillFormulaCPF(Formula gf, CPF cpf) throws Exception { BeliefNode[] nodes = cpf.getDomainProduct(); int[] addr = new int[nodes.length]; fillFormulaCPF(gf, cpf, 1, addr); } protected void fillFormulaCPF(Formula gf, CPF cpf, int iDomProd, int[] addr) throws Exception { BeliefNode[] domprod = cpf.getDomainProduct(); // if all parents have been set, determine the truth value of the formula and // fill the corresponding column of the CPT if(iDomProd == domprod.length) { // get truth value of formula double value = gf.isTrue(state) ? 1 : 0; /* for(String ga : parentGAs) System.out.print(ga + " = " + state.get(ga) + ", "); System.out.println(" -> " + value); */ // write to CPF // - true addr[0] = 0; cpf.put(addr, new ValueDouble(value)); // - false addr[0] = 1; cpf.put(addr, new ValueDouble(1.0-value)); return; } // otherwise get the next parent and consider all of its settings BeliefNode parent = domprod[iDomProd]; int domOrder = parent.getDomain().getOrder(); // - recursively consider all settings for(int i = 0; i < domOrder; i++) { // set address addr[iDomProd] = i; // set state for logical reasoner coupling.setVariableValue(parent, i, state); // recurse fillFormulaCPF(gf, cpf, iDomProd+1, addr); } } /** * gets the knowledge base of grounded hard logical constraints * @return */ public KnowledgeBase getKB() { return gkb; } public WorldVariables getWorldVars() { return coupling.getWorldVars(); } /** * gets the variable name in the ground network that corresponds to the given logical ground atom * @param gndAtom * @return */ public String getVariableName(GroundAtom gndAtom) { if(bln.rbn.isBoolean(gndAtom.predicate)) return gndAtom.toString(); else return gndAtom.predicate + "(" + StringTool.join(",", gndAtom.args, 0, gndAtom.args.length-1) + ")"; } /** * returns the belief node in the ground network that corresponds to the given ground atom * @param gndAtom * @return the belief node corresponding to gndAtom or null if no correspondence is found */ public BeliefNode getVariable(GroundAtom gndAtom) { return coupling.getVariable(gndAtom); } public int getVariableValue(BeliefNode var, PossibleWorld w) { return coupling.getVariableValue(var, w); } /** * * @param var * @return true if the variable is not an auxiliary variable that was created for a logical constraint but corresponds directly to a variable upon which the possible worlds are defined */ public boolean isRegularVariable(BeliefNode var) { return coupling.hasCoupling(var); } /** * gets the set of regular variables (i.e. non-auxiliary belief nodes, which do not correspond to logical constraints) * @return */ public Set<BeliefNode> getRegularVariables() { return coupling.getCoupledVariables(); } public VariableLogicCoupling getCoupling() { return coupling; } }