Inline operations

Sometimes, it is convenient (not to say necessary) to perform tests and validations over resources or combination of resources. This could be done by implementing objects and bags integrating dedicated methods.

With the aim of easing the task of developers, the middleware integrates the inline operations feature. Inline operations are custom operations which can be mixed with other standard operations within the rules and which provide wider capabilities.

The introduction of inline operations do not breach the "bag and resources" model: they use resources (or values propagated within a multi-operations rule) as input parameters (as would any read operation performed on a bag) and provide with Boolean results (as any result of any operation) or other types of values (similarly to resources retrieved by read or get operations).

Inline operations should be considered as a notation shortcut to provide an easy way to perform simple tests and small computation within a rule.

Inline operations are identified with dedicated keywords, set as prefixes of the related operations:

  • INLINE_ASSERT and ASSERT are used to perform simple tests retuning a Boolean result value
  • INLINE_COMPUTE or COMPUTE are used to perform computation operations over available resources within the rule

Each custom operation is associated to a corresponding python function which source code is placed in an external dedicated python file. A given source file may contain the code of multiple ASSERT and COMPUTE operations.

Within a rule, an inline operation is called like this :

KEYWORD: potential_result = module_name.function_name(parameters)

where:

  • KEYWORD may be respectively ASSERT or COMPUTE
  • potential_result is the value returned by a COMPUTE operation. It may be a resource value, a computed set-point value for an actuator or any other value. In the case of an ASSERT operation, potential_result is not explicitly returned: an ASSERT operation always returns a mandatory boolean value.
  • module_name is the name of the external python module where the function associated with the inline operation is registered.
  • function_name is the name of the function associated with the inline operation, in the module_name external python module.
  • parameters are any required parameters, available from the rule. It may be retrieved resources, numeric values or any result of other inline operations

This lesson exemplifies ASSERT and COMPUTATION inline operations integrated within the trom.rules script.

ASSERT

The trom.rules script is constituted of distinct rules dedicated to find a way for the customer from their initial location to their final awaited destination:

  • the first rule evaluates the availability of a direct journey
  • the next rule evaluates the availability of a journey with one interconnection
  • the next rule evaluates the availability of a journey with two interconnections
  • and so on...

Currently, when a rule searches for a travel with interconnections, it does not accept that a line be involved twice in the journey. As a result, some unnecessary computations are done because, if a line is involved twice, this means that the customer leaves a train on a line and then gets back in another train running on the same line further in the evaluated travel... The retained solution should be the one where the customer does not change line and stays in her/his initial seat.

In order to avoid evaluating and retaining such absurd situations, an ASSERT inline operation is introduced to check that the rule does not consider using the same line twice.

Note that, in the case of multiple interconnections, inserting ASSERT operations early within the rule will prevent a large amount of unnecessary possibilities from being evaluated. This may save a lot in terms of general performance and responsiveness of the rule.

Rules updates

We want to be sure that a line is not involved twice during the evaluation of a rule instance. This means that we have to assert the 2 retrieved lines' (the initial and final *lines of interconnections) are different.

In the example below, an are_different function, defined in a lib module, is used to ensure that 2 resources (named line1 and line2, retrieved from previous *read' operations and then passed as parameters) are different.

If the value returned by the assert function is true, then the performance part is executed.

If the assertion fails, the performance part of the rule is not triggered: a more optimal route exists and will be followed by the customer.

 {*,!}["People","Customer"].rd(name,departure) &
 {*,!}["People","Destination"].rd(name,arrival) &
 {*,!}["Metro","Connection"].rd(line1,departure,via) &
 {*,!}["Metro","Connection"].rd(line2,via,arrival) &
 ASSERT: lib.are_different(line1,line2) &
 ::
 {
 ["People","Customer"].get(name,departure) ;
 ["People","Destination"].get(name,arrival) ;
 ["Metro","Connection"].get(line1,departure,via) ;
 ["Metro","Connection"].get(line2,via,arrival) ;
 ["People","Customer"].put(name,arrival) ;
 ["Supervision","Travel"].put("2-hop",departure,arrival,name)
 }.

Please have a look in the associated source files and note how the assertions are inserted into the existing rule to avoid unnecessary combinatorial evaluations.

lib.py: are_different() method

Here, the lib.py file is stored in the same directory as the quinoa.py file.

Like in the previous case, where an object_Metro.py file was defined, this directory should be added in the list of external libraries associated with the domain :

domain1.library = [TOPDIR]   *<--- TOPDIR is the root directory, where
*quinoa.py* is stored*

The lib.py file contains an are_different() function which takes 2 values as input arguments and returns a Boolean value, true if both are different. That is :

 #method lib.are_different()
 def are_different(a, b):
    return a != b

INLINE_ASSERT

Note that for simple function such as is this example, you don't need to create a lib module. You can directly use the token:

INLINE_ASSERT: line1 != line2 &

COMPUTE

COMPUTE operations allow to perform some external computation and to get back a returned value which can be used in the following of the rule.

Rules updates

Here, as an example, we want to add a kind of traceability aspect over the execution of the rules: at each interconnection, the lines used by the customer are stored. For that, a customer_path string is built from the concatenation of all the lines used by the customer.

For that, a new concat() function is added in the same previous external lib module. this function simply takes 2 string arguments as input and returns the string resulting from the concatenation of these 2 arguments.

In the example below, where the rule corresponds to a travel with one unique interconnection, the resulting concatenated string is stored in the Travel bag of the Supervision object when the rule is executed (i.e. when the customer reaches his targeted destination).

In case of multiple interconnections, the same method is called on every line change. Then, all the changes and all the used lines are concatenated and retrievable once the customer reaches his destination.

 {*,!}["People","Customer"].rd(name,departure) &
 {*,!}["People","Destination"].rd(name,arrival) &
 {*,!}["Metro","InterConnection"].rd(via,line1,line2) &
 {*,!}["Metro","Connection"].rd(line1,departure,via) &
 {*,!}["Metro","Connection"].rd(line2,via,arrival) &
 COMPUTE: customer_path = lib.concat(line1,line2)
 ::
 {
 ["People","Customer"].get(name,departure) ;
 ["People","Destination"].get(name,arrival) ;
 ["Metro","InterConnection"].get(via,line1,line2) ;
 ["Metro","Connection"].get(line1,departure,via) ;
 ["Metro","Connection"].get(line2,via,arrival) ;
 ["People","Customer"].put(name,arrival) ;
 ["Supervision","Travel"].put(customer_path,departure,arrival,name)
 }.

lib.py: concat() method

The lib.py file is added with the corresponding concat() function, defined as follow :

 #method lib.concat
 def concat(a, b):
     return a + ' -> ' + b

The method concatenates the two arguments it received in the parameters and returns the resulting string.

INLINE_COMPUTE

Note that for simple function such as is this example, you don't need to create a lib module. You can directly use the token:

INLINE_COMPUTE: customer_path = line1 + '->' + line2 &

Run the application

You may run the application with the "optimized" rules as shown previously (and reminded hereafter):

 python quinoa.py --start_objects All

 python buildMap_METHOD.py (or python buildMap_METHOD.py)

 python quinoa.py --run_scripts All

Then, check the result in the monitor.

http://localhost.localdomain:9999/NameServer/MONITOR

In the end, don't forget to stop your application :

python quinoa.py --stop_all