Andes Script is designed to produce a data structure that represents all possible solutions to a given problem. The data structure is called a solution graph, and its central component is a particular kind of constraint network [Forbus & de Kleer, 19??, pg. ??] called a bubble graph. The bubble graph consists of nodes that represent equations and nodes that represent quantities. If a quantity appears in an equation, then the two nodes are interlinked. For instance, the simple problem "If what is the mass of a 50 kg block on the moon, where the acceleration due to gravity is 2.78 m/s^2?" has the following nodes in its bubble graph:
1. the mass of the block, m.The links in the graph connect the quantity nodes (1, 2 and 3) with the equation nodes (4, 5, and 6). In particular, there are links connecting the following pairs of nodes: (4, 1), (5,3), (6,1), (6,2), and (6,3). Constraint networks such as the bubble graph are a traditional data structure that simplifies a variety of algorithms for processing a system of equations.
2. the magnitude of the weight of the block, W.
3. the accelerational due to gravity on the moon, gm.
4. m=50 kg, the given mass of the block.
5. gm = 2.78 m/s^2, the given acceleration due to gravity on the moon
6. W=m*gm, the weight law, applied to this problem
Each equation in the solution of a problem must be either given in the problem statement or produced by applying a principle. To record the justification of equations, each node in the bubble graph has a problem solving method (PSM) graph that represents how the equation was derived. A solution graph consists of a bubble graph plus a PSM graph for each equation node.
Just as students must know both physics and algebra in order to solve physics problems, the Andes problem solver must have both kinds of knowledge as well. Its algebraic knowledge is "hard coded," that is, represented in Lisp and not easily changed. The physics knowledge is "soft coded," that is, represented in a specialized programming language called Andes Script that is designed to make it easy to express such knowledge.
The algebraic code is responsible for constructing the bubble graph, but since it doesn't know anything about which equations are justifiable in this task domain, it calls the physics code to generate equations for it. In particular, it calls the physics code with the goal, "generate equations that contains <quantity>" where <quantity> is some specific quantity. For instance, when solving the sled problem, the first call that is made is "generate equations that contain the magnitude of the weight of the sled." The Andes Script code returns not just one equation, but all equations it can generate that contain the sought quantity. As the Andes Script code is executed, the interpreter keeps track of its activity and builds a PSM graph to record it. Thus, what the algebra code actually receives is a set of equation nodes, each containing an equation and a PSM graph representing its justification.
The old Andes problem solver represented its knowledge as if-then rules called "productions", and it used forward chaining to reason. This meant that goals had to be represented explicitly in the conditions and conclusions of the rules, and the working memory contained propositions representing the goals. Because the rules could manipulate goals in any fashion they wanted, it was possible to create non-hierarchical goal structures in the solution graphs. In contrast, Andes Script represents its knowledge as if-then rules called Strips operators [Russell & Norvig, 19??, chapter ??] and it uses backwards chaining to reason. Goals are not explicitly represented in Strips operators, nor do they appear in working memory. Instead, the interpreter maintains a goal stack. This means that no matter what code the author writes, the resulting PSM is guaranteed to have a hierarchical goal structure.
Because the operators do not need to manipulate goals, the overall code is somewhat simpler. This is best appreciated with an illustration, which also helps introduce the coding style. Consider what the student must do to write a simple scalar equations, such as <speed>=<distance>/<duration>. The student must define 3 variables then write the equation. One way to express this reasoning using production rules is to write:
Although Prolog is the most widely used commercial backchainer, but we prefer our own version so that we can include some special features. In particular, an Andes Script operator can have more than just a slot for its conditions and a slot for its conclusions. It can also have a slot for a hint sequence. When the help system decides that the student show apply a particular operator, it give the first hint in its hint sequence. If the student presses the "explain more" button, it will present the next hint in the sequence, and so on. Thus, the author controls not only what the operators by writing their conditions and conclusions, but how the help system hints about them to the student.
Andes operators also provide a "features" slot, which contains a set of atomic features. One feature that is particularly important is the "unordered" feature. When an operator has this feature, then it doesn't matter what order the operators conditions are achieved in. For instance, operator 1 above should have the "unordered" feature because it doesn't matter which order the student defines variables in. However, for many operators, order does matter. For instance, the general procedure for writing a vector equation has the following conditions, which the student achieve in the given order:
This illustrates one of the design objectives for Andes Script. All knowledge about physics should be expressed in Andes Script. It should not be necessary to modify the Andes interpreter nor the help system in order to extend the task domain or to replace physics knowledge with other domain knowledge. Part of the knowledge of physics is what steps to do in which order, so that ordering knowledge has to be encoded in Andes Script. Similarly, the hints depend on the knowledge, so they must be represented in Andes Script.
In order to construct hints and other messages to the student, the help system must refer to quantities and other entities. Inside the operators, quantities and other entities are represented formally, as logical terms. For instance, the mass of the Nimitz is represented as (mass Nimitz) and the magnitude of the weight force on it due to the earth is represented as (mag (force Nimitz earth weight)). Clearly, we do not want to print such expressions in our messages to the student, but converting them into text requires knowledge that is peculiar to physics. Thus, it must be represented in Andes Script. Thus, Andes Script has a construction (defexpr??) that indicates how to translate a logical term into English. It also has a construction (defqexpr) for defining units and constructions for defining other miscelaneous information. These extra pieces of physics knowledge are called the "ontology."
In addition to operators and defexprs, Andes Script allows authors to define problems. Thus, the main constructions that the knowledge engineer must write are:
Andes Script is based on several Lisp macros, named defoperator, defproblem, etc. The files they appear in are regular Lisp code files, which are loaded by the usual Lisp code loading facilities. In particular, if em1.cl is the name of your Andes Script file, then saying (load "em1") to the top level of Lisp will cause the file to be loaded, which will cause the defoperator, defproblem and defexpr macros to be run, thus causing operators, problems and expressions to be defined. Like any Lisp file, single line comments are indicated by a semi-colon--the text from there to the end of the line is ignored by the loader. Multi-line comments begin with #| and end with |#. Emacs, VI and other editors have modes that know about Lisp syntax. They will balance parentheses for you and color-code the text (e.g., comments are in red, executable code is in black, etc.).
Although one could easily mix defoperators, defproblems and other Andes
Script macros in the same file, and one could also define regular Lisp
functions in the same file, it is convenient to put all the defoperators
in one file, all the defproblems in a second, and all the ontological macros
in a third file. Internally, the macros simply define a Lisp struct
(a record instance; like an object with no methods) and add it to a list.
If you execute the same defoperator macro twice, then two copies of the
same operator will end up in the list. Thus, one should put (clear-ops)
at the begining of the file that contains the defoperators. This
Lisp function just sets the list of operators to NIL. Similarly,
the file with the problems in it should have (clear-problem-registry) at
its beginning and the file with the ontological definitions in it should
have (clear-ontology) at its front.
An atomic statement consists of a predicate followed by its arguments. For instance, (massless sled) is a statement that the sled is massless. "Massless" is the predicate, and "sled" is a constant that denotes a particular sled. Predicate names and constants are just Lisp atoms.
The arguments of a predicate can also be a variable. Unfortunately, Lisp also uses the term "variable" to mean something different, so we use "unification variable" to mean the kinds of variables that appear in logical statements. Unification variables always begin with a question mark. For instance, the proposition (tied-to ?string ?body) could be used as a goal that means "find out if something is tied to something."
The arguments of a predicate can also be a function, where a function consists of a function name and its arguments. For instance, the proposition (given (duration (during 1 2)) (dnum 13656 |s|)) says that the duration from time 1 to time 2 is 13656 seconds. The function (during 1 2) denotes a time interval. The function (duration (during 1 2)) denotes a quantity, namely the duration of that time interval. The function (dnum 13656 |s|) denotes a dimensional number. 13656 is a constant that denotes a number. |s| is a constant that denotes a unit, namely seconds. The vertical bars here are necessary to make this atom be spelled in lower case. Lisp automatically puts atoms that are not surrounded by vertical bars into upper case.
There is much more to first order logic, but it isn't used in Andes. In particular, the knowledge base does not use quantifiers, conjunctions, disjunctions, negations, implications, etc.
(defproblem <name>Here is an example of a problem definition
:statement <list of strings>
:features <list of Lisp forms>
:givens <list of propositions>
:soughts <list of propositions>)
(defproblem Exkt1aThe <name> is just a Lisp atom that names the problem. The rest of the macro is a set of fields, each preceeded by a label (which always starts with colon). Because the fields are labelled, they may appear in any order.
:statement (
"The SR-71 strategic reconnaissance aircraft, the Blackbird, set a world speed"
"record by flying from London to Los Angeles (8.79x10^6 m) in 3 hours 47 minutes"
"and 36 seconds. Compute the average speed in m/s." )
:features (working clips kinematics)
:givens
((object aircraft)
(time (during 1 2))
(given (duration (during 1 2)) (dnum 13656 |s|))
(given (at (distance aircraft) (during 1 2)) (dnum 8790000 |m|)))
:soughts ( (answer (at (speed aircraft) (during 1 2))) ))
The statement field contains a list of strings. It should contain the exact wording form the Andes screen, but it doesn't matter how it is broken up into strings. Currently, the system pays no attention to the contents of this field, because the wording that is painted on the Andes screen comes from other files. However, that should eventually change, and the Workbench will display the contents of this field.
The features field contains a list of Lisp atoms. These are conveniences for the author. For instance, the author can run a tool that collects all problems that have the "working" feature and submits them to the problem solver. This allows an author to rapidly check that a change to the KB has not introduced any bugs into previously working problems.
The givens field is a list of propositions that represent the information given in the problem statement. The propositions are ground atomic statements in first order logic. That is, they have no variables, no quantifiers and no logical connectives (e.g., conjunction, disjunction or negation).
The soughts field is a list of propositions that need to be derived. In the current problems, this is just a list of the quantities sought by the problem, each one inclosed in an (answer …) predicate.
(defoperator <name> <arguments>For example, here is an operator that draws a weight vector:
:specifications <string>
:features <list of features>
:preconditions <list of conditions>
:effects <list of effects>
:hint <list of hints>)
(defoperator draw-weight (?b ?t ?planet)The <name> field is a Lisp atom, such as draw-weight. The <arguments> field is a list of unification variables, where a unification variable is a Lisp atom that starts with a question mark. Unification variables are used in many places in Andes Script. Unification is a kind of pattern matching. When a form containing a unification variable, such as (mass ?body), is unified (matched) to another form, such as (mass sled), the unification variables are bound to the corresponding pieces, so ?body is bound to sled.
:specifications "
If ?b is not massless, and
it is near a ?planet,
then draw a weight vector for it pointing straight down,
define a magnitude variable and an direction variable for it."
:preconditions
((force ?b ?planet weight ?t ?dir action)
(not (vector ?b (at (force ?b ?planet weight) ?t) ?dont-care))
(bind ?mag-var (format-sym "Fw_~A_~A_~A" (body-name ?b) ?planet
(time-abbrev ?t)))
(bind ?dir-var (format-sym "O~A" ?mag-var))
(debug "~&Drawing weight of ~a at ~a.~%" ?b ?t))
:effects
((vector ?b (at (force ?b ?planet weight) ?t) ?dir)
(variable ?mag-var (at (mag (force ?b ?planet weight)) ?t))
(variable ?dir-var (at (dir (force ?b ?planet weight)) ?t))
(given (at (dir (force ?b ?planet weight)) ?t) ?dir))
:features ()
:hint
((hint "Notice that ~a is near ~a." ?b ?planet)
(hint "When an object is near a planet, the planet exerts a
weight force on the object.")
(hint "Because ~a is near the planet ~a, the planet exerts a weight
force on it, so use the force drawing tool to draw a force on
~a due to ~a of type weight during time ~a." ?b ?planet ?b ?planet ?t)))
The arguments of an operator are used to prevent redundant computation. Whenever an operator is applied, its name and the bindings of its arguments are stored, and the interpreter will not apply an operator if it has already been applied with the same argument bindings. Thus, the author should include in the argument list enough variables to insure uniqueness. For instance, if ?t were left out the argument list of draw-weight, then the interpreter would refuse to draw the weight of the Nimitz (?b=Nimitz) due to the earth (?planet=earth) for time point 1 (?t=1) if it had already drawn the weight of the Nimitz due to earth for time point 2.
Although the <name> and <arguments> fields are required, the rest of the operator's fields are not. Thus, we need to label them in order to tell which ones are present and which have been left out. The labels for the fields begin with a colon. These fields can appear in any order, since the labels suffice to distinguish them.
The specifications field is a string used only for documentation. So far, we haven't had much use for it. It usually takes a couple of paragraphs to explain an operator, so authors have been putting those explanations in Lisp comments.
The preconditions field is an ordered list of conditions. A condition can be either an atomic statement or an executable. An executable is a piece of lisp code; the list of valid executables will be discussed later. A atomic statement is a first-order logic statement, written in Lisp syntax, with no quantifiers and no logical connectives. That is, it consists simply of a predicate and arguments, where each argument is a logical term. A logical term is either a unification variable (e.g., ?planet), a constant (e.g., earth) or a logical function. A logical function is a function name with a list of arguments. For instance, (force ?b ?planet weight) is a logical function, and (vector ?b (at (force ?b ?planet weight) ?t) ?dir) is an atomic statement. Logical functions and atomic statements look exactly the same, syntactically. However, they appear in different places. Atomic statements appear as conditions and effects. Functions appear in the argument positions of atomic statements or other functions. This is just the standard conventions of first order logic. When an atomic statement appears in the preconditions field of an operator, it is used both to unify with working memory and as a subgoal. This was illustrated earlier when the differences between productions and Stripts operators was discussed.
The effects field is an unordered list of atomic statements. These will eventually be added to working memory when the operator is finally executed.
The features field contains a list of Lisp forms. Currently, the only feature is unordered. When an operator has the unordered feature, it means that the preconditions of the operator can be achieved by the student in any order. The interpreter will continue to achieve them in order, so the unordered feature has no affect on the solution graph generator.
The hint field contains a list of hints to be given to the student. This field's contents are not yet used by the help system, so it won't be documented any further. We might end up having to change the syntax of hints as the implementation of the help system proceeds.
The fields of operators have been described, except that we delayed discussion of the executables that appear in the preconditions slot. They are needed in order to escape from the ordinary interpretation of operators. Thus, we need to describe more precisely what the interpreter does before describing the executables.
However, the bubble graph should receive not just one equation, but all possible equations that contain the sought quantity. This is achieved by making the interpreter non-deterministic. That is, the whole state of the interpreter is recorded in a data structure called the State. In particular, the State contains the working memory, the goal stack, and the bindings of any unification variables. The interpreter's has a workhorse function, called Successors, that inputs a State and outputs all possible successor states. If any of those States has an empty goal stack, then the State represents a solution to the problem so it is put aside. Otherwise, the remaining States are placed in a queue. The interpreter repeatedly pulls a State off the queue, calls Successors to generates all its successors, puts aside the completed states, and enqueues the rest. When the queue is finally empty, the interpreter has generated all possible solutions. It extracts the equations from them, and returns them to the bubble graph generator.
Successors inputs one State and outputs a possibly empty set of States. The basic steps are:
;;; =================== applying a scalar equation=================
;;; This operator is a problem solving method (PSM) that finds
a
;;; scalar equation containing the sought quantity then writes
the
;;; equation. Whenever an author wants to define a new scalar
;;; equation, such as speed=distance/duration, the author must
define
;;; operators for both the eqn-contains goal, which indicate which
;;; quantities are contained in the equation, and the eqn goal,
which
;;; represents writing the equation on the Andes user interface.
(defoperator apply-scalar-psm (?sought ?eqn-id)
:features (PSM)
:specifications "If the goal is to apply a psm to
find a quantity,
and there is a scalar equation
that contains that quantity,
then generate the equation."
:preconditions (
(eqn-contains ?eqn-id ?sought)
(not (eqn ?dont-care ?eqn-id))
(eqn ?eqn-algebra ?eqn-id)
)
:effects
((psm-applied ?sought ?eqn-id ?eqn-algebra)))
Successors subgoals on this operator and creates a new State with the following goal stack
(defoperator wt-law-contains (?b ?t ?planet)
:specifications "
If a body is near a planet,
then the weight law for the body potentially contains
the magnitude of the weight force,
the mass of the body, and
the gravitational constant for the planet."
:preconditions
((time ?t)
(near-planet ?planet)
(not (massless ?b)))
:effects
((eqn-contains (wt-law ?b ?t) (mass ?b))
(eqn-contains (wt-law ?b ?t) (at (mag (force ?b ?planet
weight)) ?t))
(eqn-contains (wt-law ?b ?t) (gravitational-acceleration
?planet)))
:hint
((hint "The weight law could potentially contain the sought
quantity.")))
The first effect unifies with the goal, and binds ?eqn-id to (wt-law sled ?t) as a side effect. Successors creates a new State and puts this operator's conditions, etc. onto its goal stack, which contains:
(defoperator mass-compound-contains (?b-sought ?bodies)
:preconditions (
; compound body must exist
(object (compound . ?bodies))
; applies if sought is mass of compound or one of
its parts
(test (or (member ?b-sought ?bodies)
(equal ?b-sought `(compound ,@?bodies))))
)
:effects (
(eqn-contains (mass-compound ?bodies) (mass
?b-sought))
))
Subgoaling on this operator produces a state that has the following goal stack:
All these States are placed in the queue. Each will be followed out to see if eventually it eventually leads to an equation. Let's first consider the fate of the State for the weight law. The top item on its goal stack is (time ?t). At step 4 of Successors, we find that (time ?t) unifies with the proposition (time (during 1 2)) in working memory. A new State is created with ?t bound to (during 1 2) and the following goal stack:
(defoperator wt-law (?b ?t)
:features (unordered)
:specifications "
If a body is near a planet,
and it is not massless,
and you can find the appropriate variables,
then write W=m*g where W is the magnitude of the weight
force
on the body, m is the body's mass and
g is the gravitational
acceleration of the planet."
:preconditions
((near-planet ?planet)
(not (massless ?b))
(variable ?m-var (mass ?b))
(variable ?w-var (at (mag (force ?b ?planet
weight)) ?t))
(variable ?g-var (gravitational-acceleration
?planet))
)
:effects
((eqn (= ?w-var (* ?m-var ?g-var)) (wt-law ?b ?t)))
:hint
((hint "You should try applying the weight law.")
(hint "The weight law for a body is W=m*g, where
W is the magnitude of the
weight
force acting on the body, m is the body's mass and g is the
gravitational
acceleration of earth or whatever planet is nearby.")
(hint "Write ~a=~a*~a" ?w-var ?m-var ?g-var)))
In order to satisfy the third and fourth preconditions, operators are applied that draw the body and the weight. Another operator is applied to represent the fact that "g" is predefined as the variable for gravitational acceleration. When all 3 variables are defined, the wt-law operator is executed and writes an equation into working memory. This allows apply-scalar-psm to execute, thus creating a State with an empty goal stack. The interpreter puts this State aside as one solution to the problem. It dequeues the State generated by subgoaling on the mass-compound-contains operator. The State has this goal stack:
(bind <unification variable> <Lisp form>) This causes the <Lisp form> to be executed. The value it returns is bound to the <unification variable>. If the <unification variable> is already bound, then its binding must equalp the value returned by Lisp. That is, this executable is like an assignment statement in a programming language. For instance, (bind ?dir-var (format-sym "O~A" ?mag-var)) calls the Lisp function format-sym and passes in both the string "O~A" and the value of a unification variable (which that happens to be the variable for the magnitude of a vector). The format-sym function, which acts like a Lisp format statement except that it creates symbols instead of printing to an output stream, returns a symbol that has "O" on the front of the magnitude variable's symbol. The unification variable ?dir-var is bound to this newly created symbol.
(test <Lisp form>) This causes the <Lisp form> to be executed. If it returns NIL, the precondition fails. If it returns anything else, the precondition succeeds. For instance, (test (non-zero-projectionp ?rot ?dir)) calls a Lisp function that checks whether projecting a vector with direction ?dir onto an axis with rotation ?rot will result in a non-zero component.
(not <atomic statement>) If any proposition in working memory unifies with the given atomic statement, this precondition fails. It succeeds only if there is no proposition in working memory that so unifies with the given statement. For instance, (not (vector ?b (at (force ?b ?planet weight) ?t) ?dont-care)) succeeds only if there is no weight vector in working memory for the given body, planet and time.
(not <atomic statement> <Lisp form>) This fails there is a proposition in working memory that unifies with the given atomic statement and the Lisp form returns non-NIL when executed. For instance, (not (axis-for ?sys ?t ?dontcare1 ?dontcare2) (part-of-sys ?b ?sys)) fails if an axis has been drawn for a system ?sys and the body ?b is a part of ?sys. Part-of-sys is a Lisp function.
(in-wm <atomic statement>) For each proposition in working memory that unifies with the given atomic statement, this precondition succeeds and Successors returns a State. Moreover, the bindings made during unification are kept in that State. This is just like putting the atomic statement into the precondition except that Successors will not try to unify it with the effects of operators. That is, it will match the statement to working memory but it will not try to find an operator that will achieve it. If there are no propositions in working memory that unify with the atomic statement, then Successors returns no States. That is, the precondition fails.
(count <unification variable> <atomic statement>) This counts the number of propositions in working memory that unify the with the given atomic statement, and binds the given unification variable to that integer.
(add-to-wm <atomic statement>) This just adds the given <atomic statement> to working memory, after first substituting bindings for any variables that occur in the statement. Thus, if ?b is sled, then (add-to-wm (teleport ?b)) would add (teleport sled) to working memory.
(any-member <unification variable> <list of terms>) After values are substituted for any unification variables, <set of terms> must be a possibly empty list (it is an error if it does not). For each unification of the unification variable with a member of this list, a new state is created and returned by Successors. For instance, (any-member ?x-rotation ?min-dirs) is used inside the axis rotation operator draw-rotate-axes. The variable ?min-dirs is bound to a list of directions. This precondition causes Successors to returns one state for each direction, with the variable ?x-rotation is bound to the selected direction.
(debug <string> . <list of terms>) This is like a Lisp format statement. The string is printed except that special format characters, such as ~a, are replaced by values from the list of terms. For instance, (debug "angle between ~A and ~A = ~A~%" ?v1-var ?v2-var ?angle) prints out the angle between two quantities. The ~% causes a line break to be printed. This printing can be suppressed by setting the global Lisp variable *debug* to NIL.
(setof <goal> <term> <unification variables>)
This is best explained with an example:
(setof (vector ?b (at (force ?b ?agent
?type) ?dir)
(force ?b ?agent ?type)
?forces)
This executable starts by finding all possible derivations of <goal>,
namely (vector ?b ...?dir). That is, Successors is called repeatedly
with the <goal> on the top of the stack until no new States are produced.
In this case, that derives all forces acting on the given body ?b, because
the other variables are unbound at the time this precondition is executed.
Once all the States have been collected, the <goal> is unified with
either of their working memories in order to get bindings for its variables.
These bindings are applied to the <term>, creating a different term
for each state. That set of terms is bound to the given <unification
variable> For instance, if since there are two forces on the sled,
two States are produced. One has derived (vector sled (t (force sled
earth weight) (dnum 270 |deg|)) and the other has derived (vector sled
(force sled plane normal) (dnum 20 |deg|)). From these two States,
the force terms are extracted, and ?forces is bound to ((force sled earth
weight)(force sled plane normal)).
(foreach <unification variable> <Lisp form> <atomic statement>)
This executable is actually a macro. It expands into a list of preconditions
that replace it. For instance, suppose that
(foreach ?b ?bodies (vector ?b (at (velocity
?b) ?t1) ?dir1))
is executed when ?bodies is bound to (ball bat) and ?t1 is bound to
2. Execution causes this precondition to be replaced by two preconditions:
(vector ball (at (velocity ball) 2) ?g007181)
(vector ball (at (velocity bat) 2) ?g007182)
In general, the <Lisp form> should return a list of terms when it
is executed. For each member of this set, the <unification variable>
is bound to the member, and a copy of the <atomic statement> is turned
into a precondition. Any variables in the <atomic statement> that
are unbound when this occurred are turned into variables that are guaranteed
to be unique.
(map <unification variable> <Lisp form> <atomic statement>
<unification variable> <unification variable>) This is
also a macro and expands into a list of preconditions that replace it.
For instance, suppose we execute:
(map ?v
(vars-in-eqn ?eqn-algebra)
(in-wm (variable ?v
?q))
?q
?quantities-in-eqn)
when ?eqn-algebra is bound to (= w (* m a)). It expands into
(in-wm (variable w ?q1))
(in-wm (variable m ?q2))1
(in-wm (variable a ?q3))
(bind ?quantities-in-eqn '(q1 q2 q3))
That is, the interpreter calls the <Lisp form> and expects to receive
a list back. In this case, it calls vars-in-eqn which extracts variables
from (= w (* m a)) and returns the list (w m a). For each member
of this list, the first <unification variable>, ?v in this case, is
bound to the member and a copy of <goal>, (in-wm (variable ?v
?q)) in this case, is entered as a precondition. The the unbound
variables (only ?q in this case) are renamed to make them unique.
Lastly, the renamed variables that correspond to the second <unification
variable>, ?q in this case, are gathered together into a list and included
in a bind executable that binds them to the third <unification variable>,
?quantities-in-eqn in this case.
Start Lisp in your favorite working envirnoment. If you are using the IDE that comes with Lisp, start by executing (in-package :user). If you are using Emacs or the Lisp console interface, this is unnecessary.
Set the working directory to the one that contains the Andes2 files by executing :cd C:/Andes2
Next load the system loader file by executing :ld sgg/loader. You could also do (load "sgg/loader").
Next, recompile and reload all the files (for speed) by executing (rca).
Next, try solving a problem, such as exkt1a by executing (s exkt1a). Here's the call and the output, with annotations in variable width font explaining what the output means.
USER(22): (s exkt1a)
1 1111111111111111 1 Generating Bubblegraph: EXKT1A
1 1111111111111111 1
WARNING: PSM Subevars differ;
((VARIABLE m_AIRCRAFT (MASS AIRCRAFT)))
NIL
2 2222222222222222 2 Generating Equation Index: EXKT1A
2 2222222222222222 2
3 3333333333333333 3 Generating Var Index: EXKT1A 3
3333333333333333 3
4 444444444444444444 4 Generating Solution Point: EXKT1A
4 444444444444444444 4
Complete Solution Point Returned.
5 555555555555555555 5 Generating Solution Bubbles: EXKT1A
5 555555555555555555 5
6 666666666666666666 6 Marking Problem Graph: EXKT1A
6 666666666666666666 6
NIL
[2] USER(23):
The (s...) function creates the whole solution graph in 6 stages. It first stage creates the bubble graph. This is where the KB code is executed. A warning message was generated here (you can ignore this). Next it generates two indices needed by the help system. The fourth stage is to submit the equations to the algebraic equation solver and make sure that they are solvable. In this case, one solution point was generated, which indicates success. Next it calculates all possible paths through the bubble graph. Each path corresponds to a solution path that the student could take, so these are sometimes interesting to look at in order to debug the KB code. In the sixth stage, some bookkeeping is done that involves marking parts of the solution graph for the sake of the help system.
When working on extremely simple problems, it is sometimes useful to trace the reasoning of the problem solver as it occurs. Executing (setq *actions* T) will cause a complete trace of the reasoning to be printed. Executing (setq *debug* T) will cause the trace statements in the Debug executables to be printed.
===== Quantity nodes: =====
<Quantity-Node: 0 2 (AT (SPEED AIRCRAFT) (DURING 1 2))
Variable: sp_AIRCRAFT_12
Equations: (<Eq: (SDD AIRCRAFT (DURING 1 2))>)
Marks: (SOUGHT)
Path: NIL>
<Quantity-Node: 1 1 (AT (DISTANCE AIRCRAFT) (DURING 1 2))
Variable: dist_AIRCRAFT_12
Equations: (<Eq: (SDD AIRCRAFT (DURING 1 2))>
<Eq: (GIVEN (AT (DISTANCE AIRCRAFT) (DURING 1 2))
(DNUM 8790000 m))>)
Marks: (GIVEN)
Path: NIL>
<Quantity-Node: 2 0 (DURATION (DURING 1 2))
Variable: t_12
Equations: (<Eq: (SDD AIRCRAFT (DURING 1 2))>
<Eq: (GIVEN (DURATION (DURING 1 2)) (DNUM 13656 s))>)
Marks: (GIVEN)
Path: NIL>
===== Equation nodes: =====
<Equation-Node: 3 2 (SDD AIRCRAFT (DURING 1 2))
Algebra: (= sp_AIRCRAFT_12 (/ dist_AIRCRAFT_12 t_12))
Quantities: (<Q: (AT (SPEED AIRCRAFT) (DURING 1 2))>
<Q: (AT (DISTANCE AIRCRAFT) (DURING 1 2))>
<Q: (DURATION (DURING 1 2))>)
Marks: NIL
<Equation-Node: 4 1 (GIVEN (AT (DISTANCE AIRCRAFT) (DURING 1
2))
(DNUM 8790000 m))
Algebra: (= dist_AIRCRAFT_12 (DNUM 8790000 m))
Quantities: (<Q: (AT (DISTANCE AIRCRAFT) (DURING 1 2))>)
Marks: (GIVEN)
<Equation-Node: 5 0 (GIVEN (DURATION (DURING 1 2)) (DNUM 13656
s))
Algebra: (= t_12 (DNUM 13656 s))
Quantities: (<Q: (DURATION (DURING 1 2))>)
Marks: (GIVEN)
The Path fields and the marks can be ignored here. If you want to see the PSM graph associated with a node, execute (pgn <node number>) where the node number is the number immediately following the ...-Node: above. For instance, (pgn 5) prints the details about the last node listed above:
USER(27): (pgn 5)
<Equation-Node: 5 0 (GIVEN (DURATION (DURING 1 2)) (DNUM 13656
s))
Algebra: (= t_12 (DNUM 13656 s))
Path: ((SG DUMMY (GIVEN-EQN ?EQN (DURATION (DURING 1 2))))
(OP (WRITE-KNOWN-VALUE-EQN
(DURATION (DURING 1 2))))
(SG WRITE-KNOWN-VALUE-EQN
(IN-WM (GIVEN
(DURATION (DURING 1 2)) ?VALUE-EXPR40340)))
(SG WRITE-KNOWN-VALUE-EQN
(TEST (OR
(NUMBERP (DNUM 13656 s)) (LISTP (DNUM 13656 s)))))
(SG WRITE-KNOWN-VALUE-EQN
(VARIABLE
?VAR-NAME40339 (DURATION (DURING 1 2))))
(OP (DEFINE-DURATION
(DURING 1 2)))
(SG DEFINE-DURATION
(TIME (DURING 1 2))) (WM (TIME (DURING 1 2)))
(SG DEFINE-DURATION
(TEST (TIME-INTERVALP (DURING 1 2))))
(SG DEFINE-DURATION
(NOT (VARIABLE
?DONT-CARE40343 (DURATION (DURING 1 2)))))
(SG DEFINE-DURATION
(BIND ?VAR40344
(FORMAT-SYM t_~A (TIME-ABBREV (DURING 1 2)))))
(DO (DEFINE-DURATION
(DURING 1 2))
((DEFINE-VAR (DURATION (DURING 1 2)))
(VARIABLE t_12 (DURATION (DURING 1 2))))
(?DONT-CARE40343
t_12 (DURING 1 2)))
(DO (WRITE-KNOWN-VALUE-EQN
(DURATION (DURING 1 2)))
((GIVEN-EQN (= t_12 (DNUM 13656 s)) (DURATION (DURING 1 2))))
(t_12
(DNUM 13656 s) (DURATION (DURING 1 2))))
(DO (DUMMY) ()
NIL))
Assumptions: NIL
Quantities: (<Q: (DURATION (DURING 1 2))>)
Marks: (GIVEN)
State: NIL
Subeqns: ((EQN 0 GIVEN-EQN (= t_12 (DNUM 13656 s)) (DURATION
(DURING 1 2))
(<Eq: (GIVEN (DURATION (DURING 1 2)) (DNUM 13656 s))>) T))
Subvars: NIL
Entries: NIL>
The Path field shows all the reasoning steps the problem solver went through to generate this equation node. This is often too much information to be helpful.
A more helpful printout is (pe), which prints all possible paths through the solution graph. These are the path that the student must actually go through, so sometime it is useful to study them. In this case there is just one path:
USER(28): (pe)
Equation sets for problem: EXKT1A
0: (<Eq: (GIVEN (DURATION (DURING 1 2)) (DNUM 13656 s))>
<Eq: (GIVEN (AT (DISTANCE AIRCRAFT) (DURING
1 2)) (DNUM 8790000 m))>
<Eq: (SDD AIRCRAFT (DURING 1 2))>)
(<Eq: (GIVEN (DURATION (DURING 1 2)) (DNUM 13656 s))>
<Q: (DURATION (DURING 1 2))>
<Eq: (GIVEN (AT (DISTANCE AIRCRAFT) (DURING 1 2)) (DNUM
8790000 m))>
<Q: (AT (DISTANCE AIRCRAFT) (DURING 1 2))> <Eq: (SDD
AIRCRAFT (DURING 1 2))>
<Q: (AT (SPEED AIRCRAFT) (DURING 1 2))>)
NIL
To see the details about one path, execute (pep <path number>), so (pep 0) gives:
USER(29): (pep 0)
0: (<Eq: (GIVEN (DURATION (DURING 1 2)) (DNUM 13656 s))>
<Eq: (GIVEN (AT (DISTANCE AIRCRAFT) (DURING
1 2)) (DNUM 8790000 m))>
<Eq: (SDD AIRCRAFT (DURING 1 2))>)
(<Eq: (GIVEN (DURATION (DURING 1 2)) (DNUM 13656 s))>
<Q: (DURATION (DURING 1 2))>
<Eq: (GIVEN (AT (DISTANCE AIRCRAFT) (DURING 1 2)) (DNUM
8790000 m))>
<Q: (AT (DISTANCE AIRCRAFT) (DURING 1 2))> <Eq: (SDD
AIRCRAFT (DURING 1 2))>
<Q: (AT (SPEED AIRCRAFT) (DURING 1 2))>)
NIL
<B: Knowns: (<Eq: (GIVEN (DURATION
(DURING 1 2)) (DNUM 13656 s))>
<Q: (DURATION (DURING 1 2))>
<Eq: (GIVEN (AT (DISTANCE AIRCRAFT) (DURING 1 2))
(DNUM 8790000 m))>
<Q: (AT (DISTANCE AIRCRAFT) (DURING 1 2))>
<Eq: (SDD AIRCRAFT (DURING 1 2))>
<Q: (AT (SPEED AIRCRAFT) (DURING 1 2))>)
NIL
Soughts: NIL>
This prints the path in increasing detail: first the equations nodes in this path, then all nodes in this path and finally the path itself. To print out the list of variables, execute (pgv).
USER(34): (pgv)
((QVAR 0 t_12 (DURATION (DURING 1 2)) 13656 s (POSITIVE)
(<Q: (DURATION (DURING 1 2))>))
(QVAR 1 dist_AIRCRAFT_12 (AT (DISTANCE AIRCRAFT) (DURING
1 2)) 8790000 m NIL
(<Q: (AT (DISTANCE AIRCRAFT) (DURING 1 2))>))
(QVAR 2 sp_AIRCRAFT_12 (AT (SPEED AIRCRAFT) (DURING 1 2))
643.67311072056 m/s
NIL (<Q: (AT (SPEED AIRCRAFT) (DURING 1 2))>)))
This is a list of 3 descriptors. Each one has the following items:
USER(35): (pge)
((EQN 0 GIVEN-EQN (= t_12 (DNUM 13656 s)) (DURATION (DURING 1 2))
(<Eq: (GIVEN (DURATION (DURING 1 2)) (DNUM 13656 s))>)
T)
(EQN 1 GIVEN-EQN (= dist_AIRCRAFT_12 (DNUM 8790000 m))
(AT (DISTANCE AIRCRAFT) (DURING 1 2))
(<Eq: (GIVEN (AT (DISTANCE AIRCRAFT) (DURING 1 2)) (DNUM
8790000 m))>) T)
(EQN 2 EQN (= sp_AIRCRAFT_12 (/ dist_AIRCRAFT_12 t_12))
(SDD AIRCRAFT (DURING 1 2)) (<Eq: (SDD AIRCRAFT (DURING
1 2))>) T))
This is also a list of 3 descriptors. Each has the following format: