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 X
would 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
| max
choice-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
| max
choice-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
fn
fn
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))