Skip to content

🚧 Under Construction 🚧
The HTML version of the manual is currently under construction and slightly out of date. If you find it difficult to read, there is a PDF version available here.

The Syntax of Soar Programs

This chapter describes in detail the syntax of elements in working memory, preference memory, and production memory, and how impasses and I/O are represented in working memory and in productions. Working memory elements and preferences are created as Soar runs, while productions are created by the user or through chunking. The bulk of this chapter explains the syntax for writing productions.

The first section of this chapter describes the structure of working memory elements in Soar; the second section describes the structure of preferences; and the third section describes the structure of productions. The fourth section describes the structure of impasses. An overview of how input and output appear in working memory is presented in the fifth section. Further discussion of Soar I/O can be found on the Soar website.

This chapter assumes that you understand the operating principles of Soar, as presented in the Soar architecture.

Working Memory

Working memory contains working memory elements (WME’s). As described in Chapter 2, WME’s can be created by the actions of productions, the evaluation of preferences, the Soar architecture, and via the input/output system.

A WME is a tuple consisting of three symbols: an identifier, an attribute, and a value, where the entire WME is enclosed in parentheses and the attribute is preceded by an up-arrow (^). A template for a working memory element is:

(identifier ^attribute value)

The first position always holds an internal identifier symbol, generated by the Soar architecture as it runs. The attribute and value positions can hold either identifiers or constants. The term identifier is used to refer both to the first position of a WME, as well as to the symbols that occupy that position. If a WME’s attribute or value is an identifier, there is at least one WME that has that identifier symbol in its first position.

Symbols

Soar distinguishes between two types of working memory symbols: identifiers and constants.

  • Identifiers: An identifier is a unique symbol, created at runtime when a new object is added to working memory. The names of identifiers are created by Soar, and consist of a single uppercase letter followed by a string of digits, such as G37 or O22.

(The Soar user interface will also allow users to specify identifiers using lowercase letters in a case-insensitive manner, for example, when using the print command. But internally, they are actually uppercase letters.)

  • Constants: There are three types of constants: integers, floating-point, and symbolic constants: - Integer constants (numbers). The range of values depends on the machine and implementation you’re using, but it is at least [-2 billion...+2 billion].
    • Floating-point constants (numbers). The range depends on the machine and implementation you’re using.
    • Symbolic constants. These are symbols with arbitrary names. A constant can use any combination of letters, digits, or $%&*+-/:<=>?_. Other characters (such as blank spaces) can be included by surrounding the complete constant name with vertical bars: |This is a constant|. (The vertical bars aren’t part of the name; they’re just notation.) A vertical bar can be included by prefacing it with a backslash inside surrounding vertical bars: |Odd-symbol\|name|

Identifiers should not be confused with constants, although they may "look the same"; identifiers are generated (by the Soar architecture) at runtime and will not necessarily be the same for repeated runs of the same program. Constants are specified in the Soar program and will be the same for repeated runs.

Even when a constant "looks like" an identifier, it will not act like an identifier in terms of matching. A constant is printed surrounded by vertical bars whenever there is a possibility of confusing it with an identifier: |G37| is a constant while G37 is an identifier. To avoid possible confusion, you should not use letter-number combinations as constants or for production names.

Objects

Recall from Section Working Memory: The current situation that all WME’s that share an identifier are collectively called an object in working memory. The individual working memory elements that make up an object are often called augmentations, because they augment the object. A template for an object in working memory is:

(identifier ^attribute-1 value-1 ^attribute-2 value-2
            ^attribute-3 value-3... ^attribute-n value-n)

For example, if you run Soar with the supplementary blocks-world program provided online, after one elaboration cycle, you can look at the top-level state object by using the print command:

1
2
3
4
soar> print s1
(S1 ^io I1 ^ontop O2 ^ontop O3 ^ontop O1 ^problem-space blocks
    ^superstate nil ^thing B3 ^thing T1 ^thing B1 ^thing B2
    ^type state)

The attributes of an object are printed in alphabetical order to make it easier to find a specific attribute.

Working memory is a set, so that at any time, there are never duplicate versions of working memory elements. However, it is possible for several working memory elements to share the same identifier and attribute but have different values. Such attributes are called multi-valued attributes or multi-attributes. For example, state S1, above, has two attributes that are multi-valued: thing and ontop.

Timetags

When a working memory element is created, Soar assigns it a unique integer timetag. The timetag is a part of the working memory element, and therefore, WME’s are actually quadruples, rather than triples. However, the timetags are not represented in working memory and cannot be matched by productions. The timetags are used to distinguish between multiple occurrences of the same WME. As preferences change and elements are added and deleted from working memory, it is possible for a WME to be created, removed, and created again. The second creation of the WME — which bears the same identifier, attribute, and value as the first WME — is different, and therefore is assigned a different timetag. This is important because a production will fire only once for a given instantiation, and the instantiation is determined by the timetags that match the production and not by the identifier-attribute-value triples.

To look at the timetags of WMEs, the print --internal command can be used:

soar> print --internal S1
(3: S1 ^io I1)
(10: S1 ^ontop O2)
(9: S1 ^ontop O3)
(11: S1 ^ontop O1)
(4: S1 ^problem-space blocks)
(2: S1 ^superstate nil)
(6: S1 ^thing B3)
(5: S1 ^thing T1)
(8: S1 ^thing B1)
(7: S1 ^thing B2)
(1: S1 ^type state)

This shows all the individual augmentations ofS1, each is preceded by an integer timetag.

Acceptable preferences in working memory

The acceptable preferences for operators appear in working memory as identifier-attribute-value-preference quadruples. No other preferences appear in working memory. A template for an acceptable preference in working memory is:

(identifier ^operator value +)

For example, if you run Soar with the example blocks-world program linked above, after the first operator has been selected, you can again look at the top-level state using the print --internal command:

soar> print --internal s1
(3: S1 ^io I1)
(9: S1 ^ontop O3)
(10: S1 ^ontop O2)
(11: S1 ^ontop O1)
(48: S1 ^operator O4 +)
(49: S1 ^operator O5 +)
(50: S1 ^operator O6 +)
(51: S1 ^operator O7 +)
(54: S1 ^operator O7)
(52: S1 ^operator O8 +)
(53: S1 ^operator O9 +)
(4: S1 ^problem-space blocks)
(2: S1 ^superstate nil)
(5: S1 ^thing T1)
(8: S1 ^thing B1)
(6: S1 ^thing B3)
(7: S1 ^thing B2)
(1: S1 ^type state)

The state S1 has six augmentations of acceptable preferences for different operators (O4 throughO9). These have plus signs following the value to denote that they are acceptable preferences. The state has exactly one operator,O7. This state corresponds to the illustration of working memory in Figure 2.4.

Working Memory as a Graph

Not only is working memory a set, it is also a graph structure where the identifiers are nodes, attributes are links, and constants are terminal nodes. Working memory is not an arbitrary graph, but a graph rooted in the states (e.g. S1). Therefore, all WMEs are linked either directly or indirectly to a state. The impact of this constraint is that all WMEs created by actions are linked to WMEs tested in the conditions. The link is one-way, from the identifier to the value. Less commonly, the attribute of a WME may be an identifier.

A semantic net illustration of four objects in working memory.

This figure illustrates four objects in working memory; the object with identifier X44 has been linked to the object with identifier O43, using the attribute as the link, rather than the value. The objects in working memory illustrated by this figure are:

1
2
3
4
(O43 ^isa apple ^color red ^inside O53 ^size small ^X44 200)
(O87 ^isa ball ^color red ^inside O53 ^size big)
(O53 ^isa box ^size large ^color orange ^contains O43 O87)
(X44 ^unit grams ^property mass)

In this example, object O43 and object O87 are both linked to object O53 through (O53 ^contains O43) and (O53 ^contains O87), respectively (the contains attribute is a multi-valued attribute). Likewise, object O53 is linked to object O43 through (O43 ^inside O53) and linked to object O87 through (O87 ^inside O53). Object X44 is linked to object O43 through (O43 ^X44 200).

Links are transitive so that O53is linked to X44(because O53 is linked toO43 and O43 is linked to X44). However, since links are not symmetric, X44is not linked to O53.

Working Memory Activation

WMEs have a form of base level activation associated with them that is not accessible to the agent, but that is used by the architecture. Working Memory Activation (WMA) is sub-symbolic metadata associated with a given element and represents its usage. A WME has been used if it has been matched in a rule that fired. WMA is not recorded or maintained when disabled, which is the default. See wm command for working memory settings and options for enabling WMA.

Simply enabling WMA has no impact on any agent’s behavior outside of a small additional computational cost. However, working memory activation is used for other features. Primarily, it is necessary for allowing the forgetting of memory elements from working memory. When working memory forgetting is turned on, those working memory elements with activation below a given threshold are removed from working memory. This allows agents to maintain a bounded working memory size without explicit memory size management. It also has a role in determining spreading activation values, discussed in section activation.

Preference Memory

Preferences are created by production firings and express the relative or absolute merits for selecting an operator for a state. When preferences express an absolute rating, they are identifier-attribute-value-preference quadruples; when preferences express relative ratings, they are identifier-attribute-value-preference-value quintuples

For example,

(S1 ^operator O3 +)

is a preference that asserts that operator O3 is an acceptable operator for state S1, while

(S1 ^operator O3 > O4)

is a preference that asserts that operator O3 is a better choice for the operator of state S1 than operator O4.

