/* Note (santi): - This class implements a resolution engine, which is used by the AI of the NPCs to reason about the queries that the player enters. - In order to make inference efficient, a few pruning heuristics were implemented that can be toggled from the global boolean variables at the top of the file (INFERENCE_allow_increasing_sentences, INFERENCE_allow_variable_to_variable_substitutions). - Moreover, in order to let the game flow in real time, the "InterruptibleResolution" class performs only a few inference steps per game cycle (controllable from the INFERENCE_MAX_RESOLUTIONS_PER_STEP variable). After INFERENCE_MAX_TOTAL_RESOLUTIONS steps, the inference is abandoned. - However, if the pruning rules are removed, and INFERENCE_MAX_TOTAL_RESOLUTIONS is set to infinity, this should, in principle be a full sound and complete resolution engine. */ var DEBUG_resolution:boolean = false; var DEBUG_resolution_detailed:boolean = false; // search prunning strategies to make inference faster: var INFERENCE_maximum_sentence_size:number = 4; var INFERENCE_allow_equal_size_sentences:boolean = true; var INFERENCE_allow_increasing_sentences:boolean = true; var INFERENCE_allow_variable_to_variable_substitutions:boolean = true; var INFERENCE_MAX_RESOLUTIONS_PER_STEP:number = 4000; var INFERENCE_MAX_TOTAL_RESOLUTIONS:number = 1000000; // at this point, inference will stop // var INFERENCE_MAX_TOTAL_RESOLUTIONS:number = 400000; // at this point, inference will stop // var INFERENCE_STEP_STATE_STILL_RUNNING:number = 0; var INFERENCE_STEP_STATE_DONE:number = 1; var INFERENCE_STEP_STATE_COMPUTE_LIMIT_REACHED:number = 2; class InferenceNode { constructor(s:Sentence, b:Bindings, p1:InferenceNode, p2:InferenceNode) { this.sentence = s; this.bindings = b; this.parent1 = p1; this.parent2 = p2; } getBaseSentences(target:Sentence[]) : Sentence[] { let open:InferenceNode[] = [this]; let l:Sentence[] = []; while(open.length > 0) { let current:InferenceNode = open[0]; if (current.parent1 == null && current.parent2 == null && current.sentence != null) { if (l.indexOf(current.sentence) == -1 && target.indexOf(current.sentence) == -1) l.push(current.sentence); } else { if (current.parent1 != null) open.push(current.parent1); if (current.parent2 != null) open.push(current.parent2); } open.splice(0,1); } return l; } getValueForVariableName(vName:string) : TermAttribute { return this.bindings.getValueForVariableName(vName); } matchesOnVariableNames(n2:InferenceNode, vNames:string[]) : boolean { for(let vName of vNames) { let v1:TermAttribute = this.getValueForVariableName(vName); let v2:TermAttribute = n2.getValueForVariableName(vName); if (v1 == null && v2 != null) return false; if (v1 != null && v1 == null) return false; if (Term.equalsNoBindingsAttribute(v1, v2) != 1) return false; } return true; } toString() : string { return "[ sentence: " + this.sentence + ", bindings: " + this.bindings + "]"; } sentence:Sentence; bindings:Bindings; parent1:InferenceNode; parent2:InferenceNode; } class InterruptibleResolution { constructor(KB:SentenceContainer, additionalSentences:Sentence[], target:Sentence[], occursCheck:boolean, treatSpatialPredicatesSpecially:boolean, ai:RuleBasedAI) { this.sort_cache_spatial_relation = ai.o.getSort("spatial-relation"); this.sort_cache_superlative = ai.o.getSort("superlative-adjective"); this.KB = KB; this.originalTarget = target; // get the bindings in the variables from the target: for(let ts of this.originalTarget) { this.originalTargetVariables = this.originalTargetVariables.concat(ts.getAllVariables()); } this.occursCheck = occursCheck; this.treatSpatialPredicatesSpecially = treatSpatialPredicatesSpecially; this.superlativePredicates = []; // These are predicates such as "nearest", that can only be checked once we have all the solutions this.ai = ai; this.internal_step_state = INFERENCE_STEP_STATE_DONE; // signal that we need to start from scratch the first time step_internal is called // this.internal_step_state_index = 0; for(let s of additionalSentences) { this.additionalSentences.push(new InferenceNode(s, new Bindings(), null, null)); } for(let s of target) { let r:InferenceNode = new InferenceNode(s, new Bindings(), null, null); this.resolutionEqualityCheck(r); if (r.sentence != null && this.treatSpatialPredicatesSpecially) { let n:number = r.sentence.terms.length; r.sentence = this.resolutionSpatialPredicatesCheck(r.sentence, true); if (n != r.sentence.terms.length && r.sentence.terms.length==1) { // we need to check this again: this.resolutionEqualityCheck(r); } } if (r.sentence == null) continue; this.open.push(r); } console.log("InterruptibleResolution: variables: " + this.originalTargetVariables); for(let t of this.open) { console.log(" target: " + t.toString()); } } // executes resolution till the end: resolve() : InferenceNode[] { while(!this.step()) {} this.processSuperlatives(); return this.endResults; } step() : boolean { if (this.stepAccumulatingResults(false)) return true; return false; } stepAccumulatingResults(findAllAnswers:boolean) : boolean { if (DEBUG_resolution) console.log("resolution stepAccumulatingResults:"); if (this.originalTargetVariables.length == 0) findAllAnswers = false; let resolutionsAtStepStart:number = this.total_resolutions; while(this.total_resolutions < resolutionsAtStepStart + INFERENCE_MAX_RESOLUTIONS_PER_STEP) { let newResolvents:InferenceNode[] = this.step_internal(this.additionalSentences, this.occursCheck, !findAllAnswers); if (DEBUG_resolution && newResolvents != null) { console.log(this.debug_inferenceSentenceLengths(newResolvents)); } this.firstStep = false; if (newResolvents == null || (this.open.length == 0 && newResolvents.length == 0)) { console.log("step: finished with #total_resolutions = " + this.total_resolutions + " closed: "+this.closed.length+", open: "+this.open.length+", end results: " + this.endResults.length); if (DEBUG_resolution) console.log(" - no more resolvents: " +this.endResults.length + " sets of bindings cause a contradiction! (CLOSED: " + this.closed.length + ")"); this.processSuperlatives(); return true; } // console.log("resolution stepAccumulatingResults: newResolvents.length = " + newResolvents.length); if (DEBUG_resolution) { for(let resolvent of newResolvents) { console.log(" " + resolvent.sentence + " -> " + resolvent.bindings); } } // let anyNewResolvent:boolean = false; for(let newResolvent of newResolvents) { let r:Sentence = newResolvent.sentence; let b:Bindings = newResolvent.bindings; if (r.terms.length == 0) { // we have found a contradiction! if (DEBUG_resolution) console.log(" - contradiction! (CLOSED: " + this.closed.length + ")"); let endResult:InferenceNode = new InferenceNode(r, new Bindings(), newResolvent.parent1, newResolvent.parent2); for(let [v,t] of b.l) { if (this.originalTargetVariables.indexOf(v)>=0) { let t2:TermAttribute = t.applyBindings(b); if (endResult.bindings.l.indexOf([v,t2]) == -1) endResult.bindings.l.push([v,t2]); } } let found:boolean = false; for(let tmp of this.endResults) { if (tmp.bindings.equals(endResult.bindings)) { found = true; break; } } if (!found) { this.closed.push(endResult); this.endResults.push(endResult); if (!findAllAnswers) { // we are done! console.log("step: finished with #total_resolutions = " + this.total_resolutions + " closed: "+this.closed.length+", open: "+this.open.length+", end results: " + this.endResults.length); this.processSuperlatives(); return true; } } else { console.warn(" end result was already there: " + endResult.bindings); } } else { let found:boolean = false; // make sure we are not inferring something we already knew: for(let tmp of this.closed) { if (this.resultCanBeFilteredOut(newResolvent, tmp.sentence, tmp.bindings)) { found = true; break; } } if (!found) { for(let tmp of this.open) { if (this.resultCanBeFilteredOut(newResolvent, tmp.sentence, tmp.bindings)) { found = true; break; } } } if (!found) { for(let tmp of this.additionalSentences) { if (this.resultCanBeFilteredOut(newResolvent, tmp.sentence, tmp.bindings)) { found = true; break; } } } if (!found) { for(let tmp of this.KB.plainSentenceList) { if (this.resultCanBeFilteredOut(newResolvent, tmp.sentence, null)) { found = true; break; } } } if (!found) { this.open.push(newResolvent); // anyNewResolvent = true; } } if (DEBUG_resolution) console.log(" " + r.toString()); } } console.log("step: finished with #total_resolutions = " + this.total_resolutions + " closed: "+this.closed.length+", open: "+this.open.length+", end results: " + this.endResults.length); if (this.total_resolutions >= INFERENCE_MAX_TOTAL_RESOLUTIONS) { console.log("step: finished with #total_resolutions = " + this.total_resolutions + " closed: "+this.closed.length+", open: "+this.open.length+", end results: " + this.endResults.length); this.processSuperlatives(); return true; // computation limit reached } if (DEBUG_resolution) console.log(" CLOSED: " + this.closed.length); if (DEBUG_resolution) console.log(" KB.length: " + this.KB.plainSentenceList.length); // if (!anyNewResolvent) { // console.log("all the resolvents in this round where already in the closed list, so we are done!"); // this.processSuperlatives(); // return true; // } return false; } step_internal(sentences:InferenceNode[], occursCheck:boolean, stopOnFirstContradiction:boolean) : InferenceNode[] { let newResolvents:InferenceNode[] = []; if (DEBUG_resolution) console.log("InterruptibleResolution.step_internal with sentences.length = " + sentences.length + ", open.length = " + this.open.length); if (this.firstStep) { for(let i:number = 0;i " + this.debug_inferenceSentenceLengths(tmp)); this.total_resolutions++; for(let r of tmp) { this.resolutionEqualityCheck(r); if (r.sentence != null && this.treatSpatialPredicatesSpecially) r.sentence = this.resolutionSpatialPredicatesCheck(r.sentence, false); if (r.sentence == null) continue; let found:boolean = false; for(let i:number = 0;i " + this.debug_inferenceSentenceLengths(tmp)); this.total_resolutions++; for(let r of tmp) { this.resolutionEqualityCheck(r); if (r.sentence != null && this.treatSpatialPredicatesSpecially) r.sentence = this.resolutionSpatialPredicatesCheck(r.sentence, false); if (r.sentence == null) continue; let found:boolean = false; for(let i:number = 0;i " + this.debug_inferenceSentenceLengths(tmp)); this.total_resolutions++; for(let r of tmp) { this.resolutionEqualityCheck(r); if (r.sentence != null && this.treatSpatialPredicatesSpecially) r.sentence = this.resolutionSpatialPredicatesCheck(r.sentence, false); if (r.sentence == null) continue; let found:boolean = false; for(let i:number = 0;i= INFERENCE_MAX_RESOLUTIONS_PER_STEP) { // // we need to interrupt: // console.log("step_internal: interrupted with #resolutions = " + resolutions + " (" + this.total_resolutions + "), closed "+this.closed.length+", open: "+this.open.length+", newResolvents: "+newResolvents.length+", end results: " + this.endResults.length); // return null; // } // console.log("step_internal: finished with #total_resolutions = " + this.total_resolutions + " closed: "+this.closed.length+", open: "+this.open.length+", newResolvents: "+newResolvents.length+", end results: " + this.endResults.length); this.internal_step_state = INFERENCE_STEP_STATE_DONE; return newResolvents; } // Checks to see if the equality predicate results in a contradiction: sets r.sentence = null // Or if there are any terms of the from x = x (which are then removed) resolutionEqualityCheck(r:InferenceNode) { let s:Sentence = r.sentence; let toDelete:Term[] = []; for(let i:number = 0;i(s.terms[i].attributes[0]), s.terms[i].attributes[1]]); toDelete.push(s.terms[i]); } else if ((s.terms[i].attributes[1] instanceof VariableTermAttribute) && (s.terms[i].attributes[0] instanceof ConstantTermAttribute) && s.terms[i].attributes[1].sort.subsumes(s.terms[i].attributes[0].sort)) { r.bindings.l.push([(s.terms[i].attributes[1]), s.terms[i].attributes[0]]); toDelete.push(s.terms[i]); } } continue; } if ((s.sign[i] && equals==1) || (!s.sign[i] && equals==-1)) { r.sentence = null; return; } else { toDelete.push(s.terms[i]); } } } for(let t of toDelete) { let idx:number = s.terms.indexOf(t); s.terms.splice(idx,1); s.sign.splice(idx,1); } } resolutionSpatialPredicatesCheck(s:Sentence, firstTime:boolean) : Sentence { let toDelete:Term[] = []; for(let i:number = 0;is.terms[i].attributes[0]).value, (s.terms[i].attributes[1]).value, this.ai.selfID); if (DEBUG_resolution) console.log("checkSpatialRelation: " + s.terms[i] + " -> " + truth ); if (truth != null && truth != s.sign[i]) { toDelete.push(s.terms[i]); } } } for(let t of toDelete) { let idx:number = s.terms.indexOf(t); s.terms.splice(idx,1); s.sign.splice(idx,1); } return s; } processSuperlatives() { for(let superlative of this.superlativePredicates) { this.endResults = this.ai.processSuperlatives(this.endResults, superlative); } } /* - algorithm should be: P1...Pn, Q1...Qm, usedP (initially []), usedQ (initially []) found = false for i = 0;i bindings.l.length && !anyNonVariable) continue; } // generate one resolvent: let r:InferenceNode = new InferenceNode(new Sentence([],[]), bindings2, parent1, parent2); for(let i2:number = 0;i2 s1.terms.length && r.sentence.terms.length > s2.terms.length) { if (DEBUG_resolution_detailed) console.log("Removed because of size (1a)... " + r.sentence.terms.length + " vs " + s1.terms.length + " and " + s2.terms.length); return; } } if (!INFERENCE_allow_equal_size_sentences) { if (r.sentence.terms.length >= s1.terms.length && r.sentence.terms.length >= s2.terms.length) { if (DEBUG_resolution_detailed) console.log("Removed because of size (1a)... " + r.sentence.terms.length + " vs " + s1.terms.length + " and " + s2.terms.length); return; } } if (r.sentence.terms.length > INFERENCE_maximum_sentence_size) { if (DEBUG_resolution_detailed) console.log("Removed because of size (2)... " + r.sentence.terms.length); return; } resolvents.push(r); if (DEBUG_resolution) { console.log("resolutionBetweenSentencesWithBindings_internal: new resolvent"); console.log(" s1: " + s1); console.log(" s2: " + s2); console.log(" indexes: " + i + " , " + j); console.log(" bindings: " + r.bindings); console.log(" resolvent: " + r.sentence); } } } } // --> If previousR subset r (the non contained do not have any variables that can affect the final bindings) -> filter resultCanBeFilteredOut(r:InferenceNode, previousSentence:Sentence, previousBindings:Bindings): boolean { let rl:number = r.sentence.terms.length; let psl:number = previousSentence.terms.length; if (r.sentence.terms.length < psl) return false; if (previousBindings != null && !r.bindings.equals(previousBindings)) return false; let used:boolean[] = []; for(let i:number = 0; i