# Defining Classical Planning Problems in `Functional STRIPS`

[WORK IN PROGRESS]¶

Functional STRIPS (FSTRIPS, for short) is a classical planning language that extends STRIPS with functional systems in order to provide a number of *expressive* **and** *computational* advantages, such as the ability of making indirect refernce to domain objects through the use of nested terms.

Tarski lifts Geffner’s original restrictions on the language, and follows a full first-order logic approach. We will use the classical Blocks World domain as a running example.

## Problem Language¶

Tarski supports the formulation of FSTRIPS problems \(P\) defined over a many-sorted first-order logic language with equality. We note the language associated with a prolem \(P\) as \({\cal L}(P)\).

As we have seen in previous chapters of this Tutorial, Tarski languages are made up of a finite set of sorts \(T\), a possibly infinite number of variables \(v_{1}^t\), \(v_{2}^t, \ldots\) for each sort \(t \in T\), a set \(\Phi\) of *function symbols* and a set \(\Pi\) of *relation symbols*, assumed to include a number of symbols with fixed denotation, such as equality.

We can create the language \({\cal L}(P)\) for *Blocks World* as follows

```
[1]:
```

```
import tarski
from tarski.syntax import *
# 1. Create language to model the domain
bw = tarski.language('BlocksWorld', theories=['equality', 'arithmetic'])
```

Tarski comes with several theories that define a number of useful built-in predicates and functions. For the purpose of this example we will be adding the theory of equality, which gives us access to the predicates \(=\) and \(\neq\). The set of supported theories is covered in a specific tutorial.

We define the set of sorts \(T_{blocksworld}\) in our language like this

```
[2]:
```

```
# 2. Define sorts that organise the objects considered
place = bw.sort('place')
block = bw.sort('block', place)
```

where the sort `block`

is a subset of `place`

. We define domain specific function symbols \(\phi_{blocks world}\)

```
[3]:
```

```
# 3. Define functions
loc = bw.function( 'loc', block, place )
width = bw.function('width', place, bw.Real)
looking_at = bw.function( 'looking_at', block )
holding = bw.function( 'holding', block)
```

and domain specific predicate symbols \(\Pi_{blocksworld}\)

```
[4]:
```

```
# 4. Define predicates
clear = bw.predicate( 'clear', block)
```

## Problem Interpretations¶

The current version of Tarski requires that the universe of discourse in any FSTRIPS problem \(P\) is **fixed**. That is, all valid interpretations \({\cal M}\) for \({\cal L}(P)\) have the same universe, denoted by \({\cal U}_P = \{ {\cal U}_t \}_{t \in T}\). It is further required that the universe \({\cal U}_t\) of any sort \(t\) is *finite*, this is a limitation of the current and provably
future versions of Tarski.

In our model of *Blocks World* interpretations \({\cal M}\) for \({\cal L}(P)\) consist of two sorts as defined above. We set \({\cal U}_{place}\) to \(\{ b_1, b_2, b_3, b_4, table \}\) as follows

```
[5]:
```

```
table = bw.constant('table', place)
b1, b2, b3, b4 = [ bw.constant('b_{}'.format(k), block) for k in (1,2,3,4) ]
```

Note that since `block`

is a subtype of `place`

, every time a new constant of sort `block`

is defined the domain of `place`

changes accordingly. The universe of the second sort we defined, \({\cal U}_{blocks}\), has been set implicitly as well. We will collect all the blocks in a list for convenience.

```
[6]:
```

```
the_blocks = [b1, b2, b3, b4]
```

As a result of the universe of discourse being fixed, every interpretation for the language `bw`

shares sorts and associated constant symbols. On the other hand, the denotation of functions and predicates is subject to change. Interpretations (or models) \({\cal M}\) of Tarski languages are objects that allow to associate function symbols with constants and predicate symbols with truth values.

Models are created around a given language

```
[7]:
```

```
import tarski.model
M = tarski.model.create(bw)
```

and require an *evaluation method* to be bound by the user

```
[8]:
```

```
from tarski.evaluators.simple import evaluate
```

```
[9]:
```

```
M.evaluator = evaluate
```

For the purposes of this example, we will set the denotation of function and predicate symbols as follows

```
[10]:
```

```
M.set( loc(b1), b2) # loc(b1) := b2
M.set( loc(b2), b3) # loc(b2) := b3
M.set( loc(b3), table) # loc(b3) := table
M.set( loc(b4), table) # loc(b4) := table
M.set( width(b1), 3.0) # width(b1) := 3.0
M.set( width(b2), 2.0) # width(b2) := 2.0
M.set( width(b3), 1.0) # width(b3) := 1.0
M.set( width(b4), 0.5) # width(b4) := 0.5
M.set( width(table), 100.0) # width(table) := 100.0
M.set( looking_at(), b1) # looking_at() := b1
M.add( clear, b1) # clear(b1) := T
M.add( clear, b4) # clear(b4) := T
```

## Action Schemas¶

A Functional STRIPS *action schema* \(a\) is **defined** by: - a *name*, an alphanumeric identifier, - a *signature* \((v_1,\ldots,v_n)\), where each \(v_i\) is a variable of some sort \(T\), - a *precondition* formula \(\mathrm{pre}(a)\), an arbitrary formula over \({\cal L}(P)\), - a set of *conditional effects* \(\mathrm{effs}(a)\)