The semantics of preferences and how they are processed were described in Section Preference Memory: Selection Knowledge, which also described each of the eleven different types of preferences. Multiple production instantiations may create identical preferences. Unlike working memory, preference memory is not a set: Duplicate preferences are allowed in preference memory.

Production Memory

Production memory contains productions, which can be entered in by a user (typed in while Soar is running or loaded from a file) or generated by chunking while Soar is running. Productions (both user-defined productions and chunks) may be examined using the print command, described in the print command.

sp {blocks-world*propose*move-block
   (state <s> ^problem-space blocks
      ^thing <thing1> {<> <thing1> <thing2>}
      ^ontop <ontop>)
   (<thing1> ^type block ^clear yes)
   (<thing2> ^clear yes)
   (<ontop> ^top-block <thing1>
   ^bottom-block <> <thing2>)
-->
   (<s> ^operator <o> +)
   (<o> ^name move-block
   ^moving-block <thing1>
   ^destination <thing2>)
}

Each production has three required components: a name, a set of conditions (also called the left-hand side, or LHS), and a set of actions (also called the right-hand side, or RHS). There are also two optional components: a documentation string and a type.

Syntactically, each production consists of the symbol sp, followed by: an opening curly brace, {; the production’s name; the documentation string (optional); the production type (optional); comments (optional); the production’s conditions; the symbol-->(literally: dash-dash-greater than); the production’s actions; and a closing curly brace,}. Each element of a production is separated by white space. Indentation and line feeds are used by convention, but are not necessary.

An example production, named blocks-world*propose*move-block, is shown in the following code block. This production proposes operators named move-block that move blocks from one location to another. The details of this production will be described in the following sections.

1
2
3
4
5
6
7
sp {production-name
   "Documentation string"
   :type
   CONDITIONS
   -->
   ACTIONS
   }

Conventions for indenting productions

Productions in this manual are formatted using conventions designed to improve their readability. These conventions are not part of the required syntax. First, the name of the production immediately follows the first curly bracket after the sp. All conditions are aligned with the first letter after the first curly brace, and attributes of an object are all aligned The arrow is indented to align with the conditions and actions and the closing curly brace follows the last action.

Production Names

The name of the production is an almost arbitrary constant. (See Section Symbols for a description of constants.) By convention, the name describes the role of the production, but functionally, the name is just a label primarily for the use of the programmer.

A production name should never be a single letter followed by numbers, which is the format of identifiers.

The convention for naming productions is to separate important elements with asterisks; the important elements that tend to appear in the name are:

  1. The name of the task or goal (e.g.,blocks-world).
  2. The name of the architectural function (e.g.,propose).
  3. The name of the operator (or other object) at issue. (e.g.,move-block)
  4. Any other relevant details.

This name convention enables one to have a good idea of the function of a production just by examining its name. This can help, for example, when you are watching Soar run and looking at the specific productions that are firing and retracting. Since Soar uses white space to delimit components of a production, if whitespace inadvertently occurs in the production name, Soar will complain that an open parenthesis was expected to start the first condition.

Documentation string (optional)

A production may contain an optional documentation string. The syntax for a documentation string is that it is enclosed in double quotes and appears after the name of the production and before the first condition (and may carry over to multiple lines). The documentation string allows the inclusion of internal documentation about the production; it will be printed out when the production is printed using the print command.

Production type (optional)

