PLOGTEACH SIMPLEPSYS                                    Tom Khabaza
                                                        3rd March 1988

This teach file gives the code for a very early version of the Plogpsys
production system interpreter - this version of the code will be simpler
and easier to understand than the current, maore complex version.

See HELP * PLOGPSYS for a complete description of the current version.
See PLOGSHOWLIB * PLOGPSYS for the current version of the code.


         CONTENTS - (Use <ENTER> g to access required sections)

 -- An old version of Plogpsys
 -- Operator definitions
 -- Basic run/0 predicate
 -- Running the system with a status
 -- The "recognise-act" cycle
 -- Finding the applicable rules
 -- Checking the conditions of a rule
 -- Conflict resolution
 -- Applying a rule
 -- Utilities
 -- See also


-- An old version of Plogpsys -----------------------------------------

The code in this file is that of version 3.  This version lacked many of
the features of later versions, including:

1.  Backtracking, and the 'fail' action;
2.  The 'prolog_goal' action.
3.  Pseudo-facts and system pseudo-facts (and therefore built-in
    condition types such as '='), and all action types related to them.
    However, pseudo-facts could be simulated using non-unit clauses for
    fact/1.
4.  Removal conditions.
5.  Tracing of any kind.

This version thus has only five action types - 'add', 'remove', 'input',
'print' and 'stop'.


-- Operator definitions -----------------------------------------------

The following operator definitions make it possible to express
production rules in a "nice" format - for example "a => add b." instead
of "=>(a,add(b)).".  See PLOGHELP * OP.

    ;;; Infix operator for rules
    :- op(255,xfy,=>).

    ;;; Prefix operators for actions
    :- op(200, fx, add).
    :- op(200, fx, remove).
    :- op(200, fx, print).
    :- op(200, fx, input).


-- Basic run/0 predicate ----------------------------------------------

The basic 'run' predicate simply runs the system with status 'go', and
then prints the contents of the production system database (i.e. all the
clauses for fact/1).  See PLOGHELP * LISTING.

    run :- run(go), showdatabase.

    showdatabase :-
        nl, nl, write('Production system database:'), nl, nl,
        listing(fact).


-- Running the system with a status -----------------------------------

The predicate run/1 forms the main recursive loop.  Each time it is
called, it is supplied with a "status", usually produced by the last
cycle round the loop.  This may be 'stop', indicating that the 'stop'
action has been executed, 'fail' indicating that no applicable rules
were found, or any other (e.g. 'go') indicating that another cycle
should be performed.  On a 'stop' or 'fail' status, the system prints a
message and stops.  On any other status, it performs a cycle and calls
itself recursively with the result of the cycle as the new status.

    run(stop) :- nl, write('Stopping - stop action executed'), nl, !.

    run(fail) :- nl, write('Stopping - no rules applicable'), nl, !.

    run(_) :-
        cycle(NewStatus),
        run(NewStatus).


-- The "recognise-act" cycle ------------------------------------------

The predicate cycle/1 performs the main "recognise-act" part of the main
loop.  It reads as follows:

    A cycle produces the status Status if:
        Rules is the list of rules currently applicable,
        and Rule is the rule from Rules selected to be run,
        and applying Rule gives status Status.

    A cycle produces the status 'fail' if no rules are applicable.


    cycle(Status) :-
        applicable(Rules), !,
        select(Rules, Rule),
        apply(Rule, Status).

    cycle(fail).


Note that conflict resolutions therefore takes place inside the
predicate select/2.


-- Finding the applicable rules ---------------------------------------

A rule is applicable if all its conditions are true.  The predicate
applicable/1 finds all such rules using the built-in predicate
fast_bagof/3 - see PLOGHELP * FAST_BAGOF - which is similar to
"findall".

    applicable(Rules) :-
        fast_bagof(H => B, ((H => B), alltrue(H)), Rules).

Note that fast_bagof/3 produces the list of rules in reverse order to
the solutions produced by the goal.  This is quite different from later
versions of Plogpsys, where a user-level implementation of findall/3 is
used (see PLOGHELP * FINDALL).


-- Checking the conditions of a rule ----------------------------------

