Atomic Procedure Behaviour

Earlier we noted that a major difference between TeleoR procedures and those of traditional procedural programs is that a TeleoR procedure call C, other than the initial call of a task, is always terminated because the evaluator picks a different rule in an ancestor call of C.

This will usually happen just before the first rule of C's procedure is about to be fired because the action of the previous rule that C fired has resulted in receipt of an update of the agent's BS later rule fired intended goal has achieved. This is the call instantiated guard of the first rule of C's procedure.

When this termination of C happens just as it is about to fire its first rule, we could think of this as a normal termination of C. But even this can happen not because the actions of the fired rules of C have step-by-step changed the environment until the goal of the call is inferable from the agent's BS, but because the environment has been updated by exogenous actions, and it is because of these events that C's goal has been achieved.

This distinction is even more important when it comes to understand what atomic means for TeleoR procedures. In traditional procedural programming, resources will be acquired, some activity completed, and then the resources released.

In atomic TeleoR procedures the resources are acquired and the activity of that procedure is started. However, either because of the environment or because of the actions of this procedure, a different rule for an ancestor procedure may be chosen causing this atomic procedure to terminate. The resources will be released but it is not necessarily the case that the activity of this TeleoR procedure will be completed.

This distinction might come as a surprise and go against the intuition of what atomic means to traditional procedural programmers - the distinction is about completing an activity - the concept of acquiring and releasing resources is the same.

We urge the reader to carry out the following experiment involving qulog/examples/towers/twoArmCoopTowerBuildersMod1.qlg
and
qulog/examples/towers/twoArmCoopTowerBuildersMod2.qlg
or
qulog/examples/towers/iterTwoArmCoopTowerBuilders.qlg.

The first of these programs exhibits this difference while the second and third behave more like how a procedural programmer might expect.

To run this experiment it helps to have the logger running to see how an ancestor procedure causes termination of an atomic procedure "before it finishes its job".

Start with qulog/examples/towers/twoArmCoopTowerBuildersMod1.qlg
making sure you have it logging. Let the program finish building the towers. When that is done suspend the program by pressing the spacebar. It might be helpful to reduce the speed using the left arrow key. Now move block 3 to be directly under block 2 and press the spacebar again to resume the program. The arm and table change to the colour to be the colour of the task that is running with those resources acquired. This should not look surprising. What this manouver does do, however, is change the order of tasks in the wait queue at this point.

Now repeat this experiment by again moving block 3 under block 2. Now you will see a different and perhaps surprising behaviour. First task 2 will pick up block 2 and move it to the table as it wants to access block 3 for its tower. Now task 1 gets a turn and it wants to pick up block 3 and cooperatively move it to the other tower in order to clear its partial tower so that block 2 can be added. As soon as the arm picks up block 3 the colour changes signifying that task 1 becomes waiting and task 2 becomes the running task. This means task 1 did not complete the move of block 3 - it only picked it up.

Looking at the logger will explain the reason for this.

When task 1 became running (to move block 3) the call stack in the logger was

towers::: t1: New call stack evaluation:
makeTower(arm1, [2, 5, 9, 6], table1) - 7 continued
********************
makeTower(arm1, [5, 9, 6], table1) - 3 refired
coop_move(arm1, 3, table1) - 2 fired
move_to_location(arm1, 3, table1, 4, table1) - 2 fired
move_to_block(arm1, 3, table1, 4, table1) - 3 fired
[pickup(arm1, 3, table1)]

Robotic actions for t1 are: [pickup(arm1, 3, table1)]

As soon as block 3 was picked up the following percepts were received.
towers::: Received new percept message:
[r_(holding(arm1, 3)), f_(on(3, 5))]
This caused a re-evaluation of the call stack yielding.
towers::: t1: New call stack evaluation:
********************
makeTower(arm1, [2, 5, 9, 6], table1) - 2 fired
move_across_to_block(arm1, 2, 5, table1) - 2 fired
coop_move(arm1, 3, table1) - 2 fired
move_to_location(arm1, 3, table1, 4, table1) - 2 fired
wait_choice(move_to_block(arm1, 3, table1, 4, table1))
[]

Robotic actions for t1 are: []
At this point a different rule of the top-level makeTower was chosen (rule 2 insted of rule 7). Because the atomic move_to_block was terminated this task stopped running before the block move was complete.

Now run the same experiment using
qulog/examples/towers/twoArmCoopTowerBuildersMod2.qlg

As in the first experiment, task 2 moves block 2 to the table and then task 1 gets a turn where its call stack is

towers::: t1: New call stack evaluation:
********************
makeTower(arm1, [2, 5, 9, 6], table1) - 3 refired
coop_move(arm1, 3, table1) - 2 fired
move_to_location(arm1, 3, table1, 4, table1) - 2 fired
move_to_block(arm1, 3, table1, 4, table1) - 3 fired
[pickup(arm1, 3, table1)]

Robotic actions for t1 are: [pickup(arm1, 3, table1)]

Now, however, in response to the percepts

towers::: Received new percept message:
[r_(holding(arm1, 3)), f_(on(3, 5))]
the call stack becomes
towers::: t1: New call stack evaluation:
makeTower(arm1, [2, 5, 9, 6], table1) - 3 continued
coop_move(arm1, 3, table1) - 2 continued
move_to_location(arm1, 3, table1, 4, table1) - 2 continued
********************
move_to_block(arm1, 3, table1, 4, table1) - 2 fired
[put_on_block(arm1, 4, table1)]

Robotic actions for t1 are: [put_on_block(arm1, 4, table1)]
The only change to the call stack is inside the atomic procedure move_to_block and so it continues and completes the move of block 3.