A production may also include an optional production type, which may specify that the production should be considered a default production (`:default) or a chunk (:chunk), or may specify that a production should be given o-support (:o-support) or i-support (:i-support). Users are discouraged from using these types.

Another flag (:template) can be used to specify that a production should be used to generate new reinforcement learning rules. See Section Rule Templates for details. There is one additional flag (:interrupt) which can be placed at this location in a production. However this flag does not specify a production type, but is a signal that the production should be marked for special debugging capabilities. For more information, see sp command.

Comments (optional)

Productions may contain comments, which are not stored in Soar when the production is loaded, and are therefore not printed out by the print command. A comment is begun with a pound sign character # and ends at the end of the line. Thus, everything following the # is not considered part of the production, and comments that run across multiple lines must each begin with a #.

For example:

sp {blocks-world*propose*move-block
   (state <s> ^problem-space blocks
      ^thing <thing1> {<> <thing1> <thing2>}
      ^ontop <ontop>)
   (<thing1> ^type block ^clear yes)
   (<thing2> ^clear yes)
#     (<ontop> ^top-block <thing1>
#        ^bottom-block <> <thing2>)
-->
   (<s> ^operator <o> +)
   (<o> ^name move-block # you can also use in-line comments
      ^moving-block <thing1>
      ^destination <thing2>)}

When commenting out conditions or actions, be sure that all parentheses remain balanced outside the comment.

External comments

Comments may also appear in a file with Soar productions, outside the curly braces of the sp command. Comments must either start a new line with a # or start with ;#. In both cases, the comment runs to the end of the line.

1
2
3
4
# imagine that this is part of a "Soar program" that contains
# Soar productions as well as some other code.

load file blocks.soar   ;# this is also a comment

The condition side of productions (or LHS)

The condition side of a production, also called the left-hand side (or LHS) of the production, is a pattern for matching one or more WMEs. When all of the conditions of a production match elements in working memory, the production is said to be instantiated, and is ready to perform its action. (Each instance binds the rule to specific WMEs.)

The following subsections describe the condition side of a production, including predicates, disjunctions, conjunctions, negations, acceptable preferences for operators, and a few advanced topics.

Conditions

The condition side of a production consists of a set of conditions. Each condition tests for the existence or absence (explained later in Section Negated Conditions) of working memory elements. Each condition consists of a open parenthesis, followed by a test for the identifier, and the tests for augmentations of that identifier, in terms of attributes and values. The condition is terminated with a close parenthesis. A single condition might test properties of a single working memory element, or properties of multiple working memory elements that constitute an object.

1
2
3
4
(identifier-test ^attribute1-test value1-test
   ^attribute2-test value2-test
   ^attribute3-test value3-test
   ...)

The first condition in a production must match against a state in working memory. Thus, the first condition must begin with the additional symbol "state". All other conditions and actions must be linked directly or indirectly to this condition. This linkage may be direct to the state, or it may be indirect, through objects specified in the conditions. If the identifiers of the actions are not linked to the state, a warning is printed when the production is parsed, and the production is not stored in production memory. In the actions of the example production shown in Figure 3.2, the operator preference is directly linked to the state and the remaining actions are linked indirectly via the operator preference.

Although all of the attribute tests in the example condition above are followed by value tests, it is possible to test for only the existence of an attribute and not test any specific value by just including the attribute and no value. Another exception to the above template is operator preferences, which have the following structure where a plus sign follows the value test.

(state-identifier-test ^operator value1-test +
   ...)

In the remainder of this section, we describe the different tests that can be used for identifiers, attributes, and values. The simplest of these is a constant, where the constant specified in the attribute or value must match the same constant in a working memory element.

Variables in productions

Variables match against symbols in WMEs in the identifier, attribute, or value positions. Variables can be further constrained by additional tests (described in later sections) or by multiple occurrences in conditions. If a variable occurs more than once in the condition of a production, the production will match only if the variables match the same identifier or constant. However, there is no restriction that prevents different variables from binding to the same identifier or constant.

Because identifiers are generated by Soar at run time, it impossible to include tests for specific identifiers in conditions. Therefore, variables are used in conditions whenever an identifier is to be matched.

Variables also provide a mechanism for passing identifiers and constants which match in conditions to the action side of a rule.

Syntactically, a variable is a symbol that begins with a left angle-bracket (i.e.,<), ends with a right angle-bracket (i.e.,>), and contains at least one non-pipe (|) character in between.

In the example production in Figure 3.2, there are seven variables: <s>, <clear1>, <clear2>, <ontop>,<block1>,<block2>, and <o>.

The following table gives examples of legal and illegal variable names.

Legal variables Illegal variables
<s> <>
<1> <1
<variable1> variable>
<abc1> <a b>
Predicates for values

A test for an identifier, attribute, or value in a condition (whether constant or variable) can be modified by a preceding predicate. There are six general predicates that can be used: <>, <=>, <, <=, >=, >.

Predicate Semantics of Predicate
<> Not equal. Matches anything except the value immediately following it.
<=> Same type. Matches any symbol that is the same type (identifier, integer, floating-point, non-numeric constant) as the value immediately following it.
< Numerically less than the value immediately following it.
<= Numerically less than or equal to the value immediately following it.
>= Numerically greater than or equal to the value immediately following it.
> Numerically greater than the value immediately following it.

The following table shows examples of legal and illegal predicates:

Legal predicates Illegal predicates
> <valuex> > > <valuey>
< 1 1 >
<=> <y> = 10

There are also four special predicates that can be used to test Long-Term Identifier (LTI) links held by working memory identifiers: @, !@, @+, @-

Predicate Semantics of Predicate
@ Same LTI. Matches when the two values are working memory identifiers linked to the same LTI.
!@ Different LTI. Matches when the values are not both identifiers linked to the same LTI.
@+ Matches if the value is an identifier linked to some LTI.
@- Matches if the value is not an identifier linked to some LTI.

See Knowledge Representation for more information on long-term semantic memory and LTIs.

Example Productions
1
2
3
4
5
6
sp {propose-operator*to-show-example-predicate
   (state <s> ^car <c>)
   (<c> ^style convertible ^color <> rust)
   -->
   (<s> ^operator <o> +)
   (<o> ^name drive-car ^car <c>) }

In this production, there must be a "color" attribute for the working memory object that matches <c>, and the value of that attribute must not be "rust".

1
2
3
4
5
sp {example*lti*predicates
   (state <s> ^existing-item { @+ <orig-sti> }
      ^smem.result.retrieved { @ <orig-sti> <result-sti> })
   -->
... }

In this production,<orig-sti>, is tested for whether it is linked to some LTI. It is also compared against <result-sti>(a working memory element retrieved from long-term memory and known to be linked to an LTI) to see if the two elements point to the same long-term memory. Note the the @+ in this example is actually unnecessary, since the { @ <orig-sti> <result-sti> } test will fail to match if either value tested is not linked to an LTI.

Disjunctions of values

A test for an identifier, attribute, or value may also be for a disjunction of constants. With a disjunction, there will be a match if any one of the constants is found in a working memory element (and the other parts of the working memory element matches). Variables and predicates may not be used within disjunctive tests.

Syntactically, a disjunctive test is specified with double angle brackets (i.e., << and >>). There must be spaces separating the brackets from the constants.

The following table provides examples of legal and illegal disjunctions:

Legal disjunctions Illegal disjunctions
<< A B C 45 I17 >> << <var> A >>
<< 5 10 >> << < 5 > 10 >>
<< good-morning good-evening >> <<A B C >>
Example Production

For example, the third condition of the following production contains a disjunction that restricts the color of the table to red or blue:

1
2
3
4
5
6
sp {blocks*example-production-conditions
   (state ^operator <o> + ^table <t>)
   (<o> ^name move-block)
   (<t> ^type table ^color << red blue >> )
   -->
   ... }
Note

Disjunctions of complete conditions are not allowed in Soar. Multiple (similar) productions fulfill this role.

Conjunctions of values

A test for an identifier, attribute, or value in a condition may include a conjunction of tests, all of which must hold for there to be a match.

Syntactically, conjuncts are contained within curly braces (i.e., { and }). The following table shows some examples of legal and illegal conjunctive tests:

Legal conjunctions Illegal conjunctions
{ <= <a> >= <b> } { <x> < <a> + <b> }
{ <x> > <y> } { > > <b>}
{ <> <x> <y>} { <a> <b> }
{ <y> <> <x>}
{ << A B C >> <x> }
{ <=> <x> > <y> << 1 2 3 4 >> <z>}

Because those examples are a bit difficult to interpret, let’s go over the legal examples one by one to understand what each is doing.

In the first example, the value must be less than or equal to the value bound to variable <a> and greater than or equal to the value bound to variable <b>.

In the second example, the value is bound to the variable <x>, which must also be greater than the value bound to variable <y>.

The third and fourth examples are equivalent. They state that the value must not be equal to the value bound to variable <x> and should be bound to variable <y>. Note the importance of order when using conjunctions with predicates: in the second example, the predicate modifies <y>, but in the third example, the predicate modifies <x>.

In the fifth example, the value must be one of A, B, or C, and the second conjunctive test binds the value to variable <x>.

In the sixth example, there are four conjunctive tests. First, the value must be the same type as the value bound to variable <x>. Second, the value must be greater than the value bound to variable <y>. Third, the value must be equal to 1 , 2 , 3 , or 4. Finally, the value should be bound to variable <z>.

In Figure 3.2, a conjunctive test is used for the thing attribute in the first condition.

Note that it is illegal syntax for a condition to test the equality of two variables, as demonstrated in the last illegal conjunction above. Any such test can instead be coded in simpler terms by only using one variable in the places where either would be referenced throughout the rule.

Negated conditions

In addition to the positive tests for elements in working memory, conditions can also test for the absence of patterns. A negated condition will be matched only if there does not exist a working memory element consistent with its tests and variable bindings. Thus, it is a test for the absence of a working memory element.

Syntactically, a negated condition is specified by preceding a condition with a dash (i.e., "-").

For example, the following condition tests the absence of a working memory element of the object bound to <p1> ^type father.

-(<p1> ^type father)

A negation can be used within an object with many attribute-value pairs by having it precede a specific attribute:

(<p1> ^name john -^type father ^spouse <p2>)

In that example, the condition would match if there is a working memory element that matches (<p1> ^name john) and another that matches (<p1> ^spouse <p2>), but is no working memory element that matches (<p1> ^type father) (when p1 is bound to the same identifier).

On the other hand, the condition:

-(<p1> ^name john ^type father ^spouse <p2>)

would match only if there is no object in working memory that matches all three attribute-value tests.

Example Production
1
2
3
4
5
6
7
sp {default*evaluate-object
   (state <ss> ^operator <so>)
   (<so> ^type evaluation
      ^superproblem-space <p>)
   -(<p> ^default-state-copy no)
   -->
   (<so> ^default-state-copy yes) }

For negated conditions in combination with attribute-path notation consult the section negated multi-valued attributes and attribute-path notation.

Notes

One use of negated conditions to avoid is testing for the absence of the working memory element that a production creates with i-support; this would lead to an "infinite loop" in your Soar program, as Soar would repeatedly fire and retract the production. For example, the following rule’s actions will cause it to no longer match, which will cause the action to retract, which will cause the rule to match, and so on:

1
2
3
4
5
sp {example*infinite-loop
   (state <s> ^car <c>
      -^road )
   -->
   (<s> ^road |route-66|) }

Also note that syntactically it is invalid for the first condition of a rule to be a negated condition. For example, the following production would fail to load:

1
2
3
4
5
sp {example*invalid-negated-first-condition
   (state <s> -^road <r>
      ^car <c>)
   -->
... }

Negated conjunctions of conditions

Conditions can be grouped into conjunctive sets by surrounding the set of conditions with { and }. The production compiler groups the test in these conditions together. This grouping allows for negated tests of more than one working memory element at a time. In the example below, the state is tested to ensure that it does not have an object on the table.

1
2
3
4
5
6
7
sp {blocks*negated-conjunction-example
   (state <s> ^name top-state)
  -{(<s> ^ontop <on>)
    (<on> ^bottom-object <bo>)
    (<bo> ^type table)}
   -->
   (<s> ^nothing-ontop-table true)}

When using negated conjunctions of conditions, the production has nested curly braces. One set of curly braces delimits the production, while the other set delimits the conditions to be conjunctively negated.

If only the last condition, (<bo> ^type table) were negated, the production would match only if the state had an ontop relation, and the ontop relation had a bottom-object, but the bottom object wasn’t a table. Using the negated conjunction, the production will also match when the state has no ontop augmentation or when it has an ontop augmentation that doesn’t have a bottom-object augmentation.

The semantics of negated conjunctions can be thought of in terms of mathematical logic, where the negation of \((A \wedge B \wedge C)\):

\[\neg (A \wedge B \wedge C)\]

can be rewritten as:

\[(\neg A) \vee (\neg B) \vee (\neg C)\]

That is, "not (A and B and C)" becomes "(not A) or (not B) or (not C)".

Multi-valued attributes

An object in working memory may have multiple augmentations that specify the same attribute with different values; these are called multi-valued attributes, or multi-attributes for short. To shorten the specification of a condition, tests for multi-valued attributes can be shortened so that the value tests are together.

For example, the condition:

(<p1> ^type father ^child sally ^child sue)

could also be written as:

(<p1> ^type father ^child sally sue)
Multi-valued attributes and variables

When variables are used with multi-valued attributes, remember that variable bindings are not unique unless explicitly forced to be so. For example, to test that an object has two values for attribute child, the variables in the following condition can match to the same value.

(<p1> ^type father ^child <c1> <c2>)

To do tests for multi-valued attributes with variables correctly, conjunctive tests must be used, as in:

(<p1> ^type father ^child <c1> {<> <c1> <c2>})

The conjunctive test {<> <c1> <c2>} ensures that <c2> will bind to a different value than <c1> binds to.

Negated conditions and multi-valued attributes

A negation can also precede an attribute with multiple values. In this case it tests for the absence of the conjunction of the values. For example

(<p1> ^name john -^child oprah uma)

is the same as

1
2
3
(<p1> ^name john)
-{(<p1> ^child oprah)
(<p1> ^child uma)}

and the match is possible if either (<p1> ^child oprah) or (<p1> ^child uma) cannot be found in working memory with the binding for <p1> (but not if both are present).

Acceptable preferences for operators

The only preferences that can appear in working memory are acceptable preferences for operators, and therefore, the only preferences that may appear in the conditions of a production are acceptable preferences for operators.

Acceptable preferences for operators can be matched in a condition by testing for a "+" following the value. This allows a production to test the existence of a candidate operator and its properties, and possibly create a preference for it, before it is selected.

In the example below, ^operator <o> + matches the acceptable preference for the operator augmentation of the state. This does not test that operator <o> has been selected as the current operator.

1
2
3
4
5
sp {blocks*example-production-conditions
   (state ^operator <o> + ^table <t>)
   (<o> ^name move-block)
   -->
   ... }

In the example below, the production tests the state for acceptable preferences for two different operators (and also tests that these operators move different blocks):

1
2
3
4
5
6
7
sp {blocks*example-production-conditions
   (state ^operator <o1> + <o2> + ^table <t>)
   (<o1> ^name move-block ^moving-block <m1> ^destination <d1>)
   (<o2> ^name move-block ^moving-block {<m2> <> <m1>}
      ^destination <d2>)
   -->
   ... }

Attribute tests

The previous examples applied all of the different tests to the values of working memory elements. All of the tests that can be used for values can also be used for attributes and identifiers (except those including constants).

Variables in attributes

Variables may be used with attributes, as in:

sp {blocks*example-production-conditions
   (state <s> ^operator <o> +
      ^thing <t> {<> <t> <t2>} )
   (operator <o> ^name group
      ^by-attribute <a>
      ^moving-block <t>
      ^destination <t2>)
   (<t> ^type block ^<a> <x>)
   (<t2> ^type block ^<a> <x>)
   -->
   (<s> ^operator <o> >) }

This production tests that there is acceptable operator that is trying to group blocks according to some attribute, <a>, and that block <t> and <t2> both have this attribute (whatever it is), and have the same value for the attribute.

Predicates in attributes

Predicates may be used with attributes, as in:

1
2
3
4
5
sp {blocks*example-production-conditions
   (state ^operator <o> + ^table <t>)
   (<t> ^<> type table)
   -->
... }

which tests that the object with its identifier bound to <t> must have an attribute whose value is table, but the name of this attribute is not type.

Disjunctions of attributes

Disjunctions may also be used with attributes, as in:

1
2
3
4
5
sp {blocks*example-production-conditions
   (state ^operator <o> + ^table <t>)
   (<t> ^<< type name>> table)
   -->
... }

which tests that the object with its identifier bound to <t> must have either an attribute type whose value is table or an attribute name whose value is table.

Conjunctive tests for attributes

Section Conjunction of Values illustrated the use of conjunctions for the values in conditions. Conjunctive tests may also be used with attributes, as in:

1
2
3
4
5
sp {blocks*example-production-conditions
   (state ^operator <o> + ^table <t>)
   (<t> ^{<ta> <> name} table)
   -->
   ... }

which tests that the object with its identifier bound to <t> must have an attribute whose value is table, and the name of this attribute is not name, and the name of this attribute (whatever it is) is bound to the variable <ta>.

When attribute predicates or attribute disjunctions are used with multi-valued attributes, the production is rewritten internally to use a conjunctive test for the attribute; the conjunctive test includes a variable used to bind to the attribute name. Thus,

(<p1> ^type father ^ <> name sue sally)

is interpreted to mean:

1
2
3
(<p1> ^type father
   ^{<> name <a*1>} sue
   ^<a*1> sally)

Attribute-path notation

Often, variables appear in the conditions of productions only to link the value of one attribute with the identifier of another attribute. Attribute-path notation provides a shorthand so that these intermediate variables do not need to be included.

Syntactically, path notation lists a sequence of attributes separated by dots (.), after the ^ in a condition.

For example, using attribute path notation, the production:

sp {blocks-world*monitor*move-block
   (state <s> ^operator <o>)
   (<o> ^name move-block
      ^moving-block <block1>
      ^destination <block2>)
   (<block1> ^name <block1-name>)
   (<block2> ^name <block2-name>)
   -->
   (write (crlf) |Moving Block: | <block1-name>
      | to: | <block2-name> ) }

could be written as:

1
2
3
4
5
6
7
8
sp {blocks-world*monitor*move-block
   (state <s> ^operator <o>)
   (<o> ^name move-block
      ^moving-block.name <block1-name>
      ^destination.name <block2-name>)
   -->
   (write (crlf) |Moving Block: | <block1-name>
      | to: | <block2-name> ) }

Attribute-path notation yields shorter productions that are easier to write, less prone to errors, and easier to understand.

When attribute-path notation is used, Soar internally expands the conditions into the multiple Soar objects, creating its own variables as needed. Therefore, when you print a production (using the print command), the production will not be represented using attribute-path notation.

Negations and attribute path notation

A negation may be used with attribute path notation, in which case it amounts to a negated conjunction. For example, the production:

1
2
3
4
5
6
7
sp {blocks*negated-conjunction-example
   (state <s> ^name top-state)
   -{(<s> ^ontop <on>)
   (<on> ^bottom-object <bo>)
   (<bo> ^type table)}
   -->
   (<s> ^nothing-ontop-table true) }

could be rewritten as:

1
2
3
4
sp {blocks*negated-conjunction-example
   (state <s> ^name top-state -^ontop.bottom-object.type table)
   -->
   (<s> ^nothing-ontop-table true) }
Multi-valued attributes and attribute path notation

Attribute path notation may also be used with multi-valued attributes, such as:

sp {blocks-world*propose*move-block
   (state <s> ^problem-space blocks
      ^clear.block <block1> { <> <block1> <block2> }
      ^ontop <ontop>)
   (<block1> ^type block)
   (<ontop> ^top-block <block1>
      ^bottom-block <> <block2>)
   -->
   (<s> ^operator <o> +)
   (<o> ^name move-block +
      ^moving-block <block1> +
      ^destination <block2> +) }
Multi-attributes and attribute-path notation

Note: It would not be advisable to write the production in Figure 3.2 using attribute-path notation as follows:

1
2
3
4
5
6
7
8
9
sp {blocks-world*propose*move-block*dont-do-this
   (state <s> ^problem-space blocks
      ^clear.block <block1>
      ^clear.block { <> <block1> <block2> }
      ^ontop.top-block <block1>
      ^ontop.bottom-block <> <block2>)
   (<block1> ^type block)
   -->
   ...}

This is not advisable because it corresponds to a different set of conditions than those in the original production (the top-block and bottom-block need not correspond to the same ontop relation). To check this, we could print the original production at the Soar prompt:

soar> print blocks-world*propose*move-block*dont-do-this
sp {blocks-world*propose*move-block*dont-do-this
   (state <s> ^problem-space blocks ^thing <thing2>
      ^thing { <> <thing2> <thing1> } ^ontop <o*1> ^ontop <o*2>)
   (<thing2> ^clear yes)
   (<thing1> ^clear yes ^type block)
   (<o*1> ^top-block <thing1>)
   (<o*2> ^bottom-block { <> <thing2> <b*1> })
   -->
   (<s> ^operator <o> +)
   (<o> ^name move-block
      ^moving-block <thing1>
      ^destination <thing2>) }

Soar has expanded the production into the longer form, and created two distinctive variables, <o*1> and <o*2> to represent the on top attribute. These two variables will not necessarily bind to the same identifiers in working memory.

Negated multi-valued attributes and attribute-path notation

Negations of multi-valued attributes can be combined with attribute-path notation. However; it is very easy to make mistakes when using negated multi-valued attributes with attribute-path notation. Although it is possible to do it correctly, we strongly discourage its use.

For example,

1
2
3
4
sp {blocks*negated-conjunction-example
   (state <s> ^name top-state -^ontop.bottom-object.name table A)
   -->
   (<s> ^nothing-ontop-A-or-table true) }

gets expanded to:

1
2
3
4
5
6
7
8
sp {blocks*negated-conjunction-example
   (state <s> ^name top-state)
   -{(<s> ^ontop <o*1>)
   (<o*1> ^bottom-object <b*1>)
   (<b*1> ^name A)
   (<b*1> ^name table)}
   -->
   (<s> ^nothing-ontop-A-or-table true) }

This example does not refer to two different blocks with different names. It tests that there is not a non top relation with a bottom-block that is named A and named table. Thus, this production probably should have been written as:

1
2
3
4
5
6
sp {blocks*negated-conjunction-example
   (state <s> ^name top-state
   -^ontop.bottom-object.name table
   -^ontop.bottom-object.name A)
   -->
   (<s> ^nothing-ontop-A-or-table true) }

which expands to:

sp {blocks*negated-conjunction-example
   (state <s> ^name top-state)
   -{(<s> ^ontop <o*2>)
     (<o*2> ^bottom-object <b*2>)
     (<b*2> ^name a)}
   -{(<s> ^ontop <o*1>)
     (<o*1> ^bottom-object <b*1>)
     (<b*1> ^name table)}
   -->
   (<s> ^nothing-ontop-a-or-table true +) }
Notes on attribute-path notation
  • Attributes specified in attribute-path notation may not start with a digit. For example, if you type ^foo.3.bar, Soar thinks the .3 is a floating-point number. (Attributes that don’t appear in path notation can begin with a number.)
  • Attribute-path notation may be used to any depth.
  • Attribute-path notation may be combined with structured values, described in Section Structured Value Notation.

Structured-value notation

Another convenience that eliminates the use of intermediate variables is structured-value notation. Syntactically, the attributes and values of a condition may be written where a variable would normally be written. The attribute-value structure is delimited by parentheses.

Using structured-value notation, the production in Figure 3.2 may also be written as:

sp {blocks-world*propose*move-block
   (state <s> ^problem-space blocks
      ^thing <thing1>
      ^thing {<> <thing1> <thing2>}
      ^ontop (^top-block <thing1>
      ^bottom-block <> <thing2>))
   (<thing1> ^type block ^clear yes)
   (<thing2> ^clear yes)
   -->
   (<s> ^operator <o> +)
   (<o> ^name move-block
      ^moving-block <thing1>
      ^destination <thing2>) }

Thus, several conditions may be "collapsed" into a single condition.

Using variables within structured-value notation

Variables are allowed within the parentheses of structured-value notation to specify an identifier to be matched elsewhere in the production. For example, the variable <ontop> could be added to the conditions (although it is not referenced again, so this is not helpful in this instance):

sp {blocks-world*propose*move-block
   (state <s> ^problem-space blocks
      ^thing <thing1>
      ^thing {<> <thing1> <thing2>}
      ^ontop (<ontop>
      ^top-block <thing1>
      ^bottom-block <> <thing2>))
   (<thing1> ^type block ^clear yes)
   (<thing2> ^clear yes)
   -->
   (<s> ^operator <o> +)
   (<o> ^name move-block
      ^moving-block <thing1>
      ^destination <thing2>) }

Structured values may be nested to any depth. Thus, it is possible to write our example production using a single condition with multiple structured values:

sp {blocks-world*propose*move-block
   (state <s> ^problem-space blocks
      ^thing <thing1>
   ({<> <thing1> <thing2>}
      ^clear yes)
      ^ontop (^top-block
   (<thing1>
      ^type block
      ^clear yes)
      ^bottom-block <> <thing2>) )
   -->
   (<s> ^operator <o> +)
   (<o> ^name move-block
      ^moving-block <thing1>
      ^destination <thing2>) }

Notes on structured-value notation

  • Attribute-path notation and structured-value notation are orthogonal and can be combined in any way. A structured value can contain an attribute path, or a structure can be given as the value for an attribute path.
  • Structured-value notation can be combined with negations and with multi-attributes.
  • Structured-value notation can not be used in the actions of productions.

The action side of productions (or RHS)

The action side of a production, also called the right-hand side (or RHS) of the production, consists of individual actions that can:

  • Add new elements to working memory.
  • Remove elements from working memory.
  • Create preferences.
  • Perform other actions

When the conditions of a production match working memory, the production is said to be instantiated, and the production will fire during the next elaboration cycle. Firing the production involves performing the actions using the same variable bindings that formed the instantiation.

Variables in Actions

Variables can be used in actions. A variable that appeared in the condition side will be replaced with the value that is was bound to in the condition. A variable that appears only in the action side will be bound to a new identifier that begins with the first letter of that variable (e.g., <o> might be bound to o234). This symbol is guaranteed to be unique and it will be used for all occurrences of the variable in the action side, appearing in all working memory elements and preferences that are created by the production action.

Creating Working Memory Elements

An element is created in working memory by specifying it as an action. Multiple augmentations of an object can be combined into a single action, using the same syntax as in conditions, including path notation and multi-valued attributes.

1
2
3
-->
(<s> ^block.color red
   ^thing <t1> <t2>) }

The action above is expanded to be:

1
2
3
4
5
-->
(<s> ^block <*b>)
(<*b> ^color red)
(<s> ^thing <t1>)
(<s> ^thing <t2>) }

This will add four elements to working memory with the variables replaced with whatever values they were bound to on the condition side.

Since Soar is case sensitive, different combinations of upper- and lowercase letters represent different constants. For example, "red", "Red", and "RED" are all distinct symbols in Soar. In many cases, it is prudent to choose one of uppercase or lowercase and write all constants in that case to avoid confusion (and bugs).

The constants that are used for attributes and values have a few restrictions on them:

  1. There are a number of architecturally created augmentations for state and impasse objects; see Section Impass in Working Memory and in Productions for a listing of these special augmentations. User-defined productions can not create or remove augmentations of states that use these attribute names.
  2. Attribute names should not begin with a number if these attributes will be used in attribute-path notation.

Removing Working Memory Elements

A element is explicitly removed from working memory by following the value with a dash: - , also called a reject.

-->
(<s> ^block <b> -)}

If the removal of a working memory element removes the only link between the state and working memory elements that had the value of the removed element as an identifier, those working memory elements will be removed. This is applied recursively, so that all item that become unlinked are removed.

The removal should be used with an action that will be o-supported. If removal is attempted with i-support, the working memory element will reappear if the removal loses i-support and the element still has support.

The syntax of preferences

Below are the eleven types of preferences as they can appear in the actions of a production for the selection of operators:

RHS preferences Semantics
(id ^operator value) acceptable
(id ^operator value +) acceptable
(id ^operator value !) require
(id ^operator value ~) prohibit
(id ^operator value -) reject
(id ^operator value > value2) better
(id ^operator value < value2) worse
(id ^operator value >) best
(id ^operator value <) worst
(id ^operator value =) unary indifferent
(id ^operator value = value2) binary indifferent
(id ^operator value = number) numeric indifferent

The identifier and value will always be variables, such as (<s1> ^operator <o1> > <o2>).

The preference notation appears similar to the predicate tests that appear on the left-hand side of productions, but has very different meaning. Predicates cannot be used on the right-hand side of a production and you cannot restrict the bindings of variables on the right-hand side of a production. (Such restrictions can happen only in the conditions.)

Also notice that the + symbol is optional when specifying acceptable preferences in the actions of a production, although using this symbol will make the semantics of your productions clearer in many instances. The + symbol will always appear when you inspect preference memory (with the preferences command).

Productions are never needed to delete preferences because preferences will be retracted when the production no longer matches. Preferences should never be created by operator application rules, and they should always be created by rules that will give only i-support to their actions.

Shorthand notations for preference creation

There are a few shorthand notations allowed for the creation of operator preferences on the right-hand side of productions.

Acceptable preferences do not need to be specified with a+symbol.(<s> ^operator <op1>) is assumed to mean (<s> ^operator <op1> +).

Note however that the+is only implicit if no other preferences are specified for that operator. Specifying a preference that is not the acceptable preference does not also imply an acceptable preference. For example, (<s> ^operator <op1> > ) by itself cannot lead to <op1> being selected, since it does not have an acceptable preference.

Ambiguity can easily arise when using a preference that can be either binary or unary: > < =. The default assumption is that if a value follows the preference, then the preference is binary. It will be unary if a carat (up-arrow), a closing parenthesis, another preference, or a comma follows it.

Below are four examples of legal, although unrealistic, actions that have the same effect.

1
2
3
4
5
6
(<s> ^operator <o1> <o2> + <o2> < <o1> <o3> =, <o4>)
(<s> ^operator <o1> + <o2> +
   <o2> < <o1> <o3> =, <o4> +)
(<s> ^operator <o1> <o2> <o2> < <o1> <o4> <o3> =)
(<s> ^operator <o1> ^operator <o2>
   ^operator <o2> < <o1> ^operator <o4> <o3> =)

Any one of those actions could be expanded to the following list of preferences:

1
2
3
4
5
(<s> ^operator <o1> +)
(<s> ^operator <o2> +)
(<s> ^operator <o2> < <o1>)
(<s> ^operator <o3> =)
(<s> ^operator <o4> +)

Note that structured-value notation may not be used in the actions of productions.

Commas are only allowed in rule syntax for this sort of use, in the RHS. They can be used to separate actions, and if used when no disambiguation is needed will have no effect other than syntactic sugar.

As another example, (<s> ^operator <o1> <o2> > <o3>) would be interpreted as

(<s> ^operator <o1> +
^operator <o2> > <o3>)

But (<s> ^operator <o1> <o2> >, <o3>) would be interpreted as

1
2
3
(<s> ^operator <o1> +
   ^operator <o2> >
   ^operator <o3> +)

Right-hand side Functions

The fourth type of action that can occur in productions is called a right-hand side function. Right-hand side functions allow productions to create side effects other than changing working memory. The RHS functions are described below, organized by the type of side effect they have.

Stopping and pausing Soar

halt — Terminates Soar’s execution and returns to the user prompt. A halt action irreversibly terminates the running of a Soar program. It should not be used if the agent is to be restarted (see the interrupt RHS action below.)

1
2
3
4
sp {
   ...
   -->
   (halt) }

interrupt — Executing this function causes Soar to stop at the end of the current phase, and return to the user prompt. This is similar to halt, but does not terminate the run. The run may be continued by issuing a run command from the user interface. The interrupt RHS function has the same effect as typing stop-soar at the prompt, except that there is more control because it takes effect exactly at the end of the phase that fires the production.

1
2
3
4
sp {
   ...
   -->
   (interrupt) }

Soar execution may also be stopped immediately before a production fires, using the :interrupt directive. This functionality is called a matchtime interrupt and is very useful for debugging. See 'sp' command for more information.

1
2
3
4
5
sp {production*name
   :interrupt
   ...
   -->
   ...}

wait — Executing this function causes the current Soar thread to sleep for the given integer number of milliseconds.

1
2
3
4
sp {
   ...
   -->
   (wait 1000) }

Note that use of this function is discouraged.

Text input and output

These functions are provided as production actions to do simple output of text in Soar. Soar applications that do extensive input and output of text should use Soar Markup Language (SML). To learn about SML, read the "SML Quick Start Guide" which should be located in the "Documentation" folder of your Soar install.

write — This function writes its arguments to the standard output. It does not automatically insert blanks, line feeds, or carriage returns. For example, if <o> is bound to 4, then

1
2
3
4
sp {
   ...
   -->
   (write <o> <o> <o> | x| <o> | | <o>) }

prints

444 x4 4

crlf — Short for "carriage return, line feed", this function can be called only within write. It forces a new line at its position in the write action.

1
2
3
4
sp {
   ...
   -->
   (write <x> (crlf) <y>) }

log — This function is equivalent to the write function, except that it specifies a "log channel" for output. The output will only show if that channel is active. The function takes two arguments. First is an integer corresponding to the channel level for output, second is the message to print. See the 'output` command for information about agent log channels.

1
2
3
4
sp {
   ...
   -->
   (log 3 |This only prints when agent-logs channel 3 is enabled.|) }
Mathematical functions

The expressions described in this section can be nested to any depth. For all of the functions in this section, missing or non-numeric arguments result in an error.

+, -, *, / — These symbols provide prefix notation mathematical functions. These symbols work similarly to C functions. They will take either integer or real-number arguments. The first three functions return an integer when all arguments are integers and otherwise return a real number, and the last two functions always return a real number. These functions can each take any number of arguments, and will return the result of sequentially operating on each argument. The - symbol is also a unary function which, given a single argument, returns the product of the argument and -1. The / symbol is also a unary function which, given a single argument, returns the reciprocal of the argument (1/x).

1
2
3
4
5
6
7
sp {
   ...
   -->
   (<s> ^sum (+ <x> <y>)
      ^product-sum (* (+ <v> <w>) (+ <x> <y>))
      ^big-sum (+ <x> <y> <z> 402)
      ^negative-x (- <x>))}

div, mod — These symbols provide prefix notation binary mathematical functions (they each take two arguments). These symbols work similarly to C functions: They will take only integer arguments (using reals results in an error) and return an integer: div takes two integers and returns their integer quotient; mod returns their remainder.

1
2
3
4
5
sp {
   ...
   -->
   (<s> ^quotient (div <x> <y>)
   ^remainder (mod <x> <y>)) }

abs, atan2, sqrt, sin, cos — These provide prefix notation unary mathematical functions (they each take one argument). These symbols work similarly to C functions:

They will take either integer or real-number arguments. The first function (abs) returns an integer when its argument is an integer and otherwise returns a real number, and the last four functions always return a real number. atan2 returns as a float in radians, the arctangent of (first_arg / second_arg).sin and cos take as arguments the angle in radians.

1
2
3
4
5
sp {
   ...
   -->
   (<s> ^abs-value (abs <x>)
      ^sqrt (sqrt <x>)) }

min, max — These symbols provide n-ary mathematical functions (they each take a list of symbols as arguments). These symbols work similarly to C functions. They take either integer or real-number arguments, and return a real-number value if any of their arguments are real-numbers. Otherwise they return integers.

1
2
3
4
5
sp {
   ...
   -->
   (<s> ^max (max <x> 3.14 <z>)
      ^min (min <a> <b> 42 <c>)) }

int — Converts a single symbol to an integer constant. This function expects either an integer constant, symbolic constant, or floating point constant. The symbolic constant must be a string which can be interpreted as a single integer. The floating point constant is truncated to only the integer portion. This function essentially operates as a type casting function. For example, the expression \(2 + \sqrt(6)\) could be printed as an integer using the following:

1
2
3
4
sp {
   ...
   -->
   (write (+ 2 (int sqrt(6))) ) }

float — Converts a single symbol to a floating point constant. This function expects either an integer constant, symbolic constant, or floating point constant. The symbolic constant must be a string which can be interpreted as a single floating point number. This function essentially operates as a type casting function. For example, if you wanted to print out an integer expression as a floating-point number, you could do the following:

1
2
3
4
sp {
   ...
   -->
   (write (float (+ 2 3))) }

ifeq — Conditionally return a symbol. This function takes four arguments. It returns the third argument if the first two are equal and the fourth argument otherwise. Note that symbols of different types will always be considered unequal. For example, 1.0 and 1 will be unequal because the first is a float and the second is an integer.

1
2
3
4
5
sp {example-rule
   (state <s> ^a <a> ^b <b>)
   ...
   -->
   (write (ifeq <a> <b> equal not-equal)) }
Generating and manipulating symbols

A new symbol (an identifier) is generated on the right-hand side of a production whenever a previously unbound variable is used. This section describes other ways of generating and manipulating symbols on the right-hand side.

capitalize-symbol — Given a symbol, this function returns a new symbol with the first character capitalized. This function is provided primarily for text output, for example, to allow the first word in a sentence to be capitalized.

(capitalize-symbol foo)

compute-heading — This function takes four real-valued arguments of the form \((x_1, y_1, x_2, y_2)\), and returns the direction (in degrees) from \((x_1, y_1)\) to \((x_2, y_2)\), rounded to the nearest integer.

For example:

1
2
3
4
sp {
   ...
   -->
   (<s> ^heading (compute-heading 0 0.5 32.5 28)) }

After this rule fires, working memory would look like:

(S1 ^heading 48).

compute-range — This function takes four real-valued arguments of the form \((x_1, y_1, x_2, y_2)\), and returns the distance from \((x_1, y_1)\) to \((x_2, y_2)\), rounded to the nearest integer.

For example:

1
2
3
4
sp {
   ...
   -->
   (<s> ^distance (compute-range 0 0.5 32.5 28)) }

After this rule fires, working memory would look like:

(S1 ^distance 42).

concat — Given an arbitrary number of symbols, this function concatenates them together into a single constant symbol. For example:

1
2
3
4
sp {example
   (state <s> ^type state)
   -->
   (<s> ^name (concat foo bar (+ 2 4))) }

After this rule fires, the WME (S1 ^name foobar6) will be added.

deep-copy — This function returns a copy of the given symbol along with linked copies of all descendant symbols. In other terms, a full copy is made of the working memory subgraph that can be reached when starting from the given symbol. All copied identifiers are created as new IDs, and all copied values remain the same. For example:

1
2
3
4
5
6
sp {
   (state <s> ^tree <t>)
   (<t> ^branch1 foo ^branch2 <b>)
   (<b> ^branch3 <t>)
   -->
   (<s> ^tree-copy (deep-copy <t>)) }

After this rule fires, the following structure would exist:

1
2
3
4
5
(S1 ^tree T1 ^tree-copy D1)
   (T1 ^branch1 foo ^branch2 B1)
      (B1 ^branch3 T1)
(D1 ^branch1 foo ^branch2 B2)
   (B2 ^branch3 D1)

dc — This function takes no arguments, and returns the integer number of the current decision cycle. For example:

1
2
3
4
sp {example
   (state <s> ^type state)
   -->
   (<s> ^dc-count (dc) }

@ (get) — This function returns the LTI number of the given ID. If the given ID is not linked to an LTI, it does nothing. For example:

1
2
3
4
sp {example
   (state <s> ^stm <l1>)
   -->
   (<s> ^lti-num (@ <l1>) }

After this rule fires, the (S1 ^lti-num) WME will have an integer value such as 42.

link-stm-to-ltm — This function takes two arguments. It links the first given symbol to the LTI indicated by the second integer value.

For example:

1
2
3
4
sp {example
   (state <s> ^stm <l1>)
   -->
   (link-stm-to-ltm <l1> 42) }

After this rule fires, the WME (S1 ^stm <l1>) will be linked to @42.

make-constant-symbol — This function returns a new constant symbol guaranteed to be different from all symbols currently present in the system. With no arguments, it returns a symbol whose name starts with "constant". With one or more arguments, it takes those argument symbols, concatenates them, and uses that as the prefix for the new symbol. (It may also append a number to the resulting symbol, if a symbol with that prefix as its name already exists.)

1
2
3
4
sp {
   ...
   -->
   (<s> ^new-symbol (make-constant-symbol)) }

When this production fires, it will create an augmentation in working memory such as:

(S1 ^new-symbol constant5)

The production:

1
2
3
4
sp {
   ...
   -->
   (<s> ^new-symbol (make-constant-symbol <s> )) }

will create an augmentation in working memory such as:

(S1 ^new-symbol |S14|)

when it fires. The vertical bars denote that the symbol is a constant, rather than an identifier; in this example, the number 4 has been appended to the symbol S1. This can be particularly useful when used in conjunction with the timestamp function; by using timestamp as an argument to make-constant-symbol, you can get a new symbol that is guaranteed to be unique. For example:

1
2
3
4
sp {
   ...
   -->
   (<s> ^new-symbol (make-constant-symbol (timestamp))) }

When this production fires, it will create an augmentation in working memory such as:

(S1 ^new-symbol 8/1/96-15:22:49)

rand-float — This function takes an optional positive real-valued argument. If no argument (or a negative argument) is given, it returns a random real-valued number in the range \([0. 0 , 1 .0]\). Otherwise, given a value n, it returns a number in the range \([0. 0 ,n]\).

For example:

1
2
3
4
sp {
   ...
   -->
   (<s> ^fate (rand-float 1000)) }

After this rule fires, working memory might look like: (S1 ^fate 275.481802).

rand-int — This function takes an optional positive integer argument. If no argument (or a negative argument) is given, it returns a random integer number in the range \([-2^{31} , 2^{31}]\). Otherwise, given a value n, it returns a number in the range \([0,n]\). For example:

1
2
3
4
sp {
   ...
   -->
   (<s> ^fate (rand-int 1000)) }

After this rule fires, working memory might look like: (S1 ^fate 13).

round-off — This function returns the first given value rounded to the nearest multiple of the second given value. Values must be integers or real-numbers.

For example:

1
2
3
4
sp {
   (state <s> ^pi <pi>
   -->
   (<s> ^pie (round-off <pi> 0.1)) }

After this rule fires, working memory might look like: (S1 ^pi 3.14159 ^pie 3.1).

round-off-heading — This function is the same as round-off, but additionally shifts the returned value by multiples of 360 such that \(-360 \le value \le 360\). For example:

1
2
3
4
sp {
   (state <s> ^heading <dir>
   -->
   (<s> ^true-heading (round-off-heading <dir> 0.5)) }

After this rule fires, working memory might look like: (S1 ^heading 526.432 ^true-heading 166.5).

size — This function returns an integer symbol whose value is the count of WME augmentations on a given ID argument. Providing a non-ID argument results in an error. For example:

1
2
3
4
5
sp {
   (state <s> ^numbers <n>)
   (<n> ^1 1 ^10 10 ^100 100)
   -->
   (<s> ^augs (size <n>)) }

After this rule fires, the value of S1 ^augs would be 3. Note that some architecturally-maintained IDs such as (<s> ^epmem) and (<s> ^io) are not counted by the size function.

strlen — This function returns an integer symbol whose value is the size of the given string symbol. For example:

1
2
3
4
5
sp {
   (state <s> ^io.input-link.message <m>)
   ...
   -->
   (<s> ^message-len (strlen <m>)) }

timestamp — This function returns a symbol whose print name is a representation of the current date and time. For example:

1
2
3
4
sp {
   ...
   -->
   (write (timestamp)) }

When this production fires, it will print out a representation of the current date and time, such as:

soar> run 1 e
2018-09-26 14:36:39.375

trim — This function takes a single string symbol argument and returns the same string with leading and trailing whitespace removed. For example:

1
2
3
4
sp {
   (state <s> ^message <m>)
   -->
   (<s> ^trimmed (trim <m>)) }
User-defined functions and interface commands as RHS actions

Any function with a certain signature may be registered with the Kernel (e.g. using SML) and called as an RHS function. RHS functions receive a single string argument and return a single string which is assigned to a symbol in Soar.

RHS functions are most complex in C/C++ due to the requirements of memory management. An RHS function must adhere to the signature of RHSEventHandler:

1
2
3
char const _(smlRhsEventId id, void_ pUserData, Agent* pAgent,
char const* pFunctionName, char const* pArgument,
int *buffSize, char *buff)

The function must fill *buff with the string to be returned and then return *buff, but only if *buffSize indicates there is enough room to hold the string. If *buffSize is not large enough, the function must return NULL and set *buffSize to the required size. Soar will then allocate a buffer of the required size and call the function again. If calling the function twice would have an undesirable effect, the following code can be used to cache the result in between calls by Soar:

// at beginning of function:
static std::string prevResult;
if ( !prevResult.empty() )
{
   strncpy( buf, prevResult.c_str(), *bufSize );
   prevResult = "";
   return buf;
}

// ...

// at end of function:
if ( resultString.length() + 1 > *bufSize )
{
   *bufSize = resultString.length() + 1;
   prevResult = resultString;
   return NULL;
}
strcpy( buf, resultString.c_str() );
return buf;

RHS function interfaces in other languages are much simpler. For example, in Java the signature is:

String rhsFunctionHandler(int eventID, Object data, String agentName,
   String functionName, String argument)

Any arguments passed to the function on the RHS of a production are concatenated (without spaces) and passed to the function in the pArgument argument.

Such a function can be registered with the kernel via the client interface by calling:

Kernel::AddRhsFunction(char const* pRhsFunctionName, RhsEventHandler handler, void* pUserData);
Kernel.AddRhsFunction(String functionName, RhsFunctionInterface handlerObject, Object callbackData);

The exec and cmd functions are used to call user-defined functions and interface commands on the RHS of a production.

exec — Used to call user-defined registered functions. Any arguments are concatenated without spaces. For example, if <o> is bound to x, then

1
2
3
4
sp {
   ...
   -->
   (exec MakeANote <o> 1) }

will call the user-defined MakeANote function with the argument "x1". The return value of the function, if any, may be placed in working memory or passed to another RHS function. For example, the log of a number <x> could be printed this way:

1
2
3
4
sp {
   ...
   -->
   (write |The log of | <x> | is: | (exec log(<x>))|) }

where "log" is a registered user-defined function.

cmd — Used to call built-in Soar commands. Spaces are inserted between concatenated arguments. For example, the production

1
2
3
4
sp {
   ...
   -->
   (write (cmd print --depth 2 <s>)) }

will have the effect of printing the object bound to <s> to depth 2.

Controlling chunking

Chunking is described in Chapter 4.

The following two functions are provided as RHS actions to assist in development of Soar programs; they are not intended to correspond to any theory of learning in Soar. This functionality is provided as a development tool, so that learning may be turned off in specific problem spaces, preventing otherwise buggy behavior.

The dont-learn and force-learn RHS actions are to be used with specific settings for the chunk command. Using the chunk command, learning may be set to one of always, never, flagged, or unflagged; chunking must be set to flagged for the force-learn RHS action to have any effect and chunking must be set to unflagged for the dont-learn RHS action to have any effect.

dont-learn — When chunking is set to unflagged, by default chunks can be formed in all states; the dont-learn RHS action will cause chunking to be turned off for the specified state.

1
2
3
4
sp {turn-learning-off
   (state <s> ^feature 1 ^feature 2 -^feature 3)
   -->
   (dont-learn <s>) }

The dont-learn RHS action applies when chunk is set to unflagged, and has no effect when other settings for chunk are used.

force-learn — When learning is set to flagged, by default chunks are not formed in any state; the force-learn RHS action will cause chunking to be turned on for the specified state.

1
2
3
4
sp {turn-learning-on
   (state <s> ^feature 1 ^feature 2 -^feature 3)
   -->
   (force-learn <s>) }

The force-learn RHS action applies when chunk is set to flagged, and has no effect when other settings for chunk are used.

Grammars for production syntax

This subsection contains the BNF grammars for the conditions and actions of productions. (BNF stands for Backus-Naur form or Backus normal form; consult a computer science book on theory, programming languages, or compilers for more information. However, if you don’t already know what a BNF grammar is, it’s unlikely that you have any need for this subsection.)

This information is provided for advanced Soar users, for example, those who need to write their own parsers. Note that some terms (e.g.<symconstant>) are undefined; as such, this grammar should only be used as a starting point.

Grammar of Soar productions

A grammar for Soar productions is:

1
2
3
4
<soar-production>  ::= sp "{" <production-name> [<documentation>] [<flags>]
<condition-side> --> <action-side> "}"
<documentation>    ::= """ [<string>] """
<flags>            ::= ":" (o-support | i-support | chunk | default)

Grammar for Condition Side: Below is a grammar for the condition sides of productions:

<condition-side>   ::= <state-imp-cond> <cond>*
<state-imp-cond>   ::= "(" (state | impasse) [<id_test>]
<attr_value_tests>+ ")"
<cond>             ::= <positive_cond> | "-" <positive_cond>
<positive_cond>    ::= <conds_for_one_id> | "{" <cond>+ "}"
<conds_for_one_id> ::= "(" [(state|impasse)] <id_test>
<attr_value_tests>+ ")"
<id_test>          ::= <test>
<attr_value_tests> ::= ["-"] "^" <attr_test> ("." <attr_test>)*
<value_test>*
<attr_test>        ::= <test>
<value_test>       ::= <test> ["+"] | <conds_for_one_id> ["+"]

<test>             ::= <conjunctive_test> | <simple_test>
<conjunctive_test> ::= "{" <simple_test>+ "}"
<simple_test>      ::= <disjunction_test> | <relational_test>
<disjunction_test> ::= "<<" <constant>+ ">>"
<relational_test>  ::= [<relation>] <single_test>
<relation>         ::= "<>" | "<" | ">" | "<=" | ">=" | "=" | "<=>"
<single_test>      ::= <variable> | <constant>
<variable>         ::= "<" <sym_constant> ">"
<constant>         ::= <sym_constant> | <int_constant> | <float_constant>

Notes on the Condition Side

  • In an <idtest>, only a <variable> may be used in a <singletest>.

Grammar for Action Side: Below is a grammar for the action sides of productions:

<rhs>                      ::= <rhs_action>*
<rhs_action>               ::= "(" <variable> <attr_value_make>+ ")"
| <func_call>
<func_call>                ::= "(" <func_name> <rhs_value>* ")"
<func_name>                ::= <sym_constant> | "+" | "-" | "*" | "/"
<rhs_value>                ::= <constant> | <func_call> | <variable>
<attr_value_make>          ::= "^" <variable_or_sym_constant>
("." <variable_or_sym_constant>)* <value_make>+
<variable_or_sym_constant> ::= <variable> | <sym_constant>
<value_make>               ::= <rhs_value> <preference_specifier>*

<preference-specifier>     ::= <unary-preference> [","]
| <unary-or-binary-preference> [","]
| <unary-or-binary-preference> <rhs_value> [","]
<unary-pref>               ::= "+" | "-" | "!" | "~"
<unary-or-binary-pref>     ::= ">" | "=" | "<"

Impasses in Working Memory and in Productions

When the preferences in preference memory cannot be resolved unambiguously, Soar reaches an impasse, as described in Section the Soar architecture:

  • When Soar is unable to select a new operator (in the decision cycle), it is said to reach an operator impasse.

All impasses lead to the creation of a new substate in working memory, and appear as objects within that substate. These objects can be tested by productions. This section describes the structure of state objects in working memory.

Impasses in working memory

There are four types of impasses.

Below is a short description of the four types of impasses. (This was described in more detail in Section impasses and substates)

  1. tie: when there is a collection of equally eligible operators competing for the value of a particular attribute;
  2. conflict: when two or more objects are better than each other, and they are not dominated by a third operator;
  3. constraint-failure: when there are conflicting necessity preferences;
  4. no-change: when the proposal phase runs to quiescence without suggesting a new operator.

The list below gives the seven augmentations that the architecture creates on the substate generated when an impasse is reached, and the values that each augmentation can contain:

  • ^type state
  • ^impasse Contains the impasse type: tie, conflict, constraint-failure, or no-change.

  • ^choices Either multiple (for tie and conflict impasses), constraint-failure (for constraint-failure impasses), or none (for constraint-failure or no-change impasses).

  • ^superstate Contains the identifier of the state in which the impasse arose.

  • ^attribute For multi-choice and constraint-failure impasses, this contains operator. For no-change impasses, this contains the attribute of the last decision with a value (state or operator).

  • ^item For multi-choice and constraint-failure impasses, this contains all values involved in the tie, conflict, or constraint-failure. If the set of items that tie or conflict changes during the impasse, the architecture removes or adds the appropriate item augmentations without terminating the existing impasse.

  • ^item-count For multi-choice and constraint-failure impasses, this contains the number of values listed under the item augmentation above.

  • ^non-numeric For tie impasses, this contains all operators that do not have numeric indifferent preferences associated with them. If the set of items that tie changes during the impasse, the architecture removes or adds the appropriate non-numeric augmentations without terminating the existing impasse.

  • ^non-numeric-count For tie impasses, this contains the number of operators listed under the non-numeric augmentation above.

  • ^quiescence States are the only objects with quiescence t, which is an explicit statement that quiescence (exhaustion of the elaboration cycle) was reached in the superstate. If problem solving in the subgoal is contingent on quiescence having been reached, the substate should test this flag. The side-effect is that no chunk will be built if it depended on that test. See Problem Solving that does not test Superstate for details. This attribute can be ignored when learning is turned off.

Knowing the names of these architecturally defined attributes and their possible values will help you to write productions that test for the presence of specific types of impasses so that you can attempt to resolve the impasse in a manner appropriate to your program. Many of the default productions in the demos/defaults directory of the Soar distribution provide means for resolving certain types of impasses. You may wish to make use of some of all of these productions or merely use them as guides for writing your own set of productions to respond to impasses.

Examples

The following is an example of a substate that is created for a tie among three operators:

(S12 ^type state ^impasse tie ^choices multiple ^attribute operator
   ^superstate S3 ^item O9 O10 O11 ^quiescence t)

The following is an example of a substate that is created for a no-change impasse to apply an operator:

1
2
3
(S12 ^type state ^impasse no-change ^choices none ^attribute operator
   ^superstate S3 ^quiescence t)
(S3 ^operator O2)

Testing for impasses in productions

Since states appear in working memory, they may also be tested for in the conditions of productions.

For example, the following production tests for a constraint-failure impasse on the top-level state.

sp {default*top-goal*halt*operator*failure
   "Halt if no operator can be selected for the top goal."
   :default
   (state <ss> ^impasse constraint-failure ^superstate <s>)
   (<s> ^superstate nil)
   -->
   (write (crlf) |No operator can be selected for top goal.| )
   (write (crlf) |Soar will halt now. Goodnight.| )
   (halt)
   }

Soar I/O: Input and Output in Soar

Many Soar users will want their programs to interact with a real or simulated environment. For example, Soar programs could control a robot, receiving sensory inputs and sending command outputs. Soar programs might also interact with simulated environments, such as a flight simulator. The mechanisms by which Soar receives inputs and sends outputs to an external process is called Soar I/O.

This section describes how input and output are represented in working memory and in productions. Interfacing with a Soar agent through input and output can be done using the SML. The details of designing an external process that uses SML to create the input and respond to output from Soar are beyond the scope of this manual, but they are described in the SML quick start guide. This section is provided for the sake of Soar users who will be making use of a program that has already been implemented, or for those who would simply like to understand how I/O works in Soar.

Overview of Soar I/O

When Soar interacts with an external environment, it must make use of mechanisms that allow it to receive input from that environment and to effect changes in that environment. An external environment may be the real world or a simulation; input is usually viewed as Soar’s perception and output is viewed as Soar’s motor abilities.

Soar I/O is accomplished via input functions and output functions. Input functions are called at the start of every execution cycle, and add elements directly to specific input structures in working memory. These changes to working memory may change the set of productions that will fire or retract. Output functions are called at the end of every execution cycle and are processed in response to changes to specific output structures in working memory. An output function is called only if changes have been made to the output-link structures in working memory.

The structures for manipulating input and output in Soar are linked to a predefined attribute of the top-level state, called the io attribute. The io attribute has substructure to represent sensor inputs from the environment called input links; because these are represented in working memory, Soar productions can match against input links to respond to an external situation. Likewise, the io attribute has substructure to represent motor commands, called output links. Functions that execute motor commands in the environment use the values on the output links to determine when and how they should execute an action. Generally, input functions create and remove elements on the input link to update Soar’s perception of the environment. Output functions respond to values of working memory elements that appear on Soar’s output link structure.

Input and output in working memory

All input and output is represented in working memory as substructure of the io attribute of the top-level state. By default, the architecture creates an input-link attribute of the io object and an output-link attribute of the io object. The values of the input-link and output-link attributes are identifiers whose augmentations are the complete set of input and output working memory elements, respectively. Some Soar systems may benefit from having multiple input and output links, or that use names which are more descriptive of the input or output function, such as vision-input-link, text-input-link, or motor-output-link. In addition to providing the default io substructure, the architecture allows users to create multiple input and output links via productions and I/O functions. Any identifiers for io substructure created by the user will be assigned at run time and are not guaranteed to be the same from run to run. Therefore users should always employ variables when referring to input and output links in productions.

Suppose a blocks-world task is implemented using a robot to move actual blocks around, with a camera creating input to Soar and a robotic arm executing command outputs.

The camera image might be analyzed by a separate vision program; this program could have as its output the locations of blocks on an xy plane. The Soar input function could take the output from the vision program and create the following working memory elements on the input link (all identifiers are assigned at runtime; this is just an example of possible bindings):

(S1 ^io I1)          [A]
(I1 ^input-link I2)  [A]
(I2 ^block B1)
(I2 ^block B2)
(I2 ^block B3)
(B1 ^x-location 1)
(B1 ^y-location 0)
(B1 ^color red)
(B2 ^x-location 2)
(B2 ^y-location 0)
(B2 ^color blue)
(B3 ^x-location 3)
(B3 ^y-location 0)
(B3 ^color yellow)
An example portion of the input link for the blocks-world task.

The ’[A]’ notation in the example is used to indicate the working memory elements that are created by the architecture and not by the input function. This configuration of blocks corresponds to all blocks on the table, as illustrated in the initial state in Figure 2.2.

Then, during the Apply Phase of the execution cycle, Soar productions could respond to an operator, such as "move the red block ontop of the blue block" by creating a structure on the output link, such as:

1
2
3
4
5
6
7
8
9
(S1 ^io I1)          [A]
(I1 ^output-link I3) [A]
(I3 ^name move-block)
(I3 ^moving-block B1)
(I3 ^x-destination 2)
(I3 ^y-destination 1)
(B1 ^x-location 1)
(B1 ^y-location 0)
(B1 ^color red)
An example portion of the output link for the blocks-world task.

An output function would look for specific structure in this output link and translate this into the format required by the external program that controls the robotic arm. Movement by the robotic arm would lead to changes in the vision system, which would later be reported on the input-link.

Input and output are viewed from Soar’s perspective. An input function adds or deletes augmentations of the input-link providing Soar with information about some occurrence external to Soar. An output function responds to substructure of the output-link produced by production firings, and causes some occurrence external to Soar. Input and output occur through the io attribute of the top-level state exclusively.

Structures placed on the input-link by an input function remain there until removed by an input function. During this time, the structure continues to provide support for any production that has matched against it. The structure does not cause the production to rematch and fire again on each cycle as long as it remains in working memory; to get the production to refire, the structure must be removed and added again.

Input and output in production memory

Productions involved in input will test for specific attributes and values on the input-link, while productions involved in output will create preferences for specific attributes and values on the output link. For example, a simplified production that responds to the vision input for the blocks task might look like this:

sp {blocks-world*elaborate*input
   (state <s> ^io.input-link <in>)
   (<in> ^block <ib1>)
   (<ib1> ^x-location <x1> ^y-location <y1>)
   (<in> ^block {<ib2> <> <ib1>})
   (<ib2> ^x-location <x1> ^y-location {<y2> > <y1>})
   -->
   (<s> ^block <b1>)
   (<s> ^block <b2>)
   (<b1> ^x-location <x1> ^y-location <y1> ^clear no)
   (<b2> ^x-location <x1> ^y-location <y2> ^above <b1>)
}

This production "copies" two blocks and their locations directly to the top-level state. It also adds information about the relationship between the two blocks. The variables used for the blocks on the RHS of the production are deliberately different from the variable name used for the block on the input-link in the LHS of the production. If the variable were the same, the production would create a link into the structure of the input-link, rather than copy the information. The attributes x-location and y-location are assumed to be values and not identifiers, so the same variable names may be used to do the copying.

A production that creates WMEs on the output-link for the blocks task might look like this:

1
2
3
4
5
6
7
8
9
sp {blocks-world*apply*move-block*send-output-command
   (state <s> ^operator <o> ^io.output-link <out>)
   (<o> ^name move-block ^moving-block <b1> ^destination <b2>)
   (<b1> ^x-location <x1> ^y-location <y1>)
   (<b2> ^x-location <x2> ^y-location <y2>)
   -->
   (<out> ^move-block <b1>
      ^x-destination <x2> ^y-destination (+ <y2> 1))
   }

This production would create substructure on the output-link that the output function could interpret as being a command to move the block to a new location.