EmbodimentCombo (Embodiment)

From OpenCog


THIS PAGE IS OBSOLETE

Embodiment Combo Dialect

Note: see StatusOfEmbodimentComboImplementation for what is and is not complete.

Interpreter Design

The current interpreter has two components:

  • a generic component handling pure functional, synchronous operation; and
  • an embodiment-specific component that manages side-effect-laden, asynchronous operation.

An important consequence of this design is that references to ObjectNodes are represented as strings, rather than Handles, and converted to handles as needed (this has numerous benefits - for example it makes it easier to convert expressions back and forth from strings across sessions and machines). This means that the interpreter requirement of the OAC that no two ObjectNodes ever have the same name, even if they are of different subtypes. This requirement holds for PredicateNodes as well. This is a stronger requirement than the generic unique name requirement for the Opencog's AtomSpace (which allows Nodes of different derived subtypes to have the same name).

The more complex, embodiment-specific interpreter, operates as follows: when some component being in execution of a procedure (represented as a vtree, i.e. tree<combo::vertex>), a Procedure::RunningComboProcedure object is created. This is not generally done directly, but through the runProcedure method of the Procedure::ComboInterpreter class, which stores RunningComboProcedure objects internally. When its run method is called, it selects a RunningComboProcedure and calls its cycle() method.

