🚧 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:
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
orO22
.
(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:
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:
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:
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:
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:
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.
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:
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 O53
is linked to X44
(because O53
is linked
toO43
and O43
is linked to X44
). However, since links are not symmetric,
X44
is 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,
is a preference that asserts that operator O3 is an acceptable
operator for
state S1, while
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.
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.
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:
- The name of the task or goal (e.g.,blocks-world).
- The name of the architectural function (e.g.,propose).
- The name of the operator (or other object) at issue. (e.g.,move-block)
- 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:
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.
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.
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.
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¶
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".
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:
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
.
A negation can be used within an object with many attribute-value pairs by having it precede a specific attribute:
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:
would match only if there is no object in working memory that matches all three attribute-value tests.
Example Production¶
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:
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:
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.
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)\):
can be rewritten as:
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:
could also be written as:
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.
To do tests for multi-valued attributes with variables correctly, conjunctive tests must be used, as in:
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
is the same as
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.
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):
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:
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:
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:
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:
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,
is interpreted to mean:
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:
could be written as:
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:
could be rewritten as:
Multi-valued attributes and attribute path notation¶
Attribute path notation may also be used with multi-valued attributes, such as:
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:
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 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,
gets expanded to:
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:
which expands to:
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:
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):
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:
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.
The action above is expanded to be:
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:
- 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.
- 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.
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.
Any one of those actions could be expanded to the following list of preferences:
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
But (<s> ^operator <o1> <o2> >, <o3>)
would be interpreted as
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.)
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.
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.
wait — Executing this function causes the current Soar thread to sleep for the given integer number of milliseconds.
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
prints
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.
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.
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)
.
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.
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.
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.
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:
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:
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.
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.
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:
After this rule fires, working memory would look like:
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:
After this rule fires, working memory would look like:
concat — Given an arbitrary number of symbols, this function concatenates them together into a single constant symbol. For example:
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:
After this rule fires, the following structure would exist:
dc — This function takes no arguments, and returns the integer number of the current decision cycle. For example:
@ (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:
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:
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.)
When this production fires, it will create an augmentation in working memory such as:
The production:
will create an augmentation in working memory such as:
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:
When this production fires, it will create an augmentation in working memory such as:
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:
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:
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:
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:
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:
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:
timestamp — This function returns a symbol whose print name is a representation of the current date and time. For example:
When this production fires, it will print out a representation of the current date and time, such as:
trim — This function takes a single string symbol argument and returns the same string with leading and trailing whitespace removed. For example:
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
:
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:
RHS function interfaces in other languages are much simpler. For example, in Java the signature is:
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:
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
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:
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
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.
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.
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:
Grammar for Condition Side: Below is a grammar for the condition sides of productions:
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:
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)
- tie: when there is a collection of equally eligible operators competing for the value of a particular attribute;
- conflict: when two or more objects are better than each other, and they are not dominated by a third operator;
- constraint-failure: when there are conflicting necessity preferences;
- 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
, orno-change
. -
^choices
Eithermultiple
(for tie and conflict impasses),constraint-failure
(for constraint-failure impasses), ornone
(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 containsoperator
. For no-change impasses, this contains the attribute of the last decision with a value (state
oroperator
). -
^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 withquiescence 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:
The following is an example of a substate that is created for a no-change impasse to apply an operator:
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.
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):
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:
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:
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:
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.