;;; File proj/recognise/recognise.soar ;;; Started 1.4.93 ;;; Version 5.4.93 ;;; A general-purpose operator to generate a recognition chunk for an ;;; arbitrary Soar object. It should be happy even with non-tree ;;; (i.e. converging and circular) structures, but see the Notes below. ;;; In order to help explore the space of possible designs, and indeed ;;; to show that such a space exists, this deliberately explores a ;;; different region of the design space than Joe Mertz's: ;;; * This one is non-destructive (rather than destructive) ;;; * It yields "flat", non-impassing recognition chunks ;;; * It uses a counting technique to avoid partial recognition ;;; * The recognition is hierarchical, but it handles the hierarchy ;;; by elaborations rather than by impasses. ;;; The interface to the code is as follows: ;;; There is an operator named Assimilate, with an ^object attribute ;;; which is the object to be recognised. The operator will acquire ;;; a ^symbol attribute, which is the recognition symbol. As soon as ;;; the operator has the ^symbol, it is reconsidered, and the ;;; symbol can be picked up and used by the calling program. ;;; Here is an example call. Just load the following rules in with ;;; the code and run it: #| (sp example*recognise (goal ^name top-goal) --> ( ^name recognise ^object ) ( ^hi ^hot ) ( ^bam boo + boo &, foo + foo &, ^african violet ^food ) ( ^fish chips) ( ^cool cat ^more ) ( ^yet again) ( ^operator )) (sp example*done (goal ^name top-goal ^operator ) ( ^name recognise ^symbol ) --> (write (crlf) |The recognition symbol is | ) (halt)) |# ;;; The basic method is this: ;;; 1. If the object is a constant, return it as its own symbol. ;;; Otherwise ;;; 2. Mark the object on the state as a node-to-be-recognised ;;; 3. For each such node, all identifiers in the top level of the ;;; node are also marked as nodes-to-be-recognised, and as ;;; dependents of the node ;;; 4. Count the number of Attribute-Value pairs in each node ;;; 5. At this point, recognition chunks fire. ;;; 6. If there isn't already a recognition chunk for the object, ;;; we impasse into the Recognise space: ;;; 6.1 For each node which does not yet have a symbol ... ;;; 6.2 Serially scan its Attributes and Values ;;; 6.3 Check that any dependents have their symbols ;;; 6.4 When the node is scanned, generate a new symbol for it ;;; 7. Once there is a symbol for the object, mark it on the operator. ;;; 8. Clean up: remove all the nodes from the state. ;;; The code below is organised according to this sketch of the algorithm ;;; and keyed to the step numbers. ;;; The algorithm is highly parallel. For recognition, it propagates ;;; a wave of activity *down* the levels of the structure, followed by ;;; a wave of recognition-rule firings back *up* the structure. When ;;; building chunks for a new object, there can be cross-firing and ;;; interdependence between the parallel streams. ;;; The algorithm is unusual in that, e.g. for recognising a new ;;; object, it handles a hierarchical structure (i.e. the object) ;;; within a single impasse. So learning bottom-up versus all-goals ;;; makes no difference. ;;; NOTES ;;; 1. On rule operator*recognise*dependent-as-node, the "not already ;;; there" test is needed to prevent infinite loops with circular ;;; structures. It will not prevent multiple copies of a node at ;;; the same level in the structure, but that does not matter. ;;; 2. With non-tree structures, there are some subtleties as to exactly ;;; what constitutes the "same" structure for recognition purposes. ;;; (Cf. EQ versus EQUAL in Lisp.) I believe this to be the case: A ;;; recognition chunk built for an object containing isomorphic (but not ;;; identical) sub-structure at the top level of the object or some ;;; sub-object will recognise the same object with identical ;;; sub-structure, but not vice-versa. ;;; --- The RECOGNISE operator --- ;;; Terminate the operator once it has its ^symbol (sp operator*recognise*terminate (goal ^operator ) ( ^name recognise ^symbol) --> ( ^operator @)) ;;; Step 1. If the object is a constant, return it as its own symbol (sp operator*recognise*constant :o-support (goal ^operator ) ( ^name recognise ^object -^object <=> ) --> ( ^symbol )) ;;; Step 2. Mark the object on the state as a node-to-be-recognised (sp operator*recognise*object-as-node :o-support (goal ^state ^operator ) ( ^name recognise ^object ^object <=> ) --> ( ^structure ) ( ^recognition-node + &) ( ^own-node )) ;;; Step 3. For each such node, all identifiers in the top level of the ;;; node are also marked as nodes-to-be-recognised, and as ;;; dependents of the node ;;; provided they are not already there (see Notes above) (sp operator*recognise*dependent-as-node (goal ^state ^operator.name recognise) ( ^recognition-node ) ( ^structure ) ( ^ { <=> }) -{( ^recognition-node ) ( ^structure )} --> ( ^structure ) ( ^recognition-node + &)) (sp operator*recognise*dependent-as-dependent (goal ^state ^operator.name recognise) ( ^recognition-node ) ( ^structure ) ( ^ { <=> }) ( ^recognition-node ) ( ^structure ) --> ( ^dependent + &)) ;;; Step 4. Count the number of Attribute-Value pairs in each node (sp operator*recognise*count*initial-count (goal ^state.recognition-node ^operator.name recognise) ( -^count) --> ( ^count 0)) ;(thank you, Bob D.) (sp operator*recognise*count*all-avs (goal ^state.recognition-node ^operator.name recognise) ( ^structure ) ( ^ ) --> ( ^ ) ( ^a-v + =)) (sp operator*recognise*count*done (goal ^state.recognition-node ^operator.name recognise) --> ( ^a-v done + done <)) (sp operator*recognise*count*each-av (goal ^state.recognition-node ^operator.name recognise) ( ^count ^a-v ) ( ^ ) --> ( ^a-v - ^count - (+ 1 ) +)) ;;; Step 5. At this point, recognition chunks fire. ;;; Step 6. If there isn't already a recognition chunk for the object, ;;; we impasse into the Recognise space, see below. ;;; Step 7. Once there is a symbol for the object, mark it on the operator (sp operator*recognise*pickup-symbol (goal ^state.recognition-node ^operator ) ( ^name recognise ^own-node ) ( ^symbol ) --> ( ^symbol ^own-node -)) ;;; Step 8. Clean up: remove all the nodes from the state (sp operator*recognise*delete-node (goal ^state ^operator ) ( ^recognition-node ) ( ^name recognise ^symbol) --> ( ^recognition-node -)) ;;; --- The RECOGNISE implementation space --- ;;; Step 6 (cont). In the op-implementation subgoal: ;;; Initialise the subgoal with space, state and operator (sp recognise*initialise (goal ^impasse no-change ^attribute operator ^object ) ( ^state ^operator ) ( ^name recognise) --> (

