001 package aima.logic.fol.inference; 002 003 import java.util.ArrayList; 004 import java.util.HashMap; 005 import java.util.HashSet; 006 import java.util.LinkedHashSet; 007 import java.util.List; 008 import java.util.Map; 009 import java.util.Set; 010 011 import aima.logic.fol.Connectors; 012 import aima.logic.fol.SubsumptionElimination; 013 import aima.logic.fol.inference.otter.ClauseFilter; 014 import aima.logic.fol.inference.otter.ClauseSimplifier; 015 import aima.logic.fol.inference.otter.LightestClauseHeuristic; 016 import aima.logic.fol.inference.otter.defaultimpl.DefaultClauseFilter; 017 import aima.logic.fol.inference.otter.defaultimpl.DefaultClauseSimplifier; 018 import aima.logic.fol.inference.otter.defaultimpl.DefaultLightestClauseHeuristic; 019 import aima.logic.fol.inference.proof.Proof; 020 import aima.logic.fol.inference.proof.ProofFinal; 021 import aima.logic.fol.inference.proof.ProofStepGoal; 022 import aima.logic.fol.kb.FOLKnowledgeBase; 023 import aima.logic.fol.kb.data.Clause; 024 import aima.logic.fol.kb.data.Literal; 025 import aima.logic.fol.parsing.ast.ConnectedSentence; 026 import aima.logic.fol.parsing.ast.NotSentence; 027 import aima.logic.fol.parsing.ast.Sentence; 028 import aima.logic.fol.parsing.ast.Term; 029 import aima.logic.fol.parsing.ast.TermEquality; 030 import aima.logic.fol.parsing.ast.Variable; 031 032 /** 033 * Artificial Intelligence A Modern Approach (2nd Edition): Figure 9.14, page 307. 034 * 035 * <pre> 036 * procedure OTTER(sos, usable) 037 * inputs: sos, a set of support-clauses defining the problem (a global variable) 038 * usable, background knowledge potentially relevant to the problem 039 * 040 * repeat 041 * clause <- the lightest member of sos 042 * move clause from sos to usable 043 * PROCESS(INFER(clause, usable), sos) 044 * until sos = [] or a refutation has been found 045 * 046 * -------------------------------------------------------------------------------- 047 * 048 * function INFER(clause, usable) returns clauses 049 * 050 * resolve clause with each member of usable 051 * return the resulting clauses after applying filter 052 * 053 * -------------------------------------------------------------------------------- 054 * 055 * procedure PROCESS(clauses, sos) 056 * 057 * for each clause in clauses do 058 * clause <- SIMPLIFY(clause) 059 * merge identical literals 060 * discard clause if it is a tautology 061 * sos <- [clause | sos] 062 * if clause has no literals then a refutation has been found 063 * if clause has one literal then look for unit refutation 064 * </pre> 065 * 066 * Figure 9.14 Sketch of the OTTER theorem prover. Heuristic control is applied in the 067 * selection of the "lightest" clause and in the FILTER function that eliminates uninteresting 068 * clauses from consideration. 069 */ 070 071 // Note: The original implementation of OTTER has been retired 072 // but its successor, Prover9, can be found at: 073 // http://www.prover9.org/ 074 // or 075 // http://www.cs.unm.edu/~mccune/mace4/ 076 // Should you wish to play with a mature implementation of a theorem prover :-) 077 // For lots of interesting problems to play with, see 078 // 'The TPTP Problem Library for Automated Theorem Proving': 079 // http://www.cs.miami.edu/~tptp/ 080 /** 081 * @author Ciaran O'Reilly 082 * 083 */ 084 public class FOLOTTERLikeTheoremProver implements InferenceProcedure { 085 // 086 // Ten seconds is default maximum query time permitted 087 private long maxQueryTime = 10 * 1000; 088 private boolean useParamodulation = true; 089 private LightestClauseHeuristic lightestClauseHeuristic = new DefaultLightestClauseHeuristic(); 090 private ClauseFilter clauseFilter = new DefaultClauseFilter(); 091 private ClauseSimplifier clauseSimplifier = new DefaultClauseSimplifier(); 092 // 093 private Paramodulation paramodulation = new Paramodulation(); 094 095 public FOLOTTERLikeTheoremProver() { 096 097 } 098 099 public FOLOTTERLikeTheoremProver(long maxQueryTime) { 100 setMaxQueryTime(maxQueryTime); 101 } 102 103 public FOLOTTERLikeTheoremProver(boolean useParamodulation) { 104 setUseParamodulation(useParamodulation); 105 } 106 107 public FOLOTTERLikeTheoremProver(long maxQueryTime, 108 boolean useParamodulation) { 109 setMaxQueryTime(maxQueryTime); 110 setUseParamodulation(useParamodulation); 111 } 112 113 public long getMaxQueryTime() { 114 return maxQueryTime; 115 } 116 117 public void setMaxQueryTime(long maxQueryTime) { 118 this.maxQueryTime = maxQueryTime; 119 } 120 121 public boolean isUseParamodulation() { 122 return useParamodulation; 123 } 124 125 public void setUseParamodulation(boolean useParamodulation) { 126 this.useParamodulation = useParamodulation; 127 } 128 129 public LightestClauseHeuristic getLightestClauseHeuristic() { 130 return lightestClauseHeuristic; 131 } 132 133 public void setLightestClauseHeuristic( 134 LightestClauseHeuristic lightestClauseHeuristic) { 135 this.lightestClauseHeuristic = lightestClauseHeuristic; 136 } 137 138 public ClauseFilter getClauseFilter() { 139 return clauseFilter; 140 } 141 142 public void setClauseFilter(ClauseFilter clauseFilter) { 143 this.clauseFilter = clauseFilter; 144 } 145 146 public ClauseSimplifier getClauseSimplifier() { 147 return clauseSimplifier; 148 } 149 150 public void setClauseSimplifier(ClauseSimplifier clauseSimplifier) { 151 this.clauseSimplifier = clauseSimplifier; 152 } 153 154 // 155 // START-InferenceProcedure 156 public InferenceResult ask(FOLKnowledgeBase KB, Sentence alpha) { 157 Set<Clause> sos = new HashSet<Clause>(); 158 Set<Clause> usable = new HashSet<Clause>(); 159 160 // Usable set will be the set of clauses in the KB, 161 // are assuming this is satisfiable as using the 162 // Set of Support strategy. 163 for (Clause c : KB.getAllClauses()) { 164 c = KB.standardizeApart(c); 165 c.setStandardizedApartCheckNotRequired(); 166 usable.addAll(c.getFactors()); 167 } 168 169 // Ensure reflexivity axiom is added to usable if using paramodulation. 170 if (isUseParamodulation()) { 171 // Reflexivity Axiom: x = x 172 TermEquality reflexivityAxiom = new TermEquality(new Variable("x"), 173 new Variable("x")); 174 Clause reflexivityClause = new Clause(); 175 reflexivityClause.addLiteral(new Literal(reflexivityAxiom)); 176 reflexivityClause = KB.standardizeApart(reflexivityClause); 177 reflexivityClause.setStandardizedApartCheckNotRequired(); 178 usable.add(reflexivityClause); 179 } 180 181 Sentence notAlpha = new NotSentence(alpha); 182 // Want to use an answer literal to pull 183 // query variables where necessary 184 Literal answerLiteral = KB.createAnswerLiteral(notAlpha); 185 Set<Variable> answerLiteralVariables = KB 186 .collectAllVariables(answerLiteral.getAtomicSentence()); 187 Clause answerClause = new Clause(); 188 189 if (answerLiteralVariables.size() > 0) { 190 Sentence notAlphaWithAnswer = new ConnectedSentence(Connectors.OR, 191 notAlpha, answerLiteral.getAtomicSentence()); 192 for (Clause c : KB.convertToClauses(notAlphaWithAnswer)) { 193 c = KB.standardizeApart(c); 194 c.setProofStep(new ProofStepGoal(c)); 195 c.setStandardizedApartCheckNotRequired(); 196 sos.addAll(c.getFactors()); 197 } 198 199 answerClause.addLiteral(answerLiteral); 200 } else { 201 for (Clause c : KB.convertToClauses(notAlpha)) { 202 c = KB.standardizeApart(c); 203 c.setProofStep(new ProofStepGoal(c)); 204 c.setStandardizedApartCheckNotRequired(); 205 sos.addAll(c.getFactors()); 206 } 207 } 208 209 // Ensure all subsumed clauses are removed 210 usable.removeAll(SubsumptionElimination.findSubsumedClauses(usable)); 211 sos.removeAll(SubsumptionElimination.findSubsumedClauses(sos)); 212 213 OTTERAnswerHandler ansHandler = new OTTERAnswerHandler(answerLiteral, 214 answerLiteralVariables, answerClause, maxQueryTime); 215 216 IndexedClauses idxdClauses = new IndexedClauses( 217 getLightestClauseHeuristic(), sos, usable); 218 219 return otter(ansHandler, idxdClauses, sos, usable); 220 } 221 222 // END-InferenceProcedure 223 // 224 225 /** 226 * <pre> 227 * procedure OTTER(sos, usable) 228 * inputs: sos, a set of support-clauses defining the problem (a global variable) 229 * usable, background knowledge potentially relevant to the problem 230 * </pre> 231 */ 232 private InferenceResult otter(OTTERAnswerHandler ansHandler, 233 IndexedClauses idxdClauses, Set<Clause> sos, Set<Clause> usable) { 234 235 getLightestClauseHeuristic().initialSOS(sos); 236 237 // * repeat 238 do { 239 // * clause <- the lightest member of sos 240 Clause clause = getLightestClauseHeuristic().getLightestClause(); 241 if (null != clause) { 242 // * move clause from sos to usable 243 sos.remove(clause); 244 getLightestClauseHeuristic().removedClauseFromSOS(clause); 245 usable.add(clause); 246 // * PROCESS(INFER(clause, usable), sos) 247 process(ansHandler, idxdClauses, infer(clause, usable), sos, 248 usable); 249 } 250 251 // * until sos = [] or a refutation has been found 252 } while (sos.size() != 0 && !ansHandler.isComplete()); 253 254 return ansHandler; 255 } 256 257 /** 258 * <pre> 259 * function INFER(clause, usable) returns clauses 260 */ 261 private Set<Clause> infer(Clause clause, Set<Clause> usable) { 262 Set<Clause> resultingClauses = new LinkedHashSet<Clause>(); 263 264 // * resolve clause with each member of usable 265 for (Clause c : usable) { 266 Set<Clause> resolvents = clause.binaryResolvents(c); 267 for (Clause rc : resolvents) { 268 resultingClauses.add(rc); 269 } 270 271 // if using paramodulation to handle equality 272 if (isUseParamodulation()) { 273 Set<Clause> paras = paramodulation.apply(clause, c, true); 274 for (Clause p : paras) { 275 resultingClauses.add(p); 276 } 277 } 278 } 279 280 // * return the resulting clauses after applying filter 281 return getClauseFilter().filter(resultingClauses); 282 } 283 284 // procedure PROCESS(clauses, sos) 285 private void process(OTTERAnswerHandler ansHandler, 286 IndexedClauses idxdClauses, Set<Clause> clauses, Set<Clause> sos, 287 Set<Clause> usable) { 288 289 // * for each clause in clauses do 290 for (Clause clause : clauses) { 291 // * clause <- SIMPLIFY(clause) 292 clause = getClauseSimplifier().simplify(clause); 293 294 // * merge identical literals 295 // Note: Not required as handled by Clause Implementation 296 // which keeps literals within a Set, so no duplicates 297 // will exist. 298 299 // * discard clause if it is a tautology 300 if (clause.isTautology()) { 301 continue; 302 } 303 304 // * if clause has no literals then a refutation has been found 305 // or if it just contains the answer literal. 306 if (!ansHandler.isAnswer(clause)) { 307 // * sos <- [clause | sos] 308 // This check ensure duplicate clauses are not 309 // introduced which will cause the 310 // LightestClauseHeuristic to loop continuously 311 // on the same pair of objects. 312 if (!sos.contains(clause) && !usable.contains(clause)) { 313 for (Clause ac : clause.getFactors()) { 314 if (!sos.contains(ac) && !usable.contains(ac)) { 315 idxdClauses.addClause(ac, sos, usable); 316 317 // * if clause has one literal then look for unit 318 // refutation 319 lookForUnitRefutation(ansHandler, idxdClauses, ac, 320 sos, usable); 321 } 322 } 323 } 324 } 325 326 if (ansHandler.isComplete()) { 327 break; 328 } 329 } 330 } 331 332 private void lookForUnitRefutation(OTTERAnswerHandler ansHandler, 333 IndexedClauses idxdClauses, Clause clause, Set<Clause> sos, 334 Set<Clause> usable) { 335 336 Set<Clause> toCheck = new LinkedHashSet<Clause>(); 337 338 if (ansHandler.isCheckForUnitRefutation(clause)) { 339 for (Clause s : sos) { 340 if (s.isUnitClause()) { 341 toCheck.add(s); 342 } 343 } 344 for (Clause u : usable) { 345 if (u.isUnitClause()) { 346 toCheck.add(u); 347 } 348 } 349 } 350 351 if (toCheck.size() > 0) { 352 toCheck = infer(clause, toCheck); 353 for (Clause t : toCheck) { 354 // * clause <- SIMPLIFY(clause) 355 t = getClauseSimplifier().simplify(t); 356 357 // * discard clause if it is a tautology 358 if (t.isTautology()) { 359 continue; 360 } 361 362 // * if clause has no literals then a refutation has been found 363 // or if it just contains the answer literal. 364 if (!ansHandler.isAnswer(t)) { 365 // * sos <- [clause | sos] 366 // This check ensure duplicate clauses are not 367 // introduced which will cause the 368 // LightestClauseHeuristic to loop continuously 369 // on the same pair of objects. 370 if (!sos.contains(t) && !usable.contains(t)) { 371 idxdClauses.addClause(t, sos, usable); 372 } 373 } 374 375 if (ansHandler.isComplete()) { 376 break; 377 } 378 } 379 } 380 } 381 382 // This is a simple indexing on the clauses to support 383 // more efficient forward and backward subsumption testing. 384 class IndexedClauses { 385 private LightestClauseHeuristic lightestClauseHeuristic = null; 386 // Group the clauses by their # of literals. 387 private Map<Integer, Set<Clause>> clausesGroupedBySize = new HashMap<Integer, Set<Clause>>(); 388 // Keep track of the min and max # of literals. 389 private int minNoLiterals = Integer.MAX_VALUE; 390 private int maxNoLiterals = 0; 391 392 public IndexedClauses(LightestClauseHeuristic lightestClauseHeuristic, 393 Set<Clause> sos, Set<Clause> usable) { 394 this.lightestClauseHeuristic = lightestClauseHeuristic; 395 for (Clause c : sos) { 396 indexClause(c); 397 } 398 for (Clause c : usable) { 399 indexClause(c); 400 } 401 } 402 403 public void addClause(Clause c, Set<Clause> sos, Set<Clause> usable) { 404 // Perform forward subsumption elimination 405 boolean addToSOS = true; 406 for (int i = minNoLiterals; i < c.getNumberLiterals(); i++) { 407 Set<Clause> fs = clausesGroupedBySize.get(i); 408 if (null != fs) { 409 for (Clause s : fs) { 410 if (s.subsumes(c)) { 411 addToSOS = false; 412 break; 413 } 414 } 415 } 416 if (!addToSOS) { 417 break; 418 } 419 } 420 421 if (addToSOS) { 422 sos.add(c); 423 lightestClauseHeuristic.addedClauseToSOS(c); 424 indexClause(c); 425 // Have added clause, therefore 426 // perform backward subsumption elimination 427 Set<Clause> subsumed = new HashSet<Clause>(); 428 for (int i = c.getNumberLiterals() + 1; i <= maxNoLiterals; i++) { 429 subsumed.clear(); 430 Set<Clause> bs = clausesGroupedBySize.get(i); 431 if (null != bs) { 432 for (Clause s : bs) { 433 if (c.subsumes(s)) { 434 subsumed.add(s); 435 if (sos.contains(s)) { 436 sos.remove(s); 437 lightestClauseHeuristic 438 .removedClauseFromSOS(s); 439 } 440 usable.remove(s); 441 } 442 } 443 bs.removeAll(subsumed); 444 } 445 } 446 } 447 } 448 449 // 450 // PRIVATE METHODS 451 // 452 private void indexClause(Clause c) { 453 int size = c.getNumberLiterals(); 454 if (size < minNoLiterals) { 455 minNoLiterals = size; 456 } 457 if (size > maxNoLiterals) { 458 maxNoLiterals = size; 459 } 460 Set<Clause> cforsize = clausesGroupedBySize.get(size); 461 if (null == cforsize) { 462 cforsize = new HashSet<Clause>(); 463 clausesGroupedBySize.put(size, cforsize); 464 } 465 cforsize.add(c); 466 } 467 } 468 469 class OTTERAnswerHandler implements InferenceResult { 470 private Literal answerLiteral = null; 471 private Set<Variable> answerLiteralVariables = null; 472 private Clause answerClause = null; 473 private long finishTime = 0L; 474 private boolean complete = false; 475 private List<Proof> proofs = new ArrayList<Proof>(); 476 private boolean timedOut = false; 477 478 public OTTERAnswerHandler(Literal answerLiteral, 479 Set<Variable> answerLiteralVariables, Clause answerClause, 480 long maxQueryTime) { 481 this.answerLiteral = answerLiteral; 482 this.answerLiteralVariables = answerLiteralVariables; 483 this.answerClause = answerClause; 484 // 485 this.finishTime = System.currentTimeMillis() + maxQueryTime; 486 } 487 488 // 489 // START-InferenceResult 490 public boolean isPossiblyFalse() { 491 return !timedOut && proofs.size() == 0; 492 } 493 494 public boolean isTrue() { 495 return proofs.size() > 0; 496 } 497 498 public boolean isUnknownDueToTimeout() { 499 return timedOut && proofs.size() == 0; 500 } 501 502 public boolean isPartialResultDueToTimeout() { 503 return timedOut && proofs.size() > 0; 504 } 505 506 public List<Proof> getProofs() { 507 return proofs; 508 } 509 510 // END-InferenceResult 511 // 512 513 public boolean isComplete() { 514 return complete; 515 } 516 517 public boolean isLookingForAnswerLiteral() { 518 return !answerClause.isEmpty(); 519 } 520 521 public boolean isCheckForUnitRefutation(Clause clause) { 522 523 if (isLookingForAnswerLiteral()) { 524 if (2 == clause.getNumberLiterals()) { 525 for (Literal t : clause.getLiterals()) { 526 if (t.getAtomicSentence().getSymbolicName().equals( 527 answerLiteral.getAtomicSentence() 528 .getSymbolicName())) { 529 return true; 530 } 531 } 532 } 533 } else { 534 return clause.isUnitClause(); 535 } 536 537 return false; 538 } 539 540 public boolean isAnswer(Clause aClause) { 541 boolean isAns = false; 542 543 if (answerClause.isEmpty()) { 544 if (aClause.isEmpty()) { 545 proofs.add(new ProofFinal(aClause.getProofStep(), 546 new HashMap<Variable, Term>())); 547 complete = true; 548 isAns = true; 549 } 550 } else { 551 if (aClause.isEmpty()) { 552 // This should not happen 553 // as added an answer literal to sos, which 554 // implies the database (i.e. premises) are 555 // unsatisfiable to begin with. 556 throw new IllegalStateException( 557 "Generated an empty clause while looking for an answer, implies original KB or usable is unsatisfiable"); 558 } 559 560 if (aClause.isUnitClause() 561 && aClause.isDefiniteClause() 562 && aClause.getPositiveLiterals().get(0) 563 .getAtomicSentence().getSymbolicName().equals( 564 answerLiteral.getAtomicSentence() 565 .getSymbolicName())) { 566 Map<Variable, Term> answerBindings = new HashMap<Variable, Term>(); 567 List<Term> answerTerms = aClause.getPositiveLiterals().get( 568 0).getAtomicSentence().getArgs(); 569 int idx = 0; 570 for (Variable v : answerLiteralVariables) { 571 answerBindings.put(v, answerTerms.get(idx)); 572 idx++; 573 } 574 boolean addNewAnswer = true; 575 for (Proof p : proofs) { 576 if (p.getAnswerBindings().equals(answerBindings)) { 577 addNewAnswer = false; 578 break; 579 } 580 } 581 if (addNewAnswer) { 582 proofs.add(new ProofFinal(aClause.getProofStep(), 583 answerBindings)); 584 } 585 isAns = true; 586 } 587 } 588 589 if (System.currentTimeMillis() > finishTime) { 590 complete = true; 591 // Indicate that I have run out of query time 592 timedOut = true; 593 } 594 595 return isAns; 596 } 597 598 public String toString() { 599 StringBuilder sb = new StringBuilder(); 600 sb.append("isComplete=" + complete); 601 sb.append("\n"); 602 sb.append("result=" + proofs); 603 return sb.toString(); 604 } 605 } 606 }