// -*- Mode: LISP; Syntax: Common-lisp; Package: USER -*- // Justification-based Truth Maintenence System (JTMS) // Version 176. // Last edited 1/29/93, by KDF // Copyright (c) 1986-1992, Kenneth D. Forbus, Northwestern University, // and Johan de Kleer, the Xerox Corporation. // All rights reserved. // See the file legal.txt for a paragraph stating scope of permission // and disclaimer of warranty. The above copyright notice and that // paragraph must be included in any separate copy of this file. "(in-package common-lisp-user)"; define class () slot jtms-title = #f, init-keyword: #"jtms-title"; slot jtms-node-counter = 0, init-keyword: #"jtms-node-counter"; // unique namer for nodes. slot jtms-just-counter = 0, init-keyword: #"jtms-just-counter"; // unique namer for justifications. slot jtms-nodes = #f, init-keyword: #"jtms-nodes"; // list of all tms nodes. slot jtms-justs = #f, init-keyword: #"jtms-justs"; // list of all justifications slot jtms-debugging = #f, init-keyword: #"jtms-debugging"; // debugging flag slot jtms-contradictions = #f, init-keyword: #"jtms-contradictions"; // list of contradiction nodes. slot jtms-assumptions = #f, init-keyword: #"jtms-assumptions"; // list of assumption nodes. slot jtms-checking-contradictions = #t, init-keyword: #"jtms-checking-contradictions"; // For external systems slot jtms-node-string = #f, init-keyword: #"jtms-node-string"; slot jtms-contradiction-handler = #f, init-keyword: #"jtms-contradiction-handler"; slot jtms-enqueue-procedure = #f, init-keyword: #"jtms-enqueue-procedure"; end class ; define method print-jtms (jtms, stream, ignore) format(stream, "#", jtms.jtms-title); end method print-jtms; define class () slot tms-node-index = 0, init-keyword: #"tms-node-index"; slot tms-node-datum = #f, init-keyword: #"tms-node-datum"; // pointer to external problem solver slot tms-node-label = #"out", init-keyword: #"tms-node-label"; // :IN means believed, :OUT means disbelieved slot tms-node-support = #f, init-keyword: #"tms-node-support"; // Current justification or premise marker slot tms-node-justs = #f, init-keyword: #"tms-node-justs"; // Possible justifications slot tms-node-consequences = #f, init-keyword: #"tms-node-consequences"; // Justifications in which it is an antecedent slot tms-node-mark = #f, init-keyword: #"tms-node-mark"; // Marker for sweep algorithms slot tms-node-contradictory? = #f, init-keyword: #"tms-node-contradictory?"; // Flag marking it as contradictory slot tms-node-assumption? = #f, init-keyword: #"tms-node-assumption?"; // Flag marking it as an assumption. slot tms-node-in-rules = #f, init-keyword: #"tms-node-in-rules"; // Rules that should be triggered when node goes in slot tms-node-out-rules = #f, init-keyword: #"tms-node-out-rules"; // Rules that should be triggered when node goes out slot tms-node-jtms = #f, init-keyword: #"tms-node-jtms"; end class ; // The JTMS in which this node appears. define method print-tms-node (node, stream, ignore) format(stream, "#", node-string(node)); end method print-tms-node; define class () slot just-index = 0, init-keyword: #"just-index"; slot just-informant, init-keyword: #"just-informant"; slot just-consequence, init-keyword: #"just-consequence"; slot just-antecedents, init-keyword: #"just-antecedents"; end class ; define method print-just (just, stream, ignore) format(stream, "#", just.just-index); end method print-just; define method tms-node-premise? (node) (support := node.tms-node-support) & ~ (support == #"enabled-assumption") & empty?(support.just-antecedents); end method tms-node-premise?; // Simple utilities: define method node-string (node) (node.tms-node-jtms.jtms-node-string)(node); end method node-string; // LTD: No macros. #"debugging-jtms"; define method tms-error (string, node) error(string, node-string(node)); end method tms-error; define method default-node-string (n) format(#f, "%S", n.tms-node-datum); end method default-node-string; define method create-jtms (title, #key node-string = #"default-node-string", debugging, checking-contradictions = #t, contradiction-handler = #"ask-user-handler", enqueue-procedure) make-jtms(title: title, node-string: node-string, debugging: debugging, checking-contradictions: checking-contradictions, contradiction-handler: contradiction-handler, enqueue-procedure: enqueue-procedure); end method create-jtms; define method change-jtms (jtms, #key contradiction-handler, node-string, enqueue-procedure, debugging, checking-contradictions) if (node-string) jtms.jtms-node-string := node-string; end if; if (debugging) jtms.jtms-debugging := debugging; end if; if (checking-contradictions) jtms.jtms-checking-contradictions := checking-contradictions; end if; if (contradiction-handler) jtms.jtms-contradiction-handler := contradiction-handler; end if; if (enqueue-procedure) jtms.jtms-enqueue-procedure := enqueue-procedure; end if; end method change-jtms; // Basic inference-engine interface. define method in-node? (node) node.tms-node-label == #"in"; end method in-node?; define method out-node? (node) node.tms-node-label == #"out"; end method out-node?; define method tms-create-node (jtms, datum, #key assumptionp, contradictoryp) let node = make-tms-node(index: inc!(jtms.jtms-node-counter), datum: datum, assumption?: assumptionp, contradictory?: contradictoryp, jtms: jtms); if (assumptionp) push!(node, jtms.jtms-assumptions); end if; if (contradictoryp) push!(node, jtms.jtms-contradictions); end if; push!(node, jtms.jtms-nodes); node; end method tms-create-node; // Converts a regular node to an assumption and enables it. define method assume-node (node) if (~ node.tms-node-assumption?) debugging-jtms(jtms, "~%Converting ~A into an assumption", node); node.tms-node-assumption? := #t; end if; enable-assumption(node); end method assume-node; define method make-contradiction (node) if (~ node.tms-node-contradictory?) node.tms-node-contradictory? := #t; push!(node, jtms.jtms-contradictions); check-for-contradictions(jtms); end if; end method make-contradiction; define method justify-node (informant, consequence, antecedents) begin jtms := consequence.tms-node-jtms; just := make-just(index: inc!(jtms.jtms-just-counter), informant: informant, consequence: consequence, antecedents: antecedents); end; push!(just, consequence.tms-node-justs); for (node in antecedents) push!(just, node.tms-node-consequences); end for; push!(just, jtms.jtms-justs); debugging-jtms(jtms, "~%Justifying ~A by ~A using ~A.", consequence, informant, map(node-string, antecedents)); if (antecedents | out-node?(consequence)) if (check-justification(just)) install-support(consequence, just); end if; else consequence.tms-node-support := just; end if; check-for-contradictions(jtms); end method justify-node; // Support for adding justifications define method check-justification (just) out-node?(just.just-consequence) & justification-satisfied?(just); end method check-justification; define method justification-satisfied? (just) every?(in-node?, just.just-antecedents); end method justification-satisfied?; define method install-support (conseq, just) make-node-in(conseq, just); propagate-inness(conseq); end method install-support; define method propagate-inness (node) for (until empty?(node := pop!(q))) debugging-jtms(jtms, "~% Propagating belief in ~A.", node); for (justification in node.tms-node-consequences) if (check-justification(justification)) make-node-in(justification.just-consequence, justification); push!(justification.just-consequence, q); end if; end for; end for; end method propagate-inness; define method make-node-in (conseq, reason) begin jtms := conseq.tms-node-jtms; enqueuef := jtms.jtms-enqueue-procedure; end; debugging-jtms(jtms, "~% Making ~A in via ~A.", conseq, if (instance?(reason, )) reason; else pair(reason.just-informant, map(jtms.jtms-node-string, reason.just-antecedents)); end if); conseq.tms-node-label := #"in"; conseq.tms-node-support := reason; if (enqueuef) for (in-rule in conseq.tms-node-in-rules) enqueuef(in-rule); end for; conseq.tms-node-in-rules := #f; end if; end method make-node-in; // Assumption Manipulation define method retract-assumption (node) if (node.tms-node-support == #"enabled-assumption") jtms := node.tms-node-jtms; debugging-jtms(jtms, "~% Retracting assumption ~A.", node); make-node-out(node); find-alternative-support(jtms, pair(node, propagate-outness(node, jtms))); end if; end method retract-assumption; define method enable-assumption (node) if (~ node.tms-node-assumption?) tms-error("Can't enable the non-assumption ~A", node); end if; debugging-jtms(jtms, "~% Enabling assumption ~A.", node); let _that = #f; if (out-node?(node)) make-node-in(node, #"enabled-assumption"); propagate-inness(node); elseif (_that := node.tms-node-support == #"enabled-assumption" | empty?(node.tms-node-support.just-antecedents)) _that; else node.tms-node-support := #"enabled-assumption"; end if; check-for-contradictions(jtms); end method enable-assumption; define method make-node-out (node) begin jtms := node.tms-node-jtms; enqueuef := jtms.jtms-enqueue-procedure; end; debugging-jtms(jtms, "~% retracting belief in ~a.", node); node.tms-node-support := #f; node.tms-node-label := #"out"; if (enqueuef) for (out-rule in node.tms-node-out-rules) enqueuef(out-rule); end for; end if; node.tms-node-out-rules := #f; end method make-node-out; define method propagate-outness (node, jtms) debugging-jtms(jtms, "~% Propagating disbelief in ~A.", node); for (js = node.tms-node-consequences then append(cdr(js), new), new = nil then nil, conseq = nil then nil, until empty?(js)) // For each justification using the node, check to see if // it supports some other node. If so, forget that node, // queue up the node to look for other support, and recurse conseq := just-consequence(head(js)); if (conseq.tms-node-support == head(js)) make-node-out(conseq); push!(conseq, out-queue); new := conseq.tms-node-consequences; end if; finally out-queue; end for; end method propagate-outness; define method find-alternative-support (jtms, out-queue) debugging-jtms(jtms, "~% Looking for alternative supports."); for (node in out-queue) if (~ in-node?(node)) block (return) for (just in node.tms-node-justs) if (check-justification(just)) install-support(just.just-consequence, just); return(just); end if; end for; end block; end if; end for; end method find-alternative-support; // Contradiction handling interface define method check-for-contradictions (jtms) if (jtms.jtms-checking-contradictions) for (cnode in jtms.jtms-contradictions) if (in-node?(cnode)) push!(cnode, contradictions); end if; end for; if (contradictions) (jtms.jtms-contradiction-handler)(jtms, contradictions); end if; end if; end method check-for-contradictions; // LTD: No macros. #"without-contradiction-check"; // LTD: No macros. #"with-contradiction-check"; define method contradiction-check (jtms, flag, body) let jtmsv = generate-symbol(); let old-value = generate-symbol(); list(#"let*", list(list(jtmsv, jtms), list(old-value, list(#"jtms-checking-contradictions", jtmsv))), list(#"unwind-protect", apply(list, #"progn", list(#"setf", list(#"jtms-checking-contradictions", jtmsv), flag), body), list(#"setf", list(#"jtms-checking-contradictions", jtmsv), old-value))); end method contradiction-check; // LTD: No macros. #"with-contradiction-handler"; define method default-assumptions (jtms) with-contradiction-check(jtms, with-contradiction-handler(jtms, method (#rest ignore) contradiction(#t); end method, for (assumption in jtms .jtms-assumptions) let _that = #f; if (_that := assumption .tms-node-support == #"enabled-assumption") _that; elseif (_that := ~ (#"default" == assumption .tms-node-assumption?)) _that; elseif (block (contradiction) enable-assumption(assumption); end block) retract-assumption(assumption); end if; end for)); end method default-assumptions; // Well-founded support inqueries define method supporting-justification-for-node (node) node.tms-node-support; end method supporting-justification-for-node; define method assumptions-of-node (node) for (nodes = list(node) then append(cdr(nodes), new), new = nil then nil, until empty?(nodes)) let node = head(nodes); let _that = #f; if (_that := node.tms-node-mark == marker) _that; elseif (node.tms-node-support == #"enabled-assumption") push!(node, assumptions); elseif (in-node?(node)) new := node.tms-node-support.just-antecedents; end if; node.tms-node-mark := marker; finally assumptions; end for; end method assumptions-of-node; define method enabled-assumptions (jtms) for (assumption in jtms.jtms-assumptions) if (assumption.tms-node-support == #"enabled-assumption") push!(assumption, result); end if; finally result; end for; end method enabled-assumptions; // Inference engine stub to allow this JTMS to be used stand alone define method why-node (node) justification := node.tms-node-support; if (justification == #"enabled-assumption") format-out("\n%S is an enabled assumption", node-string(node)); elseif (justification) format-out("\n%S is IN via %S on", node-string(node), justification.just-informant); for (anode in justification.just-antecedents) format-out("\n %S", node-string(anode)); end for; else format-out("\n%S is OUT.", node-string(node)); end if; node; end method why-node; define method why-nodes (jtms) for (node in jtms.jtms-nodes) why-node(node); end for; end method why-nodes; #f; define method ask-user-handler (jtms, contradictions) handle-one-contradiction(head(contradictions)); check-for-contradictions(jtms); end method ask-user-handler; define method handle-one-contradiction (contra-node) *contra-assumptions* := assumptions-of-node(contra-node); if (~ *contra-assumptions*) tms-error("~%There is a flaw in the universe...~A", contra-node); end if; format-out("\nContradiction found: %S", node-string(contra-node)); print-contra-list(*contra-assumptions*); format-out("\nCall (TMS-ANSWER ) to retract assumption."); the-answer := block (tms-contradiction-handler) break("JTMS contradiction break"); end block; if (instance?(the-answer, ) & the-answer > 0 & ~ (the-answer > size(*contra-assumptions*))) retract-assumption(*contra-assumptions*[the-answer - 1]); end if; end method handle-one-contradiction; define method print-contra-list (nodes) for (counter = 1 then 1+(counter), nn = nodes then cdr(nn), until empty?(nn)) format-out("\n%S %S", counter, node-string(head(nn))); end for; end method print-contra-list; define method tms-answer (num) if (instance?(num, )) if (num > 0) if (~ (num > size(*contra-assumptions*))) tms-contradiction-handler(num); else format-out("\nIgnoring answer, too big."); end if; else format-out("\nIgnoring answer, too small"); end if; else format-out("\nIgnoring answer, must be an integer."); end if; end method tms-answer; define method explore-network (node) block (return-from-explore-network) if (~ in-node?(node)) format-out("\n Sorry, %S not believed.", node-string(node)); return-from-explore-network(node); end if; for (stack = nil then nil, current = node then node, options = nil then nil, olen = 0 then 0, done? = nil then nil, until done?) why-node(current); options := if (instance?(current.tms-node-support, )) current.tms-node-support.just-antecedents; end if; olen := size(options); for (good? = nil then nil, choice = 0 then 0, until good?) format-out("\n>>>"); choice := // LTD: Function READ not yet implemented. read(); if (choice == #"q" | (instance?(choice, ) & ~ (choice > olen) & ~ (choice < 0))) good? := choice; else format-out("\n Must be q or an integer from 0 to %D.", olen); end if; finally select (good?) #"q" => return-from-explore-network(current); 0 => if (stack) current := pop!(stack); else return-from-explore-network(current); end if; otherwise => push!(current, stack); current := options[good? - 1]; end select; end for; finally current; end for; end block; end method explore-network;