001 package aima.logic.fol.inference; 002 003 import java.util.ArrayList; 004 import java.util.HashMap; 005 import java.util.LinkedHashSet; 006 import java.util.List; 007 import java.util.Map; 008 import java.util.Set; 009 010 import aima.logic.fol.Connectors; 011 import aima.logic.fol.inference.proof.Proof; 012 import aima.logic.fol.inference.proof.ProofFinal; 013 import aima.logic.fol.inference.proof.ProofStepGoal; 014 import aima.logic.fol.inference.trace.FOLTFMResolutionTracer; 015 import aima.logic.fol.kb.FOLKnowledgeBase; 016 import aima.logic.fol.kb.data.Clause; 017 import aima.logic.fol.kb.data.Literal; 018 import aima.logic.fol.parsing.ast.ConnectedSentence; 019 import aima.logic.fol.parsing.ast.NotSentence; 020 import aima.logic.fol.parsing.ast.Sentence; 021 import aima.logic.fol.parsing.ast.Term; 022 import aima.logic.fol.parsing.ast.Variable; 023 024 /** 025 * Artificial Intelligence A Modern Approach (2nd Edition): page 297. 026 * 027 * The algorithmic approach is very close to the propositional case, described 028 * in Figure 7.12. However, this implementation will use the T)wo F)inger M)ethod 029 * for looking for resolvents between clauses. 030 * Note: very inefficient, 031 * see: http://logic.stanford.edu/classes/cs157/2008/lectures/lecture04.pdf, 032 * slide 21 for the propositional case. 033 * In addition, an Answer literal will be used so that queries with Variables 034 * may be answered (see pg. 300 of AIMA). 035 * 036 */ 037 038 /** 039 * @author Ciaran O'Reilly 040 * 041 */ 042 public class FOLTFMResolution implements InferenceProcedure { 043 044 private long maxQueryTime = 10 * 1000; 045 046 private FOLTFMResolutionTracer tracer = null; 047 048 public FOLTFMResolution() { 049 050 } 051 052 public FOLTFMResolution(long maxQueryTime) { 053 setMaxQueryTime(maxQueryTime); 054 } 055 056 public FOLTFMResolution(FOLTFMResolutionTracer tracer) { 057 setTracer(tracer); 058 } 059 060 public long getMaxQueryTime() { 061 return maxQueryTime; 062 } 063 064 public void setMaxQueryTime(long maxQueryTime) { 065 this.maxQueryTime = maxQueryTime; 066 } 067 068 public FOLTFMResolutionTracer getTracer() { 069 return tracer; 070 } 071 072 public void setTracer(FOLTFMResolutionTracer tracer) { 073 this.tracer = tracer; 074 } 075 076 // 077 // START-InferenceProcedure 078 public InferenceResult ask(FOLKnowledgeBase KB, Sentence alpha) { 079 080 // clauses <- the set of clauses in CNF representation of KB ^ ~alpha 081 Set<Clause> clauses = new LinkedHashSet<Clause>(); 082 for (Clause c : KB.getAllClauses()) { 083 c = KB.standardizeApart(c); 084 c.setStandardizedApartCheckNotRequired(); 085 clauses.addAll(c.getFactors()); 086 } 087 Sentence notAlpha = new NotSentence(alpha); 088 // Want to use an answer literal to pull 089 // query variables where necessary 090 Literal answerLiteral = KB.createAnswerLiteral(notAlpha); 091 Set<Variable> answerLiteralVariables = KB 092 .collectAllVariables(answerLiteral.getAtomicSentence()); 093 Clause answerClause = new Clause(); 094 095 if (answerLiteralVariables.size() > 0) { 096 Sentence notAlphaWithAnswer = new ConnectedSentence(Connectors.OR, 097 notAlpha, answerLiteral.getAtomicSentence()); 098 for (Clause c : KB.convertToClauses(notAlphaWithAnswer)) { 099 c = KB.standardizeApart(c); 100 c.setProofStep(new ProofStepGoal(c)); 101 c.setStandardizedApartCheckNotRequired(); 102 clauses.addAll(c.getFactors()); 103 } 104 105 answerClause.addLiteral(answerLiteral); 106 } else { 107 for (Clause c : KB.convertToClauses(notAlpha)) { 108 c = KB.standardizeApart(c); 109 c.setProofStep(new ProofStepGoal(c)); 110 c.setStandardizedApartCheckNotRequired(); 111 clauses.addAll(c.getFactors()); 112 } 113 } 114 115 TFMAnswerHandler ansHandler = new TFMAnswerHandler(answerLiteral, 116 answerLiteralVariables, answerClause, maxQueryTime); 117 118 // new <- {} 119 Set<Clause> newClauses = new LinkedHashSet<Clause>(); 120 Set<Clause> toAdd = new LinkedHashSet<Clause>(); 121 // loop do 122 int noOfPrevClauses = clauses.size(); 123 do { 124 if (null != tracer) { 125 tracer.stepStartWhile(clauses, clauses.size(), newClauses 126 .size()); 127 } 128 129 newClauses.clear(); 130 131 // for each Ci, Cj in clauses do 132 Clause[] clausesA = new Clause[clauses.size()]; 133 clauses.toArray(clausesA); 134 // Basically, using the simple T)wo F)inger M)ethod here. 135 for (int i = 0; i < clausesA.length; i++) { 136 Clause cI = clausesA[i]; 137 if (null != tracer) { 138 tracer.stepOuterFor(cI); 139 } 140 for (int j = i; j < clausesA.length; j++) { 141 Clause cJ = clausesA[j]; 142 143 if (null != tracer) { 144 tracer.stepInnerFor(cI, cJ); 145 } 146 147 // resolvent <- FOL-RESOLVE(Ci, Cj) 148 Set<Clause> resolvents = cI.binaryResolvents(cJ); 149 150 if (resolvents.size() > 0) { 151 toAdd.clear(); 152 // new <- new <UNION> resolvent 153 for (Clause rc : resolvents) { 154 toAdd.addAll(rc.getFactors()); 155 } 156 157 if (null != tracer) { 158 tracer.stepResolved(cI, cJ, toAdd); 159 } 160 161 ansHandler.checkForPossibleAnswers(toAdd); 162 163 if (ansHandler.isComplete()) { 164 break; 165 } 166 167 newClauses.addAll(toAdd); 168 } 169 170 if (ansHandler.isComplete()) { 171 break; 172 } 173 } 174 if (ansHandler.isComplete()) { 175 break; 176 } 177 } 178 179 noOfPrevClauses = clauses.size(); 180 181 // clauses <- clauses <UNION> new 182 clauses.addAll(newClauses); 183 184 if (ansHandler.isComplete()) { 185 break; 186 } 187 188 // if new is a <SUBSET> of clauses then finished 189 // searching for an answer 190 // (i.e. when they were added the # clauses 191 // did not increase). 192 } while (noOfPrevClauses < clauses.size()); 193 194 if (null != tracer) { 195 tracer.stepFinished(clauses, ansHandler); 196 } 197 198 return ansHandler; 199 } 200 201 // END-InferenceProcedure 202 // 203 204 // 205 // PRIVATE METHODS 206 // 207 class TFMAnswerHandler implements InferenceResult { 208 private Literal answerLiteral = null; 209 private Set<Variable> answerLiteralVariables = null; 210 private Clause answerClause = null; 211 private long finishTime = 0L; 212 private boolean complete = false; 213 private List<Proof> proofs = new ArrayList<Proof>(); 214 private boolean timedOut = false; 215 216 public TFMAnswerHandler(Literal answerLiteral, 217 Set<Variable> answerLiteralVariables, Clause answerClause, 218 long maxQueryTime) { 219 this.answerLiteral = answerLiteral; 220 this.answerLiteralVariables = answerLiteralVariables; 221 this.answerClause = answerClause; 222 // 223 this.finishTime = System.currentTimeMillis() + maxQueryTime; 224 } 225 226 // 227 // START-InferenceResult 228 public boolean isPossiblyFalse() { 229 return !timedOut && proofs.size() == 0; 230 } 231 232 public boolean isTrue() { 233 return proofs.size() > 0; 234 } 235 236 public boolean isUnknownDueToTimeout() { 237 return timedOut && proofs.size() == 0; 238 } 239 240 public boolean isPartialResultDueToTimeout() { 241 return timedOut && proofs.size() > 0; 242 } 243 244 public List<Proof> getProofs() { 245 return proofs; 246 } 247 248 // END-InferenceResult 249 // 250 251 public boolean isComplete() { 252 return complete; 253 } 254 255 private void checkForPossibleAnswers(Set<Clause> resolvents) { 256 // If no bindings being looked for, then 257 // is just a true false query. 258 for (Clause aClause : resolvents) { 259 if (answerClause.isEmpty()) { 260 if (aClause.isEmpty()) { 261 proofs.add(new ProofFinal(aClause.getProofStep(), 262 new HashMap<Variable, Term>())); 263 complete = true; 264 } 265 } else { 266 if (aClause.isEmpty()) { 267 // This should not happen 268 // as added an answer literal, which 269 // implies the database (i.e. premises) are 270 // unsatisfiable to begin with. 271 throw new IllegalStateException( 272 "Generated an empty clause while looking for an answer, implies original KB is unsatisfiable"); 273 } 274 275 if (aClause.isUnitClause() 276 && aClause.isDefiniteClause() 277 && aClause.getPositiveLiterals().get(0) 278 .getAtomicSentence().getSymbolicName() 279 .equals( 280 answerLiteral.getAtomicSentence() 281 .getSymbolicName())) { 282 Map<Variable, Term> answerBindings = new HashMap<Variable, Term>(); 283 List<Term> answerTerms = aClause.getPositiveLiterals() 284 .get(0).getAtomicSentence().getArgs(); 285 int idx = 0; 286 for (Variable v : answerLiteralVariables) { 287 answerBindings.put(v, answerTerms.get(idx)); 288 idx++; 289 } 290 boolean addNewAnswer = true; 291 for (Proof p : proofs) { 292 if (p.getAnswerBindings().equals(answerBindings)) { 293 addNewAnswer = false; 294 break; 295 } 296 } 297 if (addNewAnswer) { 298 proofs.add(new ProofFinal(aClause.getProofStep(), 299 answerBindings)); 300 } 301 } 302 } 303 304 if (System.currentTimeMillis() > finishTime) { 305 complete = true; 306 // Indicate that I have run out of query time 307 timedOut = true; 308 } 309 } 310 } 311 312 public String toString() { 313 StringBuilder sb = new StringBuilder(); 314 sb.append("isComplete=" + complete); 315 sb.append("\n"); 316 sb.append("result=" + proofs); 317 return sb.toString(); 318 } 319 } 320 }