O'KeefeHPS@sri-unix.UUCP (09/15/84)
From: O'Keefe HPS (on ERCC DEC-10) I've always been quite impressed by the "EXPERT" stuff being done at Rutgers, and when I read Kastner's thesis [1] I decided to write an [1] Kastner, J.K. @i"Strategies for Expert Consultation in Therapy Planning." Technical Report CMB-TR-135, Department of Computer Science, Rutgers University, October 1983. (PhD thesis) interpreter for his rules in Prolog as an exercise. The first version just came up with the answer, that's the stuff that's commented out below. The second version left behind information for "explanations": chosen(Answer, Reason, WouldHaveBeenPreferred) Answer was the answer, Reason was the text the rule writer gave to explain his default ordering of the treatments, and WouldHaveBeenPreferred are the treatments we'd have preferred in this ordering if they hadn't been contraindicated despite(Answer, Contraindications) means that Answer was contraindicated by each of the problems listed, but it was still picked because the preferred choices had worse problems. rejected(Treatment, Contraindications) means that Treatment was rejected because it had the problems listed. Every treatment will be rejected or chosen. Note: in these two facts the Contraindications are those which were checked and found to be applicable, less severe ones may not have been checked. (This is a feature, the whole point of the code in fact.) You'll have to read Kastner's thesis to see how these rules are used, but if you're interested in Expert Systems you'll want to read it. Why have I sent this to the Digest? Two reasons. (1) someone may have a use for it, and if I send it to the library it'll sink without trace. (2) I'm quite pleased with the "no-explanations" version, but the "explanations" version is a bit of a mess, and if anyone can find a cleaner way of doing it I'd be very pleased to see it. I guess I still don't know how best to do data base hacking. A point which may be interesting: I originally had worst/6 binding its second argument to 'none' where there were no new contraindications. The mess which resulted (though it worked) reminded me of a lesson I thought I'd learned before: it is dangerous to have an answer saying there are no answers, because that looks like an answer. All the problems I had with this code came from thinking procedurally. :- op(900, fx, 'Is '). :- op(899, xf, ' true'). :- compile([ 'util:ask.pl', % for yesno/1 'util:projec.pl', % for project/3 'prefer.pl' % which follows ]). % File : PREFER.PL % Author : R.A.O'Keefe % Updated: 14 September 1984 % Purpose: Interpret Kastner's "preference rules" in Prolog :- public go/0, og/0. :- mode prefer(-, +, +, +), pass(+, +, +, -), pass(+, +, +, +, +, +, -), worst(+, -, +, +, -, -), chose(+, +, +), forget(+, +), compare_lengths(+, +, -), evaluate(+). prefer(Treatment, Rationale, Contraindications, Columns) :- pass(Columns, [], Contraindications, Treatment), append(Pref1, [Treatment=_|_], Columns), !, project(Pref1, 1, Preferred), assert(chosen(Treatment, Rationale, Preferred)). pass([Tr=Tests|U], Cu, Vu, T) :- worst(Tests, Rest, Cu, Vu, Cb, Vb), !, pass(U, [Tr=Rest], Cu, Vu, Cb, Vb, T). pass([T=_|U], C, _, T) :- chose(T, U, C). pass([], [T=_], _, _, C, _, T) :- !, chose(T, [], C). pass([], B, _, _, Cb, Vb, T) :- reverse(B, R), pass(R, Cb, Vb, T). pass([Tr=Tests|U], B, Cu, Vu, Cb, Vb, T) :- worst(Tests, Rest, Cu, Vu, Ct, Vt), compare_lengths(Vt, Vb, R), ( R = (<), C1 = Ct, V1 = Vt, B1 = [Tr=Rest], forget(B, Cb) ; R = (=), C1 = Cb, V1 = Vb, B1 = [Tr=Rest|B] ; R = (>), C1 = Cb, V1 = Vb, B1 = B, assert(rejected(Tr,Ct)) ), !, % moved down from worst/6 for "efficiency" pass(U, B1, Cu, Vu, C1, V1, T). pass([T=_|_], B, _, _, C, _, T) :- chose(T, B, C). worst([Test|Tests], Tests, C, [X|V], [X|C], V) :- evaluate(Test), !. worst([_|Tests], Rest, Cu, [_|Vu], Ct, Vt) :- worst(Tests, Rest, Cu, Vu, Ct, Vt). evaluate(fail) :- !, fail. evaluate(Query) :- known(Query, Value), !, Value = yes. evaluate(Query) :- yesno('Is ' Query ' true'), !, assert(known(Query, yes)). evaluate(Query) :- assert(known(Query, no)), fail. chose(Treatment, Rejected, Contraindications) :- assert(despite(Treatment, Contraindications)), forget(Rejected, Contraindications). forget([], _). forget([Treatment=_|Rejected], Contraindications) :- assert(rejected(Treatment, Contraindications)), forget(Rejected, Contraindications). compare_lengths([], [], =). compare_lengths([], _, <). compare_lengths( _, [], >). compare_lengths([_|List1], [_|List2], R) :- compare_lengths(List1, List2, R). /*------------------------------------------------------------ % Version that doesn't store explanation information: prefer(Treatment, Rationale, Contraindications, Columns) :- pass(Columns, 0, [], Treatment). pass([], _, [T=_], T) :- !. pass([], _, B, T) :- reverse(B, R), pass(R, 0, [], T). pass([Tr=Col|U], I, B, T) :- worst(Col, 1, W, Reduced), !, ( W > I, pass(U, W, [Tr=Reduced], T) ; W < I, pass(U, I, B, T) ; W = I, pass(U, I, [Tr=Reduced|B], T) ). pass([T=_|_], _, _, T). % no (more) contraindications worst([], _, none, []). worst([Condition|Rest], Depth, Depth, Rest) :- evaluate(Condition), !. worst([_|Col], D, W, Residue) :- E is D+1, worst(Col, E, W, Residue).