This document is the reference manual for the Pamela language (Probabilistic Advanced Modeling and Execution Learning Architecture).
For background information on Pamela please see the primary README
For the Pamela EBNF grammar please see the Pamela grammar
The Pamela language is intended as a declarative language for expressing domain models which can be “executed” with a planner and potentially augmented through machine learning.
Pamela source code is saved in files with the extension .pamela
(by convention). The “flavor” of the syntax corresponds to that
of Clojure - a modern LISP.
Pamela classes may be read from a file, from standard input, from a string, retrieved from the model database or entered in the Clojure REPL.
The pamela executable supports 2 primary operations: build and htn. The build operation causes the Pamela to be read and translated into a rich Intermediate Representation (IR) that can easily be manipulated by software components. The htn operation takes Pamela along with a specified root task, and then generates both a hierarchical task network (HTN) and a temporal plan network (TPN) that can be executed by a Pamela planner and execution environment.
The defpclass command is the primary top level Pamela construct which
defines a Pamela class and defines a model symbol which is
bound to a the class constructor method.
(defpclass psw [gnd pwr]
:meta {:version "0.2.1"
:depends [[pwrvals "0.2.0"]]
:doc "Power Switch"}
:fields {TP1 gnd
TP2 pwr
pwr (mode-of pwrvals :none)}
:modes {:on (= :pwr :high)
:off (= :pwr :none)
:fail true}
:methods [(defpmethod turn-on
{:pre :off :post :on :bounds [1 3]
:doc "turns on the power supply"}
[])
(defpmethod turn-off
{:pre :on :post :off :bounds [1 3]
:doc "turns off the power supply"}
[])
(defpmethod reset
{:post :off
:doc "resets the power supply"}
[])]
:transitions {:off:on {:pre :off :post :on
:doc "turning on"}
:on:off {:pre :on :post :off
:doc "turning off"}
:*:fail {:probability 0.0000001
:doc "spontaneous switch failure"}})
The first argument to defpclass is the Pamela class symbol (pclass)
being defined.
Then, there is a vector formal arguments to the pclass (which may be empty).
The pclass is then defined by a series of options: each introduced with a keyword and elaborated with a map.
The :meta map contains optional metadata for the Pamela class, with the following entries:
The :inherit option specifies a vector of Pamela superclasses. The idea is that Pamela classes may subclass a parent class for code reuse and specialization. Pamela superclass inheritance is not yet implemented.
NOTE: :inherit may be folded in with :meta in the future.
Each field is like member data for the pclass and may refer to another pclass instance, an LVar or a value. Simple field values may be a boolean, string, keyword or a number.
A field is defined with a map of
If the default values for :access and :observable are acceptable then the initial field value may be used by itself (instead of in a subordinate map containing a single :initial key and value).
Arguments to a pclass constructor may be simple values, including keywords (except for :id and :interface), or symbols. If a symbol is used in a pclass constructor it may refer to a previously defined field (but as a symbol, not a keyword) or an argument passed into this pclass.
Options to a pclass constructor may include:
Each pclass instance is always in one of a set of enumerated modes.
The modes for a pclass can be specified in one of two ways:
A mode-condition is a conditional expression, that when evaluated
to true, means that the given mode is possible for this pclass
instance. Certain modes (e.g. failure modes or enumerations)
may have the trivial condition expression true which means that
the given mode is always possible.
A conditional expression may be comprised, recursively, of the following:
(and a b c...)(or a b c...)(implies a b c...)(not a)(= d e)(> h j)(>= h j)(< h j)(<= h j)(same f g)(call "foo/bar" d e)(propositions [([wm ltm (recency 5)] :is-connected-to a b) ...]
where conditional-expression)In the case of call conditional expressions, the first argument ("foo/bar" in this example) specifies the name of a Clojure (or Java) function that can be dynamically called with the remaining arguments, in order to determine the truthy value of the condition.
For two objects d and e, (= d e) is true if the two objects are believed to have the same mode. If d and e are objects of a switch class that has modes on and off, (= d e) is true if both switches are on or if both switches are off. (same f g) is true if f and g are the same switch. This is an essential form of comparison for retrieving objects linked by propositions. The inequalities <, <=, >, >= can be used to compare numbers in pre and post conditions.
A proposition has the form (:proposition-type arg1 … argn), the most common of which is the two argument case. It is useful to lookup propositions in the preconditions of methods.
For example, to find a man who is married to a woman who was born in the same town as him, we can use the following:
(propositions [(:is-a X man)
(:is-married-to X Y)
(:is-a Y woman)]
where (same X.city-of-birth Y.city-of-birth))
Note that in this case if city-of-birth is a simple string, like “Boston”, (= X.city-of-birth Y.city-of-birth) would poduce the same result as (same ...) but if the cities were objects that had fields and modes, the = would only compare their modes.
Cities tend not to be very dynamic, but let’s say that the mode of a city were to represent whether the city was dry or not, let’s say that cities were modeled to be :dry :wet :dry-on-weekends. The law could change based on voting of the local government.
In this case using (= X.city-of-birth Y.city-of-birth) would be true for any two cities that were both dry, wet, or dry-on-weekends. (same …) would be true if and only if the X and Y were the same object – and hence the same city. Propositions, listed within square brackets, can be proceeded with advice on where to find the proposition. Three constraints can be provided, ‘wm’ indicating working memory, ‘ltm’ indicating long-term memory, perhaps implemented in an object base, and (recency n) which limits the search to propositions that are no older than n, so to match a man, in long-term memory, which proposition is no older than n one would write: (propositions [(ltm (recency n)) :is-a X man] ...). It should be noted that the ‘where’ condition cannot be used to match values other than those in the objects that make up the propositions. The ‘where’ is used to narrow the search for propositions and thus must be applied only to proposition objects.
The proposition form returns true or false but any LVARS bound in the process remain bound for the duration of the method. In the above examples, if the class definition that contained the method had a field [... X (lvar "a man") ...] the LVAR Xwould be bound to one such instance found in memory that satisfies any where condition. If multiple matching propositions are found, one will be chosen randomly.
:pre conditions of a pmethod, the condition is implicitly tested to be true prior to the actual invocation of the method.:post conditions of a pmethod, the condition is implicitly asserted at the end of the actual invocation of the method. The post condition represents a guarantee of the successful application of the method.Each operand is disambiguated based on type as follows:
(mode-of this :mode-name).X.fieldname1 where X may be one of the fields of the root class using arbitrary dot references. When a field is a an instance of a pclass the field reference is understood to refer to the mode of that pclass instance but the object itself can be referred to using the (same …) predicate described above.(mode-of pclass :mode-name) where pclass may be this or the symbol for a previously defined pclass and :mode-name is one of the modes of that pclass.Bounds represent the lower and upper bounds that an activity is expected to take. Bounds are generally represented by numbers that refer to the time units of the model, such as :bounds [4 8], meaning that the activity in question is expected to take at least 4 and at most 8 time units. If the time units for the model were seconds, that would mean between 4 and 8 seconds. How these values are arrived at is up to the modeler. Given historical data, one way would be for the lower bound to be the mean minus two standard deviations and the upper bound being the mean plus two standard deviations. Sometimes the bounds can be learned and read in from a file; for that LVARS can be used in place of the constants. Furthermore, limited arithmetic is permitted. Expressions using (+ x y) (- x y) (* x y) or (/ x y) may be used and nested as long as x and y are constants or lvars.
This (optional) section, provides the specifications of the mode transitions for the modes defined within this defpclass.
A pclass may have methods specified in the vector value of :methods. Each element of this vector should be a Pamela method as constructed by the defpmethod command.
(defpmethod turn-on
{:pre :off :post :on :bounds [1 3]
:doc "turns on the power supply"}
[flavor]
(sequence (prepare-to-turn-on)(really-turn-on flavor)))
The first argument to defpmethod is the symbol for the method
to be defined. Note that this symbol cannot override the name any of the Pamela built-in methods.
Then there is an optional conditions map which may contain any of the following keys:
true, the body is assumed to be documentation for the method behavior, that may optionally be used for higher-level reasoning (e.g., by the planner).:display-name is assumed to be a Title Case version of the method name symbol, with the hyphens converted to spaces (e.g., "Turn On").The third argument to defpmethod is a vector of zero or more formal arguments to the method.
The next form is the body of the method, which is a call to either a user-defined method (defined via defpmethod) or one of the built-in Pamela methods (see below).
If the body is empty then this is considered a primitive method which
is implemented by the plant (unless overridden by the :primitive key as described above).
When calling a user method (either non-primitive or primitive), each of the arguments can be a Literal (i.e., boolean, string, number, map, vector, keyword) or a Reference. A reference can be either a Symbol or a dotted pair of symbols.
Symbols must have a value (through argument unification via args list) that is one of the supported literals (see above). Alternatively, the value of a symbol could be a pclass instance. In the case of a dotted pair of symbols, the first symbol designates a pclass instance and the second refers to a field of that pclass instance.
A keyword represents a specific mode value (if that mode is defined in the modes section of the current defpclass). Otherwise, it’s treated as a simple literal, very similar to a string. [In fact, when generating JSON forms of the HTN/TPN, keywords and strings have the same representation type].
When specifying a complex literal as an argument value (i.e., map or vector), any of the components of that complex literal structure can be a literal of any type. E.g., {:age 40 :siblings ["tom" "dick" "harry"]
Lastly, following the main body of the method, there may be zero or more between statements.
Bounds values are a vector which contain a lower bound and an upper bound. Each of the bounds are a number (integer or floating point) which represent arbitrary time units for the execution engine. In addition the upper bound may be the keyword :infinity to indicate there is no upper bound.
The special case of bounds [0 :infinity] is considered to be the
default bounds as there is no lower bound nor upper bound.
The defproposition command is a top level Pamela construct which
defines a Pamela global proposition that can be used by any pclass.
(defproposition :connects-with [a b]
:meta {:doc "a is connected to b"})
(defproposition :many-arg-prop [a b c d e])
The first argument to defproposition is the Pamela proposition keyword
being defined.
Then, there is a vector formal arguments to the proposition (which may be empty).
The proposition is then optionally defined with the :meta option, just as in defpclass (see above).
The following is the set of the built-in methods defined in Pamela. All of these are supported by the Pamela parser (i.e., via the build operation). However methods marked with “[Not yet supported]” are not supported for the Pamela htn operation in the current Pamela release.
Note that the Execution sections describe how the Pamela execution environment deals with the Pamela statement. There are logically 4 separate components of the Pamela execution environment:
true, false, or an error (error representation TBD).assert statement.ask is used in conjunction with a tell that is running in a parallel execution thread. If the invocation takes longer than the (optionally) specified upper temporal bound, the statement will be considered to have failed.'(' 'ask' cond-expr opt-bounds? ')'(ask (= door "open") :bounds [0 10])ask statement (which includes the condition) to BSM.assert will cause the planner to dynamically generate a new subplan (i.e., HTN and TPN) whose post condition will satisfy the asserted condition. If the invocation takes longer than the (optionally) specified upper temporal bound, the statement will be considered to have failed.'(' 'assert' cond-expr opt-bounds? ')'(assert (= door "open") :bounds [0 10])assert statement (which includes the condition) to BSM.assert (either success or failure).choose-whenever; the one exception is that when a choice is selected in a choose, that choice will never be canceled and an alternative choice selected. By default, one of the choices is selected, but the use of :exactly, :min, and :max will require more than one choice to be selected. If N choices are chosen, and any of those choices should fail, the execution environment may select an alternative choice.choose ::= '(' 'choose' choose-opt* choice+ ')'choice ::= '(' 'choice' choice-opt* fn ')'choose-opt
::= fn-opt
| exactly
| min
| maxchoice-opt
::= label
| opt-bounds
| cost
| reward
| probability
| guard
| enter
| leave (choose :bounds [50 100]
(choice
(option1 :bounds [55 65]))
(choice
(option2 :bounds [65 75]))
(choice
(option3 :bounds [75 85])))
choose; the one exception is that when a choice is selected in a choose, that choice will never be canceled and an alternative choice selected. For choose-whenever, the execution environment may cancel execution of a choice at any time, and begin execution of an alternative choice. By default, one of the choices is selected, but the use of :exactly, :min, and :max will require more than one choice to be selected. If N choices are chosen, and any of those choices should fail, the execution environment may select an alternative choice.choice-opt declarations to guide when and which choices should be canceled and selected.choose-whenever ::= '(' 'choose-whenever' choose-opt* choice+ ')'choice ::= '(' 'choice' choice-opt* fn ')'choose-opt
::= fn-opt
| exactly
| min
| maxchoice-opt
::= label
| opt-bounds
| cost
| reward
| probability
| guard
| enter
| leave (choose-whenever :bounds [50 100]
(choice
(option1 :bounds [55 65]))
(choice
(option2 :bounds [65 75]))
(choice
(option3 :bounds [75 85])))
fn statement.fn statement should be executed, it would be useful to specify one or more fn-opt parameters (e.g., :cost<= 10).choose and delay. Since it is a macro, an optional statement will never appear in the Pamela Intermediate Representation (IR).'(' 'optional' fn-opt* fn ')'(optional (clean-window)):bounds. time units (typically seconds), as specified by the lower bound of the :bounds specification. Note that the upper bound is ignored.'(' 'delay' delay-opt* ')'(delay :bounds [10 10])parallel statement will complete when all of the subordinate statements complete.'(' 'parallel' fn-opt* fn+ ')' (parallel :bounds [50 100]
(do-this)
(do-that))
slack-parallel statement will complete when all of the subordinate statements complete.delay :bounds [0 :infinity]parallel, sequence and delay. Since it is a macro, a slack-parallel statement will never appear in the Pamela Intermediate Representation (IR).'(' 'slack-parallel' fn-opt* fn+ ')' (slack-parallel :bounds [50 100]
(do-this)
(do-that))
soft-parallel statement will complete when all of the subordinate statements complete.delay, i.e., that each subordinate statement is considered optional.parallel, choose and delay. Since it is a macro, a soft-parallel statement will never appear in the Pamela Intermediate Representation (IR).'(' 'slack-parallel' fn-opt* fn+ ')' (soft-parallel :bounds [50 100]
(do-this)
(do-that))
sequence statement will complete when all of the subordinate statements complete.'(' 'sequence' fn-opt* fn+ ')' (sequence :bounds [50 100]
(do-this-first)
(do-this-second))
slack-sequence statement will complete when all of the subordinate statements complete.delay :bounds [0 :infinity]sequence and delay. Since it is a macro, a slack-sequence statement will never appear in the Pamela Intermediate Representation (IR).'(' 'slack-sequence' fn-opt* fn+ ')' (slack-sequence :bounds [50 100]
(do-this-first)
(do-this-second))
soft-sequence statement will complete when all of the subordinate statements complete.delay, i.e., that each subordinate statement is considered optional.sequence, choose and delay. Since it is a macro, a soft-sequence statement will never appear in the Pamela Intermediate Representation (IR).'(' 'soft-sequence' fn-opt* fn+ ')' (soft-sequence :bounds [50 100]
(do-this-first)
(do-this-second))
ask is used in conjunction with a tell that is running in a parallel execution thread.'(' 'tell' cond-expr ')'(tell (= door "open"))tell statement (which includes the condition) to BSM.fn within the specified temporal bounds. If that fn invocation fails (because of an explicit failure of the fn, or because of a violation of the temporal bounds), then the catch fn is executed.'(' 'try' opt-bounds? fn '(' 'catch' fn ')' ')'(try :bounds [2 3]
(initialize)
(catch (reset-and-initialize)))
fn within the specified temporal bounds.catch fnfn was executed successfully, the try statement is considered to have executed successfully.catch fn was executed successfully, the try statement is considered to have executed successfully.try statement is considered to have executed unsuccessfully (i.e., failed).fn one time. The condition is checked only once. The :bounds, if specified, applies to the duration of the entire statement.unless, when, whenever, and maintain statements.'(' 'unless' cond-expr opt-bounds? fn ')'(unless (= door "open") :bounds [2 3]
(open-door))
unless statement (which includes the condition and activity) to BSM.fn one time. If the condition is not initially true, execution of this when statement will wait until either 1) the condition becomes true, or 2) the temporal duration (:bounds) has been exceeded.unless, when, whenever, and maintain statements.'(' 'when' cond-expr opt-bounds? fn ')'(when (not (= door "open")) :bounds [2 3]
(open-door))
when statement (which includes the condition and activity) to BSM.:bounds) has been exceeded).:bounds, every time the condition is true, execute the statement fn one time. This assumes that once execution of the fn has begun, the cond-expr won’t be revisited until execution of the fn has completed.whenever determined? Proposal: The whenever is successful unless any of the attempted executions of the statement fn fails.unless, when, whenever, and maintain statements.:bounds is [0 :infinity]. So, a whenever statement with an unspecified bounds should be part of a parallel statement.'(' 'whenever' cond-expr opt-bounds? fn ')'(whenever (not (= door "open")) :bounds [2 30]
(open-door))
whenever statement (which includes the condition and activity) to BSM.:bounds) has been exceeded).fn one time. However, while doing so, the condition cond-expr can never be violated.unless, when, whenever, and maintain statements.'(' 'maintain' cond-expr opt-bounds? fn ')'(maintain (= door "open") :bounds [2 30]
(do-lots-of-stuff-possibly-involving-the-door))
:post condition for every statement execution to ensure compliance. However, this has two problems:
:post conditionsfn one time. While doing so, the condition cond-expr can be violated. However, at the completion of executing the statement fn, the condition cond-expr must be true.unless, when, whenever, and maintain statements.'(' 'soft-maintain' cond-expr opt-bounds? fn ')'(soft-maintain (= door "open") :bounds [2 30]
(do-lots-of-stuff-possibly-involving-the-door))
fn the specified number of times, as part of a sequence.'(' 'dotimes' repeat-count fn ')'sequence. Since it is a macro, this statement will never appear in the Pamela Intermediate Representation (IR).dotimes is a macro, repeat-count must be a literal number (i.e., not a variable).(dotimes 3 (open-door)) which is macroexpanded to: (sequence
(open-door)
(open-door)
(open-door))