001 package aima.logic.fol; 002 003 import java.util.HashSet; 004 import java.util.LinkedHashMap; 005 import java.util.List; 006 import java.util.Map; 007 import java.util.Set; 008 009 import aima.logic.fol.parsing.ast.FOLNode; 010 import aima.logic.fol.parsing.ast.Function; 011 import aima.logic.fol.parsing.ast.Term; 012 import aima.logic.fol.parsing.ast.Variable; 013 014 /** 015 * Artificial Intelligence A Modern Approach (2nd Edition): Figure 9.1, page 278. 016 * 017 * <pre> 018 * function UNIFY(x, y, theta) returns a substitution to make x and y identical 019 * inputs: x, a variable, constant, list, or compound 020 * y, a variable, constant, list, or compound 021 * theta, the substitution built up so far (optional, defaults to empty) 022 * 023 * if theta = failure then return failure 024 * else if x = y the return theta 025 * else if VARIABLE?(x) then return UNIVY-VAR(x, y, theta) 026 * else if VARIABLE?(y) then return UNIFY-VAR(y, x, theta) 027 * else if COMPOUND?(x) and COMPOUND?(y) then 028 * return UNIFY(ARGS[x], ARGS[y], UNIFY(OP[x], OP[y], theta)) 029 * else if LIST?(x) and LIST?(y) then 030 * return UNIFY(REST[x], REST[y], UNIFY(FIRST[x], FIRST[y], theta)) 031 * else return failure 032 * 033 * ------------------------------------------------------------------------------------------------- 034 * 035 * function UNIFY-VAR(var, x, theta) returns a substitution 036 * inputs: var, a variable 037 * x, any expression 038 * theta, the substitution built up so far 039 * 040 * if {var/val} E theta then return UNIFY(val, x, theta) 041 * else if {x/val} E theta then return UNIFY(var, val, theta) 042 * else if OCCUR-CHECK?(var, x) then return failure 043 * else return add {var/x} to theta 044 * </pre> 045 * 046 * Figure 9.1 The unification algorithm. The algorithm works by comparing the structures 047 * of the inputs, elements by element. The substitution theta that is the argument to UNIFY is built 048 * up along the way and is used to make sure that later comparisons are consistent with bindings 049 * that were established earlier. In a compound expression, such as F(A, B), the function OP 050 * picks out the function symbol F and the function ARGS picks out the argument list (A, B). 051 */ 052 053 /** 054 * @author Ravi Mohan 055 * @author Ciaran O'Reilly 056 * 057 */ 058 public class Unifier { 059 // 060 private static SubstVisitor _substVisitor = new SubstVisitor(); 061 private static VariableCollector _variableCollector = new VariableCollector(); 062 063 public Unifier() { 064 065 } 066 067 public Map<Variable, Term> unify(FOLNode x, FOLNode y) { 068 return unify(x, y, new LinkedHashMap<Variable, Term>()); 069 } 070 071 /** 072 * <code> 073 * function UNIFY(x, y, theta) returns a substitution to make x and y identical 074 * inputs: x, a variable, constant, list, or compound 075 * y, a variable, constant, list, or compound 076 * theta, the substitution built up so far (optional, defaults to empty) 077 * </code> 078 * 079 * @return a Map<Variable, Term> representing the substitution (i.e. a set 080 * of variable/term pairs, see pg. 254 for a description) or null 081 * which is used to indicate a failure to unify. 082 */ 083 public Map<Variable, Term> unify(FOLNode x, FOLNode y, 084 Map<Variable, Term> theta) { 085 // if theta = failure then return failure 086 if (theta == null) { 087 return null; 088 } else if (x.equals(y)) { 089 // else if x = y then return theta 090 return theta; 091 } else if (x instanceof Variable) { 092 // else if VARIABLE?(x) then return UNIVY-VAR(x, y, theta) 093 return unifyVar((Variable) x, y, theta); 094 } else if (y instanceof Variable) { 095 // else if VARIABLE?(y) then return UNIFY-VAR(y, x, theta) 096 return unifyVar((Variable) y, x, theta); 097 } else if (isCompound(x) && isCompound(y)) { 098 // else if COMPOUND?(x) and COMPOUND?(y) then 099 // return UNIFY(ARGS[x], ARGS[y], UNIFY(OP[x], OP[y], theta)) 100 return unify(args(x), args(y), unifyOps(op(x), op(y), theta)); 101 } else { 102 // else return failure 103 return null; 104 } 105 } 106 107 // else if LIST?(x) and LIST?(y) then 108 // return UNIFY(REST[x], REST[y], UNIFY(FIRST[x], FIRST[y], theta)) 109 public Map<Variable, Term> unify(List<? extends FOLNode> x, 110 List<? extends FOLNode> y, 111 Map<Variable, Term> theta) { 112 if (theta == null) { 113 return null; 114 } else if (x.size() != y.size()) { 115 return null; 116 } else if (x.size() == 0 && y.size() == 0) { 117 return theta; 118 } else if (x.size() == 1 && y.size() == 1) { 119 return unify(x.get(0), y.get(0), theta); 120 } else { 121 return unify(x.subList(1, x.size()), y.subList(1, y.size()), unify( 122 x.get(0), y.get(0), theta)); 123 } 124 } 125 126 // 127 // PROTECTED METHODS 128 // 129 130 // Note: You can subclass and override this method in order 131 // to re-implement the OCCUR-CHECK?() to always 132 // return false if you want that to be the default 133 // behavior, as is the case with Prolog. 134 protected boolean occurCheck(Map<Variable, Term> theta, Variable var, 135 FOLNode x) { 136 if (x instanceof Function) { 137 Set<Variable> varsToCheck = _variableCollector 138 .collectAllVariables((Function) x); 139 if (varsToCheck.contains(var)) { 140 return true; 141 } 142 143 // Now need to check if cascading will cause occurs to happen 144 // e.g. 145 // Loves(SF1(v2),v2) 146 // Loves(v3,SF0(v3)) 147 // or 148 // P(v1,SF0(v1),SF0(v1)) 149 // P(v2,SF0(v2),v2 ) 150 // or 151 // P(v1, F(v2),F(v2),F(v2),v1, F(F(v1)),F(F(F(v1))),v2) 152 // P(F(v3),v4, v5, v6, F(F(v5)),v4, F(v3), F(F(v5))) 153 return cascadeOccurCheck(theta, var, varsToCheck, new HashSet<Variable>(varsToCheck)); 154 } 155 return false; 156 } 157 158 // 159 // PRIVATE METHODS 160 // 161 162 /** 163 * <code> 164 * function UNIFY-VAR(var, x, theta) returns a substitution 165 * inputs: var, a variable 166 * x, any expression 167 * theta, the substitution built up so far 168 * </code> 169 */ 170 private Map<Variable, Term> unifyVar(Variable var, FOLNode x, 171 Map<Variable, Term> theta) { 172 173 if (!Term.class.isInstance(x)) { 174 return null; 175 } else if (theta.keySet().contains(var)) { 176 // if {var/val} E theta then return UNIFY(val, x, theta) 177 return unify(theta.get(var), x, theta); 178 } else if (theta.keySet().contains(x)) { 179 // else if {x/val} E theta then return UNIFY(var, val, theta) 180 return unify(var, theta.get(x), theta); 181 } else if (occurCheck(theta, var, x)) { 182 // else if OCCUR-CHECK?(var, x) then return failure 183 return null; 184 } else { 185 // else return add {var/x} to theta 186 cascadeSubstitution(theta, var, (Term) x); 187 return theta; 188 } 189 } 190 191 private Map<Variable, Term> unifyOps(String x, String y, 192 Map<Variable, Term> theta) { 193 if (theta == null) { 194 return null; 195 } else if (x.equals(y)) { 196 return theta; 197 } else { 198 return null; 199 } 200 } 201 202 private List<? extends FOLNode> args(FOLNode x) { 203 return x.getArgs(); 204 } 205 206 private String op(FOLNode x) { 207 return x.getSymbolicName(); 208 } 209 210 private boolean isCompound(FOLNode x) { 211 return x.isCompound(); 212 } 213 214 private boolean cascadeOccurCheck(Map<Variable, Term> theta, Variable var, Set<Variable> varsToCheck, Set<Variable> varsCheckedAlready) { 215 // Want to check if any of the variable to check end up 216 // looping back around on the new variable. 217 Set<Variable> nextLevelToCheck = new HashSet<Variable>(); 218 for (Variable v : varsToCheck) { 219 Term t = theta.get(v); 220 if (null == t) { 221 // Variable may not be a key so skip 222 continue; 223 } 224 if (t.equals(var)) { 225 // e.g. 226 // v1=v2 227 // v2=SFO(v1) 228 return true; 229 } else if (t instanceof Function) { 230 // Need to ensure the function this variable 231 // is to be replaced by does not contain var. 232 Set<Variable> indirectvars = _variableCollector 233 .collectAllVariables((Function) t); 234 if (indirectvars.contains(var)) { 235 return true; 236 } else { 237 // Determine the next cascade/level 238 // of variables to check for looping 239 for (Variable iv : indirectvars) { 240 if (!varsCheckedAlready.contains(iv)) { 241 nextLevelToCheck.add(iv); 242 } 243 } 244 } 245 } 246 } 247 if (nextLevelToCheck.size() > 0) { 248 varsCheckedAlready.addAll(nextLevelToCheck); 249 return cascadeOccurCheck(theta, var, nextLevelToCheck, varsCheckedAlready); 250 } 251 return false; 252 } 253 254 // See: 255 // http://logic.stanford.edu/classes/cs157/2008/miscellaneous/faq.html#jump165 256 // for need for this. 257 private void cascadeSubstitution(Map<Variable, Term> theta, Variable var, 258 Term x) { 259 theta.put(var, x); 260 for (Variable v : theta.keySet()) { 261 Term t = theta.get(v); 262 theta.put(v, _substVisitor.subst(theta, t)); 263 } 264 } 265 }