/******************************************************************************* * Copyright (C) 2007-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; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.Map; import java.util.Vector; import probcog.srl.Database; import probcog.srl.GenericDatabase; import probcog.srl.RelationKey; import probcog.srl.Signature; import probcog.srl.taxonomy.Taxonomy; import edu.tum.cs.util.StringTool; /** * Generates groundings of all nodes relevant to a given node N for particular bindings * of N's parameters; the relevant nodes being N and its parents. * @author Dominik Jain */ public class ParentGrounder { protected class FunctionalLookup { protected RelationKey key; protected RelationalNode node; public FunctionalLookup(RelationKey key, RelationalNode node) { this.key = key; this.node = node; } /** * perform the functional lookup, extending the given variable binding with the result * @param db * @param varBindings the variable binding to extend with the results of the lookup * @throws Exception if a lookup that is required to work failed * @return true if the lookup could be performed to extend the variable binding, * false if the lookup is not applicable because the precondition is not met */ public boolean doLookup(GenericDatabase<?,?> db, HashMap<String,String> varBindings) throws Exception { // build the key values String[] keyValues = new String[key.keyIndices.size()]; int i = 0; for(Integer idxParam : key.keyIndices) keyValues[i++] = varBindings.get(node.params[idxParam]); // perform the lookup String[] params = db.getParameterSet(this.key, keyValues); if(params == null) { // lookup yielded no values // if the node is a precondition, we report that the lookup failed, indicating that // there is no valid instantiation of all variables if(this.node.isPrecondition) return false; // if the lookup failed but does not refer to a required precondition, // we have an error case String[] buf = new String[node.params.length]; for(int k = 0; k < node.params.length; k++) buf[k] = "_"; int j = 0; for(Integer k : key.keyIndices) buf[k] = keyValues[j++]; throw new Exception("Could not perform required lookup for " + Signature.formatVarName(node.getFunctionName(), buf)); } // update the variable bindings java.util.Iterator<Integer> iter = key.keyIndices.iterator(); int nextKey = iter.next(); for(i = 0; i < node.params.length; i++) { if(i == nextKey) { nextKey = iter.hasNext() ? iter.next() : -1; continue; } varBindings.put(node.params[i], params[i]); } return true; } } protected Vector<FunctionalLookup> functionalLookups; protected RelationalNode mainNode; protected Collection<RelationalNode> parents; /** * parameters that cannot be uniquely determined, i.e. parameters for which we consider all possible bindings */ protected String[] ungroundedParams; protected String[] ungroundedParamDomains; public ParentGrounder(RelationalBeliefNetwork bn, RelationalNode child) throws Exception { functionalLookups = new Vector<FunctionalLookup>(); mainNode = child; parents = bn.getRelationalParents(child); ungroundedParams = child.addParams; // keep a list of variables which we know how to ground HashSet<String> handledVars = new HashSet<String>(); // - all parameters of the main node are obviously known for(String p : mainNode.params) handledVars.add(p); // - additional parameters of the main node, for which we must generate all possible bindings, can be regarded as handled if(mainNode.addParams != null) for(String p : mainNode.addParams) handledVars.add(p); // determine domains of ungrounded params (if any) // If a parameter appears in more than one parent, we use the most specific type in the taxonomy that we come across Taxonomy taxonomy = bn.getTaxonomy(); if(ungroundedParams != null) { ungroundedParamDomains = new String[ungroundedParams.length]; for(int i = 0; i < ungroundedParams.length; i++) { for(RelationalNode parent : parents) { for(int j = 0; j < parent.params.length; j++) { if(parent.params[j].equals(ungroundedParams[i])) { Signature sig = parent.getSignature(); if(sig != null && !parent.isConstant) { // sig can be null for built-in predicates if(sig.argTypes.length != parent.params.length) throw new Exception(String.format("Parameter count in signature %s (%d) does not match node %s (%d).", sig.toString(), sig.argTypes.length, parent.toString(), parent.params.length)); if(ungroundedParamDomains[i] == null || taxonomy.query_isa(sig.argTypes[j], ungroundedParamDomains[i])) ungroundedParamDomains[i] = sig.argTypes[j]; } } } } } } // now take care of the yet unhandled parameters Collection<RelationalNode> workingSet = new LinkedList<RelationalNode>(); for(RelationalNode p : parents) workingSet.add(p); // while we have parents that aren't yet grounded... while(!workingSet.isEmpty()) { Collection<RelationalNode> newWorkingSet = new LinkedList<RelationalNode>(); // go through all of them int gains = 0; for(RelationalNode n : workingSet) { int numHandledParams = 0; FunctionalLookup flookup = null; // check all of the parent's parameters for(String param : n.params) { //System.out.println("trying to handle " + param + " in " + n); if(!handledVars.contains(param) && !RelationalNode.isConstant(param)) { // check if we can handle this parameter via a functional lookup // - if we already have a functional lookup for this node, we definitely can if(flookup != null) { handledVars.add(param); ++numHandledParams; ++gains; } // - otherwise look at all the keys for the relation and check if we // already have one of them completely else { Collection<RelationKey> keys = bn.getRelationKeys(n.getFunctionName()); if(keys != null) { for(RelationKey key : keys) { int c = 0; for(Integer keyParamIdx : key.keyIndices) { if(handledVars.contains(n.params[keyParamIdx])) ++c; } if(c == key.keyIndices.size()) { //System.out.println(key + " can handle " + param); handledVars.add(param); flookup = new FunctionalLookup(key, n); functionalLookups.add(flookup); ++numHandledParams; ++gains; break; } } } } } else ++numHandledParams; } // if not all the parameters were handled, we need to reiterate if(numHandledParams != n.params.length) newWorkingSet.add(n); } // if there weren't any gains in this iteration, then we cannot ground the parents if(gains == 0 && !newWorkingSet.isEmpty()) { throw new Exception("Could not determine how to ground parents of " + mainNode + "; some parameters of " + newWorkingSet + " could not be resolved; handled vars: " + handledVars); } workingSet = newWorkingSet; } } @Override public String toString() { StringBuffer ret = new StringBuffer("<known from main node: "); // known bindings from main node ret.append(StringTool.join(", ", mainNode.params)); // functional lookups ret.append("; functional lookups: "); int i = 0; for(FunctionalLookup fl : this.functionalLookups) { if(i++ > 0) ret.append(", "); ret.append(fl.key.toString()); } ret.append(">"); return ret.toString(); } /** * generates a grounding of all parent nodes (and the main node itself), i.e. a list of actual parameters for each node, given a vector of actual parameters for this object's main node * @param actualParameters actual parameters of the man node for which this parameter grounding should be performed * @return mapping of node indices to lists of corresponding actual parameters or null * @throws Exception * @deprecated this method is apparently not required; it is not referenced anywhere */ public Map<Integer, String[]> generateParameterSets(String[] actualParameters, Database db) throws Exception { HashMap<Integer, String[]> m = new HashMap<Integer, String[]>(); m.put(this.mainNode.index, actualParameters); // generate the variable bindings via parameter matching and functional lookups HashMap<String, String> varBindings = generateParameterBindings(actualParameters, db); // get the parameter set for each parent node if(varBindings != null) { for(RelationalNode parent : this.parents) { String[] params = new String[parent.params.length]; for(int i = 0; i < params.length; i++) params[i] = varBindings.get(parent.params[i]); m.put(parent.index, params); } return m; } else return null; } /** * generates all possible groundings of all parent nodes (and the main node itself), where a grounding is a list of actual parameters for each node, given a vector of actual parameters for this object's main node * @param actualParameters actual parameters of the main node for which this parameter grounding should be performed * @return vector of mappings of node indices to lists of corresponding actual parameters or null if there is no valid binding for the given actual parameters of the main node * @throws Exception */ public Vector<ParentGrounding> getGroundings(String[] actualParameters, GenericDatabase<?,?> db) throws Exception { // generate all the parameter bindings we can HashMap<String, String> paramBindings = generateParameterBindings(actualParameters, db); if(paramBindings == null) return null; // complete the bindings and get the parameter sets for each complete binding Vector<ParentGrounding> v = new Vector<ParentGrounding>(); getCompleteGroundings(actualParameters, db, paramBindings, 0, v); return v; } public static class ParentGrounding { public HashMap<Integer, String[]> nodeArgs; public HashMap<String,String> paramBinding; public ParentGrounding(HashMap<Integer, String[]> nodeArgs, HashMap<String,String> paramBinding) { this.nodeArgs = nodeArgs; this.paramBinding = paramBinding; } } /** * completes a (yet incomplete) grounding by considering all possibilities of grounding the ungrounded variables * @param mainNodeParams * @param db * @param paramBindings * @param idx * @param ret * @throws Exception */ @SuppressWarnings("unchecked") protected void getCompleteGroundings(String[] mainNodeParams, GenericDatabase<?,?> db, HashMap<String, String> paramBindings, int idx, Vector<ParentGrounding> ret) throws Exception { if(ungroundedParams == null || idx == ungroundedParams.length) { // all variables have been grounded, so now generate a mapping: node index -> list of actual parameters HashMap<Integer, String[]> m = new HashMap<Integer, String[]>(); m.put(this.mainNode.index, mainNodeParams); for(RelationalNode parent : this.parents) { String[] params = new String[parent.params.length]; for(int i = 0; i < params.length; i++) { if(RelationalNode.isConstant(parent.params[i])) params[i] = parent.params[i]; else params[i] = paramBindings.get(parent.params[i]); } m.put(parent.index, params); } ret.add(new ParentGrounding(m, (HashMap<String,String>)paramBindings.clone())); } else { // ground the next variable String param = ungroundedParams[idx]; Iterable<String> s = db.getDomain(ungroundedParamDomains[idx]); if(s == null) throw new Exception("Domain " + ungroundedParamDomains[idx] + " not found!"); for(String constant : s) { paramBindings.put(param, constant); getCompleteGroundings(mainNodeParams, db, paramBindings, idx+1, ret); } } } /** * generates, for a particular binding of the main node's parameters, the complete binding of all relevant variables (which includes all variables/parameters of parents) using the functional lookups that were determined at construction time * @param actualParameters * @param db * @return * @throws Exception */ public HashMap<String, String> generateParameterBindings(String[] actualParameters, GenericDatabase<?,?> db) throws Exception { HashMap<String, String> bindings = new HashMap<String, String>(); // add known bindings from main node for(int i = 0; i < mainNode.params.length; i++) bindings.put(mainNode.params[i], actualParameters[i]); // perform functional lookups for(FunctionalLookup fl : this.functionalLookups) if(!fl.doLookup(db, bindings)) // if a functional lookup cannot be applied, there is no full parameter binding (probably because a precondition is not applicable) return null; return bindings; } }