The RunningComboProcedure::cycle() method is guaranteed to create either zero or one ActionPlans (via the PAI) on each call (to be executed asynchronously); if zero action plans are created, it means that either the procedure is now finished running (after return from the call to cycle()), or that it is waiting for an ActionPlan that is already executing to finish before sending anther one (e.g. in the case of a conditional. Percepts correspond either to atom-table lookups (the typical case), or type lookups (i.e. is_avatar and is_pet may be determined without doing a lookup, just by looking at the atom types).

It is also possible to create a running combo procedure object without reference to a PAI; the current behavior in this case is to handle perception and action by querying the user (this can be tried out with the combo_shell_stdio).

The embodiment-specific interpreter calls the main (side-effect free, synchronous) interpreter as-needed. The main interpreter can in turn query the embodiment-specific interpreter as-needed (e.g. to resolve perceptual queries), via the following interface:

struct Evaluator {
    vertex eval_percept(vtree::iterator);
    vertex eval_procedure(vtree::iterator);
    vertex eval_indefinite_object(indefinite_object);
};

which is implemented by RunningComboProcedure.

Expression Syntax

Combo expressions (equivalent to Lisp S-expressions) are written with the root symbol name given first, follow by arguments (if any) within parentheses, separated by whitespace. Whitespace consists of one or more spaces, tabs, and newlines. If there are no arguments to a symbol, then no parentheses are used. Symbol-naming is case-sensitive throughout.

Some correct examples are:

my_binary_fun(arg1 arg2) 
my_unary_fun(my_binary_fun(arg1 arg2))

foo(x
    y
    foo(x
        y
        z))   

The first of these represents a call to my_binary_fun with arguments arg1 and arg2, the second a call to my_unary_fun with an argument that is the result (return value) of a call to my_binary_fun with arguments arg1 and arg2, and the third a call to foo with arguments x, y, and the result of foo called with arguments x, y, and z.

Some incorrect examples:

my_binary_fun(arg1, arg2) 
my_unary_fun(my_binary_fun(arg1() arg2()))

foo x
    y
    foo x
        y
        z

Function Definition Syntax

An n-ary function in combo is defined as:

name(arity) := expr

where name is a valid symbol name (alphanumeric with underscores, doesn't start with a number, no clash with a pre-existing symbol), arity is a natural number (including 0), and expr is a valid combo expression. There is no function overloading allowed.

Within an n-ary function, the symbols #1, #2, ... may be used to refer to arguments (indexed from 1 up to n). Referring to an out-of-index argument is a parse-time error (the function does not get created).

Comment Syntax

A '#' character not within any expression or function definition indicates a comment extending to the next newline. For example:

#a comment
+(1 2 3) # another comment
+(1 
2 #error-not a comment since we're inside an expression
#not a comment either for the same reason
3)

Built-in Constant Symbols

Embodiment Combo contains the a number of built-in symbols that function as constants. These are listed below by category (note that some belong to multiple categories and are hence listed multiple times).

step-towards symbols:

TOWARDS
AWAY

sniff-pet symbols:

NOSE
NECK
BUTT

sniff-avatar symbols:

RIGHT_FOOT
LEFT_FOOT
RIGHT_HAND
LEFT_HAND
CROTCH
BUTT

scrach-self symbols:

NOSE
RIGHT_EAR
LEFT_EAR
NECK
RIGHT_SHOULDER
LEFT_SHOULDER

ear-symbols:

TWITCH
PERK
BACK

Angles and Distances

Angles are given in radians, and are represented by numbers in the range [-pi,pi].

Distances are given in virtual-world-dependent units, and are represented by numbers in the range [0,inf).

Lengths of time (durations) are given in seconds, and are represented by numbers in the range (0,inf).

Virtual World Objects

The basic datatype for interfacing with a virtual world via combo is "obj" - these is differentiated into the subtypes based on the virtual world ontology. Based on Tristan's spec, a sufficient set of subtypes appears to be:

edible_obj
movable_obj
pickupable_obj
drinkable_obj
avatar_obj
pet_obj

where pickupable_obj is a subtype of movable_obj (anything that can be picked up may also be moved, but some objects may be movable, via nudging, without being pickupable).

There is a special obj, null_obj, which is used to indicate failure conditions (see below), and does not belong to any of these subclasses.

Another special obj is owner, which always returns the pet's owner (or null_obj if the owner is not in sensing range).

Yet another special obj is self, which evaluates to the pet itself.

Further special objs are the schemata food_bowl, water_bowl, pet_home, and last_food_place, which may potentially evaluate to null_obj.

There will presumably also be definite subtypes of these (e.g. small_stick as a subtype of pickupable_obj), but there is no definitive list of these, yet.

A number of functions are defined to obtain objects, based on proximity to the pet or on uniform random selection:

nearest_object
nearest_edible
nearest_movable
nearest_pickupable
nearest_drinkable
nearest_avatar
nearest_pet
nearest_small
nearest_moving
nearest_friendly
nearest_poo_place
nearest_pee_place
nearest_noisy

random_object
random_edible
random_movable
random_pickupable
random_drinkable
random_avatar
random_pet
random_small
random_moving
random_friendly
random_poo_place
random_pee_place
random_noisy

Note that nearest_pet and random_pet never return the pet itself, only other pets. These all return null_obj if no satisfying object exists in sensing range.

Messages

Used in second argument of the perception has_said. A message is basically a string but it has a different type to clearly differentiate it from a definite object. On the standard input and output it has the following format "message:X" where X is the actual message, like "message:Well done Fido!".

Booleans

The combo Boolean type contains two symbols, "true" and "false". Connectors are n-ary "and" and "or", and the unary "not".

Percepts (nullary), perceptual predicates (unary) and relations (binary) return Booleans, as follows:

exists_edible
exists_movable
exists_pickupable
exists_drinkable
exists_avatar
exists_pet
exists_small 
exists_moving
exists_friendly
exists_poo_place
exists_pee_place
exists_noisy
exists(obj)

is_edible(obj)
is_movable(obj)
is_pickupable(obj)
is_drinkable(obj)
is_avatar(obj)
is_pet(obj)
is_small(obj)
is_moving(obj)
is_friendly(obj)
is_poo_place(obj)
is_pee_place(obj)
is_noisy(obj)

is_null(obj)

near(obj obj)
above(obj obj)
below(obj obj)
inside(obj obj)

has_said(obj message)

Note that exists refers to existence only within the pet's current sensorium. So for example if the owner is not present (i.e. not indexed in the current space map), then exists(owner) will return false. Furthermore, a "closed world assumption" is used - e.g. if there is not information (in the atom table) regarding whether or not some object is edible, it is assumed to be inedible.

A perceptual predicate is considered true iff there exists in the atom space of the PAI an EvaluationLink between the handles corresponding to the objects in question with a strength >0.5.

Note: see ImplementationDetailsPredicates on how these functions and booleans are handled.

Numbers

Numbers may be written out directly (42, -3, 3.14, etc.), and may be manipulated with the n-ary functions + and * (addition and multiplication), the binary function / (division), and the unary functions sin, exp, and log (sine, e^x, and base-e logarithm).

A pseudorandom value drawn uniformly from [0,1) may be obtained via the symbol rand. Numbers may be compared using the unary 0< function (greater-than-zero), which returns a Boolean.

Actions

The combo action_result type contains two symbols "action_success" and "action_failure".

There are currently only sequential action connectors (although parallel ones are planed for the future).

The n-arry and_seq (sequential and) takes a variable number of action S-expressions, evaluates them left-to-right, and returns failure immediately if one of its children returns action_failure (i.e. it behaves like the short-circuiting C operator '&&'). Otherwise, it returns action_success. So for example:

and_seq(foo goo)

evaluates foo first - if foo fails, it immediately returns action failure. Otherwise, it returns the result of evaluating goo. So if foo==action_failure, goo never gets evaluated.

and_seq

always returns action_success (for completeness).


The built-in action functions (functions returning action_results) for embodiment combo are as follows:

movement actions:

goto_obj(obj)
follow(obj)
step_towards(obj TOWARDS|AWAY)
step_backward
step_forward
        random_step
rotate_left
rotate_right
heel
jump_up
jump_towards(obj)
turn_to_face(obj)
go_behind(obj avatar_obj|pet_obj)

(note: the intention of go_behind is to be able to implement behavior such as hide-and-seek. The first argument is the thing to go behind, and the second argument is the avatar/pet whose line-of-sight to the object is intended to be blocked. So for example saying "go_behind(tree moshe)" should move the pet so that the tree is between it and moshe.)

partial body actions:

grab(pickupable_obj)
        grab_nearest
nudge_to(movable_obj obj)
nudge_forward(movable_obj)
drop
sniff
sniff_at(obj)
sniff_pet_part(pet NOSE|NECK|BUTT)
sniff_avatar_part(avatar RIGHT_FOOT|LEFT_FOOT|RIGHT_HAND|LEFT_HAND|CROTCH|BUTT)
eat(edible_obj)
drink(drinkable_obj)
chew(obj)
scratch_other(obj)
beg
stay
howl
sit
stretch
dig
scratch_self(NOSE|RIGHT_EAR|LEFT_EAR|NECK|RIGHT_SHOULDER|LEFT_SHOULDER)
scratch_other(obj)
lie_down
roll_over
pee
poo

head actions:

sniff
speak
bark
bark_at(obj)
lick
lick_at(obj)
belch
move_head(angle angle angle)
growl        
growl_at(obj)
whine
whine_at(obj)
sneeze
clean 
pant
bare_teeth
bare_teeth_at(obj)

full body actions:

play_dead
vomit
shake_out
anticipate_play

minor actions:

fart
move_left_ear(TWITCH|PERK|BACK)
move_right_ear(TWITCH|PERK|BACK)

eyes actions:

widen_eyes
blink

sleep actions:

sleep
dream(obj)

tail actions:

wag
tail_flex(angle)

Functions involving angles may have maximum (minimum) ranges and, when called with a value above (below) their maximum (minimum), will automatically truncate it to the maximum (minimum) allowed value.

Calling any action expecting an obj with null_obj as an argument causes the action to return action_failure.

Simple atomic actions such as bark are not expected to fail under any normal circumstances excepting a communication failure (failure to send to SL). Commands involving navigation (goto_obj, etc.) may "fail" if either the target is not within the pets sensorium (this can be checked by calling exists(obj)), or if the target is present but unreachable by the navigation algorithm (e.g. behind a wall). In the first case, the command will return action_failure. In the second case, the pet will go as near as it can to the target (so if you want to be certain that goto actually got you where you intended to go, you should check this afterwards). Note that jumping over obstacles is not yet implemented.

Control Flow

There are three conditional statement types:

  • The ternary action_action_if function takes as its first argument an action (or action S-expression, of course) which is immediately executed. If the result is action_success, its second argument is immediately evaluated and the result returned, otherwise (action_failure) its third argument. The second and third arguments must also have the action type.
  • The ternary action_boolean_if function takes as its first argument a Boolean (or Boolean S-expression, of course) which is immediately evaluated. If the result is true, its second argument is immediately evaluated and the result returned, otherwise (false) its third argument. The second and third arguments must also have the action type.
  • The ternary boolean_if function takes as its first argument a Boolean (or Boolean S-expression, of course) which is immediately evaluated. If the result is true, its second argument is immediately evaluated and the result returned, otherwise (false) its third argument. The second and third arguments must evaluate the same type, and it must not be the action type. The rationale behind making a distinction between the conditional involving actions and the one for everything else is that actions are executed differently from everything else (actions get broken down in action plans which are sent asynchronously, whereas everything gets executed immediately and serially).

Similarly, two while-like looping statements are provided:

  • The unary action_while function takes as its argument an action (or action S-expression, of course) which is executed repeatedly as long as it returns action_success.
  • The binary boolean_while function takes as its first argument a Boolean (or Boolean S-expression, of course) which is immediately evaluated. If the result is true, its second argument is immediately evaluated. This repeats until the result of the first evaluation is false, or until the second argument returns action_failure. The result of the final evaluation of the second argument is returned. If the first argument immediately returns false (and hence the second argument is never evaluated), then action_success is returned.

A "repeat n times" looping statement is available for bounded execution, the binary repeat_n, which takes a numerical argument n first, and an action_result S-expression second. The action_result expression is evaluated max(round(n), 0) times, and the result of the final evaluation is returned (or success if it never executes).

An early exit nullary statement return_success is provided, which, when evaluated, terminates execution of the containing procedure and returns action_success.

Evaluation Semantics

With the exception of the action type, combo expressions are pure (no side-effects). Evaluation order (including eager vs. lazy) is for these cases undefined. Evaluation order for actions is always defined on a by-function basis (i.e. there are specific rules for and_seq, etc.). For function calls involving actions, evaluation is guaranteed to be lazy. This means that in:

do_twice(1) := and_seq(#1 #1)
do_twice(bark)

The bark action gets executed twice. If argument evaluation were eager, bark would be evaluated to produce action_success (assuming it succeeded), which would be passed as the argument to the do_twice function. So we can also do e.g.:

do_twice(and_seq(bark bark)

and get four barks, or equivalently:

do_twice(do_twice(bark))

Examples

###
# Seek food locally, and whines if no food is around:
###
seek_food_locally(0) := 
action_boolean_if(exists_edible
    and_seq(goto_obj(random_edible)
     eat(nearest_edible))
    whine)

###
# Sniff butt if available (preferably pet-butt, otherwise avatar-butt):
###
sniff_butt(0) := 
action_boolean_if(exists_pet
    and_seq(goto_obj(random_pet)
            sniff_pet_part(nearest_pet 
      BUTT))
    action_boolean_if(exists_avatar
            and_seq(goto_obj(random_avatar)
                 sniff_avatar_part(nearest_avatar 
             BUTT))
        action_success))

###
# Chase pets or avatars (which one is chosen by a coin-flip):
###
randbool(0) := 0<(+(rand -0.5))
rand_pet_or_avatar(0) := 
boolean_if(exists_pet
           boolean_if(exists_avatar
               boolean_if(randbool
     random_avatar
                   random_pet)
        random_pet)
    random_avatar)
chase_helper(1) :=
boolean_while(not(is_null(#1))
       and_seq(bark
        goto_obj(#1)
        growl))
chase(0) := chase_helper(rand_pet_or_avatar)

###
# Pick up something, carry it a while, then put it down:
###
random_step(0) := 
and_seq(action_boolean_if(randbool
            rotate_left
            rotate_right)
action_boolean_if(randbool
step_forward
step_backward))
pickup_carry_put_down(0) :=
and_seq(goto_obj(random_pickupable)
grab(nearest_pickupable)
random_step
random_step
random_step
        drop)

###
# Nudge something around (uses random_step defined above):
###
nudge_around(0) :=
and_seq(goto_obj(random_movable)
nudge_forward(nearest_movable)
random_step
goto_obj(nearest_movable)
nudge_forward(nearest_movable)
random_step
goto_obj(nearest_movable)
nudge_forward(nearest_movable))

### 
# No functional language example set is complete without an
# inefficient implementation of factorial
###
factorial(1) := boolean_if(0<(#1) *(#1 factorial(+(#1 -1))) 1)

###
# And an even less efficient implementation of fibonacci
###
fibonacci(1) := 
boolean_if(0<(#1)
           boolean_if(0<(+(#1 -1))
               +(fibonacci(+(#1 -1)) 
                 fibonacci(+(#1 -2)))
               1)
          0)

-- Main.MosheLooks - 30 Aug 2007