^name recognise) ( ^superstate ) ( ^name build) ( ^problem-space

^state ^operator )) ;;; Step 6.1 For each node which does not yet have a symbol ... (sp recognise*propose-scan-all (goal ^problem-space.name recognise ^state ^operator.name build) ( ^superstate.recognition-node ) --> ( ^recognition-node ^bead ) ( ^node + &)) (sp recognise*propose-scan-ok (goal ^problem-space.name recognise ^state ^operator.name build) ( ^node ) --> ( ^ok yes)) (sp recognise*propose-scan-duplicate (goal ^problem-space.name recognise ^state ^operator.name build) ( ^node ) ( ^recognition-node ) ( ^symbol ) --> ( ^duplicate yes)) ;;; Step 6.2 Serially scan its Attributes and Values (sp recognise*scan*all-a-v (goal ^problem-space.name recognise ^state ^operator.name build) ( ^node ) ( ^recognition-node.structure ^ok yes -^duplicate yes) ( ^ ) --> ( ^ ) ( ^check + =)) (sp recognise*scan*default-done (goal ^problem-space.name recognise ^state ^operator.name build) ( ^node ) ( ^ok yes -^duplicate yes) --> ( ^check done + done <)) #| *** In some cases, the process will run a little faster if A-Vs with *** *** V a constant, or if/when V is an identifier but has its symbol, *** *** are checked before the others. Could add code to prefer these. *** |# ;;; Step 6.3 Check that any dependents have their symbols (sp recognise*scan*check*constant (goal ^problem-space.name recognise ^state ^operator.name build) ( ^node ) ( ^check ^bead ) ( ^ -^ <=> ) --> ( ^bead - + ^check -)) (sp recognise*scan*check*identifier (goal ^problem-space.name recognise ^state ^operator.name build) ( ^node ) ( ^check ^bead ) ( ^ { <=> }) ( ^recognition-node.dependent ) ( ^structure ^symbol ) --> ( ^bead - + ^check -)) ;;; Step 6.4 When the node is scanned, generate a new symbol for it (sp recognise*scan*done (goal ^problem-space.name recognise ^state ^operator.name build) ( ^node ) ( ^check done ^bead ^recognition-node ) --> ( ^new-symbol-for + =)) ;;; The actual assignment of the new symbol needs to be done serially, ;;; and with great care over timing. The assignment of the symbol ;;; yields a recognition chunk which may fire immediately. The chunk ;;; should not clash with any new symbol generated by this code. (sp recognise*new-symbol*ok (goal ^problem-space.name recognise ^state ) ( ^new-symbol-for ) --> ( ^new-symbol-ok )) (sp recognise*new-symbol*duplicate (goal ^problem-space.name recognise ^state ) ( ^new-symbol-for ) ( ^symbol) --> ( ^new-symbol-duplicate )) (sp recognise*new-symbol*generate (goal ^problem-space.name recognise ^state ^operator.name build) ( ^new-symbol-for ^new-symbol-ok -^new-symbol-duplicate ) ( ^count ^a-v done) --> ( ^symbol (make-constant-symbol recog-))) (sp recognise*new-symbol*done (goal ^problem-space.name recognise ^state ^operator.name build) ( ^new-symbol-for ) ( ^symbol ) --> ( ^new-symbol-for -))