001 package aima.logic.fol.kb; 002 003 import java.util.ArrayList; 004 import java.util.Collections; 005 import java.util.HashMap; 006 import java.util.LinkedHashMap; 007 import java.util.LinkedHashSet; 008 import java.util.List; 009 import java.util.Map; 010 import java.util.Set; 011 012 import aima.logic.fol.CNFConverter; 013 import aima.logic.fol.StandardizeApart; 014 import aima.logic.fol.StandardizeApartIndexical; 015 import aima.logic.fol.StandardizeApartIndexicalFactory; 016 import aima.logic.fol.StandardizeApartResult; 017 import aima.logic.fol.SubstVisitor; 018 import aima.logic.fol.Unifier; 019 import aima.logic.fol.VariableCollector; 020 import aima.logic.fol.domain.FOLDomain; 021 import aima.logic.fol.inference.FOLOTTERLikeTheoremProver; 022 import aima.logic.fol.inference.InferenceProcedure; 023 import aima.logic.fol.inference.InferenceResult; 024 import aima.logic.fol.inference.proof.Proof; 025 import aima.logic.fol.inference.proof.ProofStepClauseClausifySentence; 026 import aima.logic.fol.kb.data.CNF; 027 import aima.logic.fol.kb.data.Chain; 028 import aima.logic.fol.kb.data.Clause; 029 import aima.logic.fol.kb.data.Literal; 030 import aima.logic.fol.parsing.FOLParser; 031 import aima.logic.fol.parsing.ast.FOLNode; 032 import aima.logic.fol.parsing.ast.Predicate; 033 import aima.logic.fol.parsing.ast.Sentence; 034 import aima.logic.fol.parsing.ast.Term; 035 import aima.logic.fol.parsing.ast.Variable; 036 037 /** 038 * A First Order Logic (FOL) Knowledge Base. 039 */ 040 041 /** 042 * @author Ciaran O'Reilly 043 * 044 */ 045 public class FOLKnowledgeBase { 046 047 private FOLParser parser; 048 private InferenceProcedure inferenceProcedure; 049 private Unifier unifier; 050 private SubstVisitor substVisitor; 051 private VariableCollector variableCollector; 052 private StandardizeApart standardizeApart; 053 private CNFConverter cnfConverter; 054 // 055 // Persistent data structures 056 // 057 // Keeps track of the Sentences in their original form as added to the 058 // Knowledge base. 059 private List<Sentence> originalSentences = new ArrayList<Sentence>(); 060 // The KB in clause form 061 private Set<Clause> clauses = new LinkedHashSet<Clause>(); 062 // Keep track of all of the definite clauses in the database 063 // along with those that represent implications. 064 private List<Clause> allDefiniteClauses = new ArrayList<Clause>(); 065 private List<Clause> implicationDefiniteClauses = new ArrayList<Clause>(); 066 // All the facts in the KB indexed by Atomic Sentence name (Note: pg. 279) 067 private Map<String, List<Literal>> indexFacts = new HashMap<String, List<Literal>>(); 068 // Keep track of indexical keys for uniquely standardizing apart sentences 069 private StandardizeApartIndexical variableIndexical = StandardizeApartIndexicalFactory 070 .newStandardizeApartIndexical('v'); 071 private StandardizeApartIndexical queryIndexical = StandardizeApartIndexicalFactory 072 .newStandardizeApartIndexical('q'); 073 074 // 075 // PUBLIC METHODS 076 // 077 public FOLKnowledgeBase(FOLDomain domain) { 078 // Default to Full Resolution if not set. 079 this(domain, new FOLOTTERLikeTheoremProver()); 080 } 081 082 public FOLKnowledgeBase(FOLDomain domain, 083 InferenceProcedure inferenceProcedure) { 084 this(domain, inferenceProcedure, new Unifier()); 085 } 086 087 public FOLKnowledgeBase(FOLDomain domain, 088 InferenceProcedure inferenceProcedure, Unifier unifier) { 089 this.parser = new FOLParser(new FOLDomain(domain)); 090 this.inferenceProcedure = inferenceProcedure; 091 this.unifier = unifier; 092 // 093 this.substVisitor = new SubstVisitor(); 094 this.variableCollector = new VariableCollector(); 095 this.standardizeApart = new StandardizeApart(variableCollector, 096 substVisitor); 097 this.cnfConverter = new CNFConverter(parser); 098 } 099 100 public void clear() { 101 this.originalSentences.clear(); 102 this.clauses.clear(); 103 this.allDefiniteClauses.clear(); 104 this.implicationDefiniteClauses.clear(); 105 this.indexFacts.clear(); 106 } 107 108 public InferenceProcedure getInferenceProcedure() { 109 return inferenceProcedure; 110 } 111 112 public void setInferenceProcedure(InferenceProcedure inferenceProcedure) { 113 if (null != inferenceProcedure) { 114 this.inferenceProcedure = inferenceProcedure; 115 } 116 } 117 118 public Sentence tell(String aSentence) { 119 Sentence s = parser.parse(aSentence); 120 tell(s); 121 return s; 122 } 123 124 public void tell(List<? extends Sentence> sentences) { 125 for (Sentence s : sentences) { 126 tell(s); 127 } 128 } 129 130 public void tell(Sentence aSentence) { 131 store(aSentence); 132 } 133 134 /** 135 * 136 * @param aQuerySentence 137 * @return an InferenceResult. 138 */ 139 public InferenceResult ask(String aQuerySentence) { 140 return ask(parser.parse(aQuerySentence)); 141 } 142 143 public InferenceResult ask(Sentence aQuery) { 144 // Want to standardize apart the query to ensure 145 // it does not clash with any of the sentences 146 // in the database 147 StandardizeApartResult saResult = standardizeApart.standardizeApart( 148 aQuery, queryIndexical); 149 150 // Need to map the result variables (as they are standardized apart) 151 // to the original queries variables so that the caller can easily 152 // understand and use the returned set of substitutions 153 InferenceResult infResult = getInferenceProcedure().ask( 154 this, saResult.getStandardized()); 155 for (Proof p : infResult.getProofs()) { 156 Map<Variable, Term> im = p.getAnswerBindings(); 157 Map<Variable, Term> em = new LinkedHashMap<Variable, Term>(); 158 for (Variable rev : saResult.getReverseSubstitution().keySet()) { 159 em.put((Variable) saResult.getReverseSubstitution() 160 .get(rev), im.get(rev)); 161 } 162 p.replaceAnswerBindings(em); 163 } 164 165 return infResult; 166 } 167 168 public int getNumberFacts() { 169 return allDefiniteClauses.size() - implicationDefiniteClauses.size(); 170 } 171 172 public int getNumberRules() { 173 return clauses.size() - getNumberFacts(); 174 } 175 176 public List<Sentence> getOriginalSentences() { 177 return Collections.unmodifiableList(originalSentences); 178 } 179 180 public List<Clause> getAllDefiniteClauses() { 181 return Collections.unmodifiableList(allDefiniteClauses); 182 } 183 184 public List<Clause> getAllDefiniteClauseImplications() { 185 return Collections.unmodifiableList(implicationDefiniteClauses); 186 } 187 188 public Set<Clause> getAllClauses() { 189 return Collections.unmodifiableSet(clauses); 190 } 191 192 // Note: pg 278, FETCH(q) concept. 193 public synchronized Set<Map<Variable, Term>> fetch(Literal l) { 194 // Get all of the substitutions in the KB that p unifies with 195 Set<Map<Variable, Term>> allUnifiers = new LinkedHashSet<Map<Variable, Term>>(); 196 197 List<Literal> matchingFacts = fetchMatchingFacts(l); 198 if (null != matchingFacts) { 199 for (Literal fact : matchingFacts) { 200 Map<Variable, Term> substitution = unifier.unify(l 201 .getAtomicSentence(), fact.getAtomicSentence()); 202 if (null != substitution) { 203 allUnifiers.add(substitution); 204 } 205 } 206 } 207 208 return allUnifiers; 209 } 210 211 // Note: To support FOL-FC-Ask 212 public Set<Map<Variable, Term>> fetch(List<Literal> literals) { 213 Set<Map<Variable, Term>> possibleSubstitutions = new LinkedHashSet<Map<Variable, Term>>(); 214 215 if (literals.size() > 0) { 216 Literal first = literals.get(0); 217 List<Literal> rest = literals.subList(1, literals.size()); 218 219 recursiveFetch(new LinkedHashMap<Variable, Term>(), first, rest, 220 possibleSubstitutions); 221 } 222 223 return possibleSubstitutions; 224 } 225 226 public Map<Variable, Term> unify(FOLNode x, FOLNode y) { 227 return unifier.unify(x, y); 228 } 229 230 public Sentence subst(Map<Variable, Term> theta, Sentence aSentence) { 231 return substVisitor.subst(theta, aSentence); 232 } 233 234 public Literal subst(Map<Variable, Term> theta, Literal l) { 235 return substVisitor.subst(theta, l); 236 } 237 238 public Term subst(Map<Variable, Term> theta, Term aTerm) { 239 return substVisitor.subst(theta, aTerm); 240 } 241 242 // Note: see page 277. 243 public Sentence standardizeApart(Sentence aSentence) { 244 return standardizeApart.standardizeApart(aSentence, variableIndexical) 245 .getStandardized(); 246 } 247 248 public Clause standardizeApart(Clause aClause) { 249 return standardizeApart.standardizeApart(aClause, variableIndexical); 250 } 251 252 public Chain standardizeApart(Chain aChain) { 253 return standardizeApart.standardizeApart(aChain, variableIndexical); 254 } 255 256 public Set<Variable> collectAllVariables(Sentence aSentence) { 257 return variableCollector.collectAllVariables(aSentence); 258 } 259 260 public CNF convertToCNF(Sentence aSentence) { 261 return cnfConverter.convertToCNF(aSentence); 262 } 263 264 public Set<Clause> convertToClauses(Sentence aSentence) { 265 CNF cnf = cnfConverter.convertToCNF(aSentence); 266 267 return new LinkedHashSet<Clause>(cnf.getConjunctionOfClauses()); 268 } 269 270 public Literal createAnswerLiteral(Sentence forQuery) { 271 String alName = parser.getFOLDomain().addAnswerLiteral(); 272 List<Term> terms = new ArrayList<Term>(); 273 274 Set<Variable> vars = variableCollector.collectAllVariables(forQuery); 275 for (Variable v : vars) { 276 // Ensure copies of the variables are used. 277 terms.add(v.copy()); 278 } 279 280 return new Literal(new Predicate(alName, terms)); 281 } 282 283 // Note: see pg. 281 284 public boolean isRenaming(Literal l) { 285 List<Literal> possibleMatches = fetchMatchingFacts(l); 286 if (null != possibleMatches) { 287 return isRenaming(l, possibleMatches); 288 } 289 290 return false; 291 } 292 293 // Note: see pg. 281 294 public boolean isRenaming(Literal l, List<Literal> possibleMatches) { 295 296 for (Literal q : possibleMatches) { 297 if (l.isPositiveLiteral() != q.isPositiveLiteral()) { 298 continue; 299 } 300 Map<Variable, Term> subst = unifier.unify(l.getAtomicSentence(), q 301 .getAtomicSentence()); 302 if (null != subst) { 303 int cntVarTerms = 0; 304 for (Term t : subst.values()) { 305 if (t instanceof Variable) { 306 cntVarTerms++; 307 } 308 } 309 // If all the substitutions, even if none, map to Variables 310 // then this is a renaming 311 if (subst.size() == cntVarTerms) { 312 return true; 313 } 314 } 315 } 316 317 return false; 318 } 319 320 public String toString() { 321 StringBuilder sb = new StringBuilder(); 322 for (Sentence s : originalSentences) { 323 sb.append(s.toString()); 324 sb.append("\n"); 325 } 326 return sb.toString(); 327 } 328 329 // 330 // PROTECTED METHODS 331 // 332 333 protected FOLParser getParser() { 334 return parser; 335 } 336 337 // 338 // PRIVATE METHODS 339 // 340 341 // Note: pg 278, STORE(s) concept. 342 private synchronized void store(Sentence aSentence) { 343 originalSentences.add(aSentence); 344 345 // Convert the sentence to CNF 346 CNF cnfOfOrig = cnfConverter.convertToCNF(aSentence); 347 for (Clause c : cnfOfOrig.getConjunctionOfClauses()) { 348 c.setProofStep(new ProofStepClauseClausifySentence(c, aSentence)); 349 if (c.isEmpty()) { 350 // This should not happen, if so the user 351 // is trying to add an unsatisfiable sentence 352 // to the KB. 353 throw new IllegalArgumentException( 354 "Attempted to add unsatisfiable sentence to KB, orig=[" 355 + aSentence + "] CNF=" + cnfOfOrig); 356 } 357 358 // Ensure all clauses added to the KB are Standardized Apart. 359 c = standardizeApart.standardizeApart(c, variableIndexical); 360 361 // Will make all clauses immutable 362 // so that they cannot be modified externally. 363 c.setImmutable(); 364 if (clauses.add(c)) { 365 // If added keep track of special types of 366 // clauses, as useful for query purposes 367 if (c.isDefiniteClause()) { 368 allDefiniteClauses.add(c); 369 } 370 if (c.isImplicationDefiniteClause()) { 371 implicationDefiniteClauses.add(c); 372 } 373 if (c.isUnitClause()) { 374 indexFact(c.getLiterals().iterator().next()); 375 } 376 } 377 } 378 } 379 380 // Only if it is a unit clause does it get indexed as a fact 381 // see pg. 279 for general idea. 382 private void indexFact(Literal fact) { 383 String factKey = getFactKey(fact); 384 if (!indexFacts.containsKey(factKey)) { 385 indexFacts.put(factKey, new ArrayList<Literal>()); 386 } 387 388 indexFacts.get(factKey).add(fact); 389 } 390 391 private void recursiveFetch(Map<Variable, Term> theta, Literal l, 392 List<Literal> remainingLiterals, 393 Set<Map<Variable, Term>> possibleSubstitutions) { 394 395 // Find all substitutions for current predicate based on the 396 // substitutions of prior predicates in the list (i.e. SUBST with 397 // theta). 398 Set<Map<Variable, Term>> pSubsts = fetch(subst(theta, l)); 399 400 // No substitutions, therefore cannot continue 401 if (null == pSubsts) { 402 return; 403 } 404 405 for (Map<Variable, Term> psubst : pSubsts) { 406 // Ensure all prior substitution information is maintained 407 // along the chain of predicates (i.e. for shared variables 408 // across the predicates). 409 psubst.putAll(theta); 410 if (remainingLiterals.size() == 0) { 411 // This means I am at the end of the chain of predicates 412 // and have found a valid substitution. 413 possibleSubstitutions.add(psubst); 414 } else { 415 // Need to move to the next link in the chain of substitutions 416 Literal first = remainingLiterals.get(0); 417 List<Literal> rest = remainingLiterals.subList(1, 418 remainingLiterals.size()); 419 420 recursiveFetch(psubst, first, rest, possibleSubstitutions); 421 } 422 } 423 } 424 425 private List<Literal> fetchMatchingFacts(Literal l) { 426 return indexFacts.get(getFactKey(l)); 427 } 428 429 private String getFactKey(Literal l) { 430 StringBuilder key = new StringBuilder(); 431 if (l.isPositiveLiteral()) { 432 key.append("+"); 433 } else { 434 key.append("-"); 435 } 436 key.append(l.getAtomicSentence().getSymbolicName()); 437 438 return key.toString(); 439 } 440 }