Each effect \(e \in \mathrm{effs}(a)\) can be either a *relational* or *functional* effect. Actions have *unspecified* durations, measured as number of *time steps* \(dur(a)\). In Classical FSTRIPS problems durations of all actions are \(1\), Differential FSTRIPS problems allow \(dur(a)\) to be fixed to or bounded by an arbitrary constant. **Note**: This formalisation is still WIP.

### Relational Effects¶

Relational effects of actions \(a\) specify *general* properties states *must* have after the action has been executed. The current version of Tarski requires relational effects to be of the form

where \(\phi\) is an arbitrary formula over \({\cal L}(P)\) and \(L\) is a **literal**. Literals are *atomic formulas* \(R(\bar{t})\) or their negation, \(\neg R(\bar{t})\) over the predicates \(R\) of a given language. In the first case, we say that the effect is an *add effect*, in the second, a *delete effect*.

Relational effects can be formalised as Linear Temporal Logic (LTL) formulas such as

The above specifies the precise *meaning* of the effect, that is: that if \(\phi\) is true on the current state, then necessarily \(L\), *as interpreted in the current state*, needs to be true in the *neXt* state.

### Functional Effects¶

Functional effects of actions \(a\) allow actions to set *directly* the denotation of a given functional symbol \(f(\bar{t})\) in states resulting from \(a\) being done. Tarski current implementation of Functional STRIPS requires these effects to have the form

where \(w\) is an **arbitrary** term. \(f(\bar{t})\) is made up of a function symbol \(f\), of whatever arity, over a tuple of terms \(\bar{t}\) of appropiate size and type. The types of the term \(w\) and symbol \(f\) must coincide.

The LTL specification for functional effects is as follows

with the same semantics, and *proviso* with respect to interpretation of terms, as above.

## Constructing action schemas with Tarski¶

In order to define action schemas, we need to load Tarki’s `fstrips`

module

```
[11]:
```

```
import tarski.fstrips as fs
```

Let \(A\) be our set of actions, initially empty

```
[12]:
```

```
A = []
```

The first action we will model is `move`

that allows us to move the block we’re currently looking at on top of a different one which is currently clear. The action involves two objects. One is referred to indirectly as whatever is `looking_at()`

mapping to. The second one is unspecified, it could be *any* block other than the one we’re looking at. Hence we define it as a *variable* of sort *block*

```
[13]:
```

```
dest = bw.variable('dest', block)
```

and the precondition formula is the conjunction

```
[14]:
```

```
pre = land( clear(looking_at()), clear(dest), looking_at() != dest )
```

This formula is only true whenever: - The block we’re looking at is clear, - the block referred to by variable \(dest\) is clear too, and - both blocks are different.

Note that we have not needed to define the predicate \(\neq\), it was attached automatically to the FOL \(bw\) when we called the constructor above.

The effects of the action are such that: - The block we’re looking at is now placed on top of \(dest\) (i.e. \(loc(looking\_at()):= dest\))

```
[15]:
```

```
eff1 = fs.FunctionalEffect(loc(looking_at()), dest)
```

block \(dest\) is no longer clear (i.e. \(\neg clear(dest)\) must be true in the next state)

```
[16]:
```

```
eff2 = fs.DelEffect(clear(dest))
```

We put together the components of the action and tag it with a descriptive name

```
[17]:
```

```
move_schema = fs.Action(bw, name='move', \
parameters=[dest], \
precondition=pre,
effects = [eff1, eff2])
```

and update the set of actions \(A\) s.t. \(A = A \cup \{ move \}\)

```
[18]:
```

```
A.append(move_schema)
```

The other action we need allows to change the block we’re looking at. We can do that at any time - the precondition is \(\top\), that is, always true - and the effect is that we end up looking at whatever block we have selected.

We define the action reusing the variable symbol \(dest\) we created above and set preconditions, effects and name as follows.

```
[19]:
```

```
look_at_schema = fs.Action( bw, name='look_at',\
parameters=[dest],\
precondition=top,\
effects = [ fs.FunctionalEffect(looking_at(), dest)]
)
```

and we add it to to our set of actions

```
[20]:
```

```
A.append(look_at_schema)
```

Below follows the text based representation of the actions we have modeled so far

```
[21]:
```

```
for k, a in enumerate(A) : print('{}. {}'.format(k,a))
```

```
0. move(dest: block)
1. look_at(dest: block)
```

## Constraints¶

The truth values of the predicate \(clear(p)\) for any place \(p\) need to be consistent with the following formula

```
[22]:
```

```
x = bw.variable('x', block)
y = bw.variable('y', block)
clear_constraint = forall( x, equiv( neg(clear(x)), land( x != table, exists(y, loc(y) == x ) )) )
print(clear_constraint)
```

```
forall x : ((((not (not clear(x))) or (!=(x,table) and exists y : (=(loc(y),x)))) and ((not (!=(x,table) and exists y : (=(loc(y),x)))) or (not clear(x)))))
```

The (redundant) constraint above is binding the truth value of the predicate \(clear(x)\) to two conditions:

\(x\) is not the \(table\) object, or in other words, \(clear(table)\) is

*always*truethere is no block \(y\) on top of \(x\), e.g. \(loc(y) = x\)

## Goals¶

The final constraint we need to specify is the one that goal states need to comply with, the *goal formula*

which corresponds with the blocks arranged in a tower, one of top the other.

```
[23]:
```

```
land( loc(b1) == loc(b2), land( loc(b3) == loc(b4), loc(b4) == table))
```

```
[23]:
```

```
(=(loc(b_1),loc(b_2)) and (=(loc(b_3),loc(b_4)) and =(loc(b_4),table)))
```