A condition is either a simple Prolog term, or a negative condition of
the form "not( <cond> )", or a conjunction of conditions separated by
commas - that is a "comma structure".  The predicate alltrue/1 checks
that a condition or comma structure of conditions is currently true.  It
reads as follows:

    A comma structure of conditions (A,B) is true if:
        A is true and B is true.

    A negated comma structure of conditions not((A,B)) is true if:
        it is not the case that A and B are both true.

    A negated single condition not(A) is true if A is not true.

    A simple condition A is true if:
        there is no fact that denies A,
        and there is a fact that asserts A.

Note the double brackets used for negated comma structures, to avoid the
syntactic confusion of not/1 with not/2.

    alltrue((A,B)) :- !,
        alltrue(A), alltrue(B).

    alltrue(not((A,B))) :- !, not((alltrue(A),alltrue(B))).

    alltrue(not(A)) :- not(fact(A)), !.

    alltrue(A) :- not(fact(not(A))), fact(A).


-- Conflict resolution ------------------------------------------------

The predicate select/2 is the site of conflict resolution in this
interpreter.  "select(L,R)" reads "R is a rule selected from the list of
instantiated applicable rules L".  This version gives more or less the
simplest possible strategy - simply select a member of the list.
Because of the behaviour of member/2, this will select the first member,
and later members only on backtracking.

    select(Rules, Rule) :- member(Rule, Rules).

However, because of the behaviour of fast_bagof/3, noted above, the
order of the list of applicable rules will be the reverse of the order
in which the rules were loaded.  Thus, the default conflict resolution
in early versions of Plogpsys is less "intuitive" than in later versions
where findall/3 is used.

The conflict resolution strategy can be customised by the user by
providing a new version of select/2.  This is the same in all versions
of Plogpsys.


-- Applying a rule ----------------------------------------------------

Applying a rule produces the new "status" for the next cycle of the
interpreter.  This will remain unchanged unless the 'stop' action is
executed.  "apply(R,S)" reads "applying the rule R produces the status
S".  "apply_body(S1,R,S2)" reads "applying the rule R in the context of
the status S1 produces the status S2".  The input status for
apply_body/3 is provided so that if the status has been changed to
'stop', 'apply_body' will ignore any further actions.


    apply(H => B, Status) :- apply_body(go, B, Status).


    ;;; Ignore further actions on stop status
    apply_body(stop, _, stop) :- !.

    ;;; Conjunction of actions
    apply_body(go, (A,B), Stat2) :-
        apply_body(go, A, Stat1),
        apply_body(Stat1, B, Stat2).

    ;;; Ignore add if fact is already in database
    apply_body(go, add X, go) :- fact(X), !.
    apply_body(go, add X, go) :- assert(fact(X)), !.

    ;;; Ignore remove if fact is not in database
    apply_body(go, remove X, go) :- not(fact(X)), !.
    apply_body(go, remove X, go) :- retract(fact(X)), !.

    apply_body(go, print X, go) :- write(X), nl, !.

    apply_body(go, input X, go) :- readline(X), !.

    ;;; Stop action - just change status to stop.
    apply_body(go, stop, stop) :- !.


-- Utilities ----------------------------------------------------------

The code above requires a couple of utility predicates: readline/1 and
member/2.  member/2 is the classic Prolog version.  readline/1 is
analogous to POP-11's 'readline' procedure, and works by calling
POP-11's version.

    readline(X) :- prolog_eval(apply(valof(readline)), X).

    member(X, [X|_]).
    member(X, [_|T]) :- member(X, T).


-- See also -----------------------------------------------------------

PLOGTEACH * PLOGPSYS    Tutorial introduction to Plogpsys.

PLOGTEACH * BLOCKPSYS   Exercise using Plogpsys.

PLOGHELP * PLOGPSYS     Detailed explanation of Plogpsys facilities.

TEACH * PSYS1           Theoretical introduction to production systems
                        in AI and Cognitive Science.

TEACH * PRODSYS         Introduction to LIB PRODSYS, a POP-11 based
                        production system interpreter.

TEACH * EXPERTS         An overview of some POP-11 based expert system
                        shells.
