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    }