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 }