Fluents

Fluents are proxy objects which are synchronized between different threads. They can be used to store values in a dynamic parallel system and to react to changes of these values.

In PyCRAM fluents are created by calling the constructor of the Fluent class which can be imported from pycram.fluent. The constructor has to given a value which should be saved in the fluent and optionally a name, if no name is provided a random string will be generated instead.

from pycram.fluent import Fluent 

f = Fluent(5)

This creates a fluent which stores the integer 5.

To get to value stored in a fluent the method 'get_value' is used. To assign a new value to a fluent to method 'set_value' is used.

f.get_value() # Would return 5
f.set_value("a") # Sets the value of this fluent to "a" 

It is also possible to wait for the value of a fluent to change. This can be done by using the 'wait_for' function of a fluent.

f.wait_for()

This blocks the thread until the value of the fluent is changed. It is also possible to combine fluents to create a conditions that should be checked every time the fluent is updated. This chaining of fluents is called a fluent network.

(f / 2 == 0).wait_for()

Every time the value of f is changed the condition will be checked and because operator on fluents generate new fluents the wait_for method is also available. For the purpose the comparison operators <, ⇐, ==, !=, >, >= are overloaded as well as the arithmetic operators +, -, *, / so they return a fluent network when used. Because 'is', 'and', 'or', 'not' and 'is not' are keywords and can't therefore not be overloaded the fluent implements its own version which has the same functionality. The fluent implementations are IS, AND, OR, NOT, IS_NOT.

For these operators to work it is enough if one of the two parameter is a fluent. However, the parameter should be compatible with one another under this operator. For example, a fluent holding an integer can't be used with the / operator with a string because the operator is not defined for the two types.

If you want to execute code every time the value of a fluent is changed you can use the macro 'whenever', this macro is meant to be used as a loop and executes the code block every time the given fluent is changed and the new value is not None. An example on how to use whenever can be seen below.

from pycram.fluent import macros, whenever

with whenever(f):
    statement1 
    statement2

It is also possible to execute a block every time a fluent is changed, even when the new value is None. This can be done by calling the pulsed function of the fluent in question.

from pycram.fluent import macros, whenever

with whenever(f.pulsed()):
    statement1 
    statement2

The pulsed function returns again a fluent of which the value changes to True if the value of the parent fluent changes.

A problem that might occur when using whenever is that the value of the fluent can change while the code is still executed. The whenever macro has different behaviour implemented how to handle missed changes. The behaviours are:

  • Changes while execution are ignored
  • The code is executed once more, no matter how many times the value was changed during execution
  • The code is executed again for every missed change

The desired behaviour can be chosen by passing the corresponding value from the enum to the pulsed function. The available values are NEVER, ONCE, ALWAYS. This can also be seen in the example below.

from pycram.fluent import macros, whenever, Behaviour

with whenever(f.pulsed(Behaviour.ALWAYS)):
    statement1 
    statement2

With this the code in the with statement will be run again for every missed change of the value of f.