001    package aima.logic.fol.inference;
002    
003    import java.util.ArrayList;
004    import java.util.HashMap;
005    import java.util.List;
006    import java.util.Map;
007    
008    import aima.logic.fol.inference.proof.Proof;
009    import aima.logic.fol.inference.proof.ProofFinal;
010    import aima.logic.fol.inference.proof.ProofStepBwChGoal;
011    import aima.logic.fol.kb.FOLKnowledgeBase;
012    import aima.logic.fol.kb.data.Clause;
013    import aima.logic.fol.kb.data.Literal;
014    import aima.logic.fol.parsing.ast.AtomicSentence;
015    import aima.logic.fol.parsing.ast.Sentence;
016    import aima.logic.fol.parsing.ast.Term;
017    import aima.logic.fol.parsing.ast.Variable;
018    
019    /**
020     * Artificial Intelligence A Modern Approach (2nd Edition): Figure 9.6, page 288.
021     * 
022     * <pre>
023     * function FOL-BC-ASK(KB, goals, theta) returns a set of substitutions
024     *   input: KB, a knowledge base
025     *          goals, a list of conjuncts forming a query (theta already applied)
026     *          theta, the current substitution, initially the empty substitution {}
027     *   local variables: answers, a set of substitutions, initially empty
028     *   
029     *   if goals is empty then return {theta}
030     *   qDelta <- SUBST(theta, FIRST(goals))
031     *   for each sentence r in KB where STANDARDIZE-APART(r) = (p1 ^ ... ^ pn => q)
032     *          and thetaDelta <- UNIFY(q, qDelta) succeeds
033     *       new_goals <- [p1,...,pn|REST(goals)]
034     *       answers <- FOL-BC-ASK(KB, new_goals, COMPOSE(thetaDelta, theta)) U answers
035     *   return answers
036     * </pre>
037     * 
038     * Figure 9.6 A simple backward-chaining algorithm.
039     */
040    
041    /**
042     * @author Ciaran O'Reilly
043     * 
044     */
045    public class FOLBCAsk implements InferenceProcedure {
046    
047            public FOLBCAsk() {
048    
049            }
050    
051            //
052            // START-InferenceProcedure
053            public InferenceResult ask(FOLKnowledgeBase KB, Sentence query) {
054                    // Assertions on the type queries this Inference procedure
055                    // supports
056                    if (!(query instanceof AtomicSentence)) {
057                            throw new IllegalArgumentException(
058                                            "Only Atomic Queries are supported.");
059                    }
060    
061                    List<Literal> goals = new ArrayList<Literal>();
062                    goals.add(new Literal((AtomicSentence) query));
063    
064                    BCAskAnswerHandler ansHandler = new BCAskAnswerHandler();
065    
066                    List<List<ProofStepBwChGoal>> allProofSteps = folbcask(KB, ansHandler,
067                                    goals, new HashMap<Variable, Term>());
068    
069                    ansHandler.setAllProofSteps(allProofSteps);
070    
071                    return ansHandler;
072            }
073    
074            // END-InferenceProcedure
075            //
076    
077            //
078            // PRIVATE METHODS
079            //
080    
081            /**
082             * <code>
083             * function FOL-BC-ASK(KB, goals, theta) returns a set of substitutions
084             *   input: KB, a knowledge base
085             *          goals, a list of conjuncts forming a query (theta already applied)
086             *          theta, the current substitution, initially the empty substitution {}
087             * </code>
088             */
089            private List<List<ProofStepBwChGoal>> folbcask(FOLKnowledgeBase KB,
090                            BCAskAnswerHandler ansHandler, List<Literal> goals,
091                            Map<Variable, Term> theta) {
092                    List<List<ProofStepBwChGoal>> thisLevelProofSteps = new ArrayList<List<ProofStepBwChGoal>>();
093                    // local variables: answers, a set of substitutions, initially empty
094    
095                    // if goals is empty then return {theta}
096                    if (goals.isEmpty()) {
097                            thisLevelProofSteps.add(new ArrayList<ProofStepBwChGoal>());
098                            return thisLevelProofSteps;
099                    }
100    
101                    // qDelta <- SUBST(theta, FIRST(goals))
102                    Literal qDelta = (Literal) KB.subst(theta, goals.get(0));
103    
104                    // for each sentence r in KB where
105                    // STANDARDIZE-APART(r) = (p1 ^ ... ^ pn => q)
106                    for (Clause r : KB.getAllDefiniteClauses()) {
107                            r = KB.standardizeApart(r);
108                            // and thetaDelta <- UNIFY(q, qDelta) succeeds
109                            Map<Variable, Term> thetaDelta = KB.unify(r.getPositiveLiterals()
110                                            .get(0).getAtomicSentence(), qDelta.getAtomicSentence());
111                            if (null != thetaDelta) {
112                                    // new_goals <- [p1,...,pn|REST(goals)]
113                                    List<Literal> newGoals = new ArrayList<Literal>(r
114                                                    .getNegativeLiterals());
115                                    newGoals.addAll(goals.subList(1, goals.size()));
116                                    // answers <- FOL-BC-ASK(KB, new_goals, COMPOSE(thetaDelta,
117                                    // theta)) U answers
118                                    Map<Variable, Term> composed = compose(KB, thetaDelta, theta);
119                                    List<List<ProofStepBwChGoal>> lowerLevelProofSteps = folbcask(
120                                                    KB, ansHandler, newGoals, composed);
121    
122                                    ansHandler.addProofStep(lowerLevelProofSteps, r, qDelta,
123                                                    composed);
124                                    
125                                    thisLevelProofSteps.addAll(lowerLevelProofSteps);
126                            }
127                    }
128    
129                    // return answers
130                    return thisLevelProofSteps;
131            }
132    
133            // Artificial Intelligence A Modern Approach (2nd Edition): page 288.
134            // COMPOSE(delta, tau) is the substitution whose effect is identical to
135            // the effect of applying each substitution in turn. That is,
136            // SUBST(COMPOSE(theta1, theta2), p) = SUBST(theta2, SUBST(theta1, p))
137            private Map<Variable, Term> compose(FOLKnowledgeBase KB,
138                            Map<Variable, Term> theta1, Map<Variable, Term> theta2) {
139                    Map<Variable, Term> composed = new HashMap<Variable, Term>();
140    
141                    // So that it behaves like:
142                    // SUBST(theta2, SUBST(theta1, p))
143                    // There are two steps involved here.
144                    // See: http://logic.stanford.edu/classes/cs157/2008/notes/chap09.pdf
145                    // for a detailed discussion:
146    
147                    // 1. Apply theta2 to the range of theta1.
148                    for (Variable v : theta1.keySet()) {
149                            composed.put(v, KB.subst(theta2, theta1.get(v)));
150                    }
151    
152                    // 2. Adjoin to delta all pairs from tau with different
153                    // domain variables.
154                    for (Variable v : theta2.keySet()) {
155                            if (!theta1.containsKey(v)) {
156                                    composed.put(v, theta2.get(v));
157                            }
158                    }
159    
160                    return cascadeSubstitutions(KB, composed);
161            }
162    
163            // See:
164            // http://logic.stanford.edu/classes/cs157/2008/miscellaneous/faq.html#jump165
165            // for need for this.
166            private Map<Variable, Term> cascadeSubstitutions(FOLKnowledgeBase KB,
167                            Map<Variable, Term> theta) {
168                    for (Variable v : theta.keySet()) {
169                            Term t = theta.get(v);
170                            theta.put(v, KB.subst(theta, t));
171                    }
172    
173                    return theta;
174            }
175    
176            class BCAskAnswerHandler implements InferenceResult {
177    
178                    private List<Proof> proofs = new ArrayList<Proof>();
179                    
180                    public BCAskAnswerHandler() {
181    
182                    }
183    
184                    //
185                    // START-InferenceResult
186                    public boolean isPossiblyFalse() {
187                            return proofs.size() == 0;
188                    }
189    
190                    public boolean isTrue() {
191                            return proofs.size() > 0;
192                    }
193    
194                    public boolean isUnknownDueToTimeout() {
195                            return false;
196                    }
197    
198                    public boolean isPartialResultDueToTimeout() {
199                            return false;
200                    }
201    
202                    public List<Proof> getProofs() {
203                            return proofs;
204                    }
205    
206                    // END-InferenceResult
207                    //
208    
209                    public void setAllProofSteps(List<List<ProofStepBwChGoal>> allProofSteps) {
210                            for (List<ProofStepBwChGoal> steps : allProofSteps) {
211                                    ProofStepBwChGoal lastStep = steps.get(steps.size() - 1);
212                                    Map<Variable, Term> theta = lastStep.getBindings();
213                                    proofs.add(new ProofFinal(lastStep, theta));
214                            }
215                    }
216    
217                    public void addProofStep(
218                                    List<List<ProofStepBwChGoal>> currentLevelProofSteps,
219                                    Clause toProve, Literal currentGoal,
220                                    Map<Variable, Term> bindings) {
221    
222                            if (currentLevelProofSteps.size() > 0) {
223                                    ProofStepBwChGoal predecessor = new ProofStepBwChGoal(toProve,
224                                                    currentGoal, bindings);
225                                    for (List<ProofStepBwChGoal> steps : currentLevelProofSteps) {
226                                            if (steps.size() > 0) {
227                                                    steps.get(0).setPredecessor(predecessor);
228                                            }
229                                            steps.add(0, predecessor);
230                                    }
231                            }
232                    }
233            }
234    }