pamela

Pamela Language Manual

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

Pamela Introduction

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

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.

Pamela language

defpclass

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.

:meta

The :meta map contains optional metadata for the Pamela class, with the following entries:

:inherit

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.

:fields

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:

:modes

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.

Conditional expressions

A conditional expression may be comprised, recursively, of the following:

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.

Each operand is disambiguated based on type as follows:

:bounds

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.

:transitions

This (optional) section, provides the specifications of the mode transitions for the modes defined within this defpclass.

:methods and defpmethod

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:

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).

Arguments used within calls to user-defined methods

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

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.

defproposition

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).

Pamela Built-In Methods

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:

  1. Dispatcher Manager: Given a new HTN/TPN, the Dispatcher Manager creates a Dispatcher instance to execute that HTN/TPN. (Note that the TPN is what is actually executed. The HTN may have been used to generate the TPN, and may be used by Planviz to display the HTN and its execution status).
  2. Dispatcher: This orchestrates the elements of the TPN in the proper order, sending activities to the Belief State Manager (BSM) and the multiple Plant instances. The Dispatcher will dereference any symbolic references contained in the arguments (using the BSM), before sending a command statement to a Plant instance. However, statements containing conditions will be sent to BSM in their entirety.
  3. Belief State Manager (BSM): Based on the initial declarations in the Pamela files, along with the observations obtained during the execution of the plan, the BSM maintains the state of each of the: 1) global states, 2) the mode of each Plant instance, and 3) the fields of each Plant instance. Initiated by requests from a Dispatcher, this information is used in 2 different ways:
    1. To dereference any symbolic references (e.g., field name) contained in the arguments of a Pamela statement. The value of the symbolic reference is returned to the requesting Dispatcher as either a literal or as an error (i.e., when the reference is an invalid field name) (error representation TBD).
    2. To determine the truth value of any condition contained in a Pamela statement. The value of a condition is one of 3 values: true, false, or an error (error representation TBD).
  4. Planner: A component of the BSM, it generates a new plan (HTN/TPN) to satisfy a condition, generally as specified by an assert statement.
  5. Plant: There are one or more Plants that receive and execute commands from the Dispatcher.

ask

assert

choose

 (choose :bounds [50 100]
        (choice
         (option1 :bounds [55 65]))
        (choice
         (option2 :bounds [65 75]))
        (choice
         (option3 :bounds [75 85])))

choose-whenever

 (choose-whenever :bounds [50 100]
        (choice
         (option1 :bounds [55 65]))
        (choice
         (option2 :bounds [65 75]))
        (choice
         (option3 :bounds [75 85])))

optional

delay

parallel

slack-parallel

soft-parallel

sequence

slack-sequence

soft-sequence

tell

try [Not yet supported]

(try :bounds [2 3]
     (initialize)
     (catch (reset-and-initialize)))

unless [Not yet supported]

(unless (= door "open") :bounds [2 3]
     (open-door))

when [Not yet supported]

(when (not (= door "open")) :bounds [2 3]
     (open-door))

whenever [Not yet supported]

(whenever (not (= door "open")) :bounds [2 30]
     (open-door))

maintain [Not yet supported]

(maintain (= door "open") :bounds [2 30]
     (do-lots-of-stuff-possibly-involving-the-door))

soft-maintain [Not yet supported] [Not yet supported by parser and IR]

(soft-maintain (= door "open") :bounds [2 30]
     (do-lots-of-stuff-possibly-involving-the-door))

dotimes