3.11. Interpretation of reset elements

  1. For the purposes of this section, we define the “reset variable” to be the variable referenced by a reset element’s variable attribute, and the “test variable” to be the variable referenced by its test_variable attribute.

  2. Each reset element describes a change to be applied to the reset variable when specified conditions are met during the simulation of the model.

See more

Understanding reset variables and test variables

In the following example we want to model the position of an automatic vacuum cleaner as it deflects off two opposite walls in a room. The device follows a straight line until it encounters a wall, at which point it immediately switches direction and travels back to the other wall. We will use resets to model the interaction of the device with the wall, but before they’re added, we’ll start with a very simplistic model which describes the position of the device changing with time:

model: CleaningTheHouse
  └─ component: Roomba
      ├─ variable: position [metre], initially 0
      ├─ variable: width [metre], constant, 5
      ├─ variable: velocity [metre_per_second], initially 0.1
      └─ math:
          └─ ode(position, time) = velocity

See CellML syntax

<model name="CleaningTheHouse">

  <component name="Roomba">
    <!-- Variables should be initialised using the initial_value attribute. -->
    <variable name="position" units="metre" initial_value="0" />
    <variable name="velocity" units="metre_per_second" initial_value="0.1" />

    <!-- Constants should be set in the math element so that they are true for all time. -->
    <variable name="width" units="metre" />

    <math>
      <!-- Constant: the room is 5m wide. -->
      <apply><eq/>
        <ci>width</ci>
        <cn cellml:units="metre">5</cn>
      </apply>

      <!-- Variable: the position of the device comes from solving the ODE. -->
      <apply><eq/>
        <diff>
          <ci>position</ci>
          <bvar>time</bvar>
        </diff>
        <ci>velocity</ci>
      </apply>

    </math>
  </component>
</model>

Now let’s add a reset to this such that when the device reaches the opposite wall, its direction of travel is reversed.

model: CleaningTheHouse
  └─ component: Roomba
      ├─ variable: position
      ├─ variable: width
      │
      ├─ variable: velocity
      │    └─reset:
      │      ├─ "when position equals width"
      │      └─ "then go back the other way"
      │
      └─ math:
          └─ ode(position, time) = velocity

Show CellML syntax

<reset variable="velocity" test_variable="position" order="1">

  <!-- The "when" statement above is true when the test_variable
      attribute equals the test_value statement: -->
  <test_value>
    <ci>width</ci>
  </test_value>

  <!-- The "then" statement above is defined by setting the
        variable attribute to the reset_value statement: -->
  <reset_value>
      <cn cellml:units="metre_per_second">-0.1</cn>
  </reset_value>
</reset>

The behaviour at the other end of the wall is discussed in the following section.

  1. All reset elements SHALL be considered sequentially for the equivalent variable set (see 3.10 Interpretation of map_variables elements) to which the reset variable belongs.

    1. The sequence SHALL be determined by the value of the reset element’s order attribute, lowest (least positive / most negative) having priority.

See more

Understanding the reset order

CellML is designed to represent complicated model behaviour, and this includes the idea that a single variable might need to be reset based different kinds of behaviour; or, that more than one reset is permitted per variable.

Continuing the example from the previous block, we’ll now add the behaviour of the automatic vacuum cleaner when it reaches its starting position at the first side of the room.

model: CleaningTheHouse
  └─ component: Roomba
      ├─ variable: position
      ├─ variable: width
      │
      ├─ variable: velocity
      │    ├─ reset A: order = 1
      │    │   ├─ "when position equals width"
      │    │   └─ "then set negative velocity"
      │    └─ reset B: order = 2
      │        ├─ "when position equals start"
      │        └─ "then set positive velocity"
      │
      └─ math:
          └─ ode(position, time) = velocity

At present it’s clear from the conditions on the two resets (the position at the start and at the far wall) that they cannot occur simulataneously, but this is not always the case. For this reason, every reset must specify an order attribute which will be used as a tie-breaker in situations where more than one reset on a variable is active at the same time.

It’s also worth noting that because of the variable equivalence functionality, when we talk about “per variable” here, we’re really including all resets which directly (as above, through the same variable attribute) or indirectly (where another variable in the same equivalent variable set is referenced) change a variable’s value.

To illustrate the idea of the equivalent variables and order, we’ll add a new component which represents the battery charge on the device. When the battery reaches a lower limit the machine will stop moving.

model: CleaningTheHouse
  └─ component: Roomba
      ├─ variable: position
      ├─ variable: width
      ├─ variable: battery_level [charge], initially 100
      │
      ├─ variable: velocity
      │    ├─ reset A: order = 1
      │    │   ├─ "when position equals width"
      │    │   └─ "then set negative velocity"
      │    ├─ reset B: order = 2
      │    │   ├─ "when position equals start"
      │    │   └─ "then set positive velocity"
      │    └─ reset C: order = -1
      │        ├─ "when battery level is less than 10"
      │        └─ "then stop"
      │
      └─ math:
          ├─ ode(position, time) = velocity
          └─ ode(battery_level, time) = -abs(velocity)

Show CellML syntax

<model name="CleaningTheHouse">
  <component name="Roomba">
    <variable name="position" units="metre" />
    <variable name="width" units="metre" />
    <variable name="battery_level" units="charge" initial_value="100" />
    <variable name="velocity" units="metre_per_second" initial_value="0.1" />

    <!-- Reset A will set a negative velocity when the Roomba reaches
            the opposite wall: -->
    <reset variable="velocity" test_variable="position" order="1">
      <test_value>
        <ci>width</ci>
      </test_value>
      <reset_value>
        <cn cellml:units="metre_per_second">-0.1</cn>
      </reset_value>
    </reset>

    <!-- Reset B will set a positive velocity when the Roomba reaches
            the starting wall: -->
    <reset variable="velocity" test_variable="position" order="2">
      <test_value>
        <cn cellml:units="metre">0</cn>
      </test_value>
      <reset_value>
        <cn cellml:units="metre_per_second">0.1</cn>
      </reset_value>
    </reset>

    <!-- Reset C will stop the Roomba when its charge is 10. -->
    <reset variable="velocity" test_variable="battery_level" order="-1">
      <test_value>
        <cn cellml:units="charge">10</cn>
      </test_value>
      <reset_value>
        <cn cellml:units="metre_per_second">0</cn>
      </reset_value>
    </reset>

    <math>
      <!-- Setting the width of the room as a constant: -->
      <apply>
        <eq/>
        <ci>width</ci>
        <cn cellml:units="metre">5</cn>
      </apply>

      <!-- Simple ODE for position of the Roomba with time: -->
      <apply>
        <eq/>
        <diff>
          <ci>position</ci>
          <bvar>time</bvar>
        </diff>
        <ci>velocity</ci>
      </apply>

      <!-- Simple ODE for charge of the Roomba with time: -->
      <apply>
        <eq/>
        <diff>
          <ci>battery_level</ci>
          <bvar>time</bvar>
        </diff>
        <apply>
          <times/>
          <apply>
            <abs/>
            <ci>velocity</ci>
          </apply>
          <cn units:cellml="charge_second_per_metre">-1</cn>
        </apply>
      </apply>

    </math>
  </component>

  <!-- Custom units needed: -->
  <units name="metre_per_second">
    <unit units="metre" />
    <unit units="second" exponent="-1" />
  </units>

  <units name="charge"/>

  <units name="charge_second_per_metre">
    <unit units="charge" />
    <unit units="metre_per_second" exponent="-1"/>
  </units>

</model>

In order for the machine to be able to stop when the battery is low, reset C must always be able to trump either of the other two as the conditions of reaching a wall and having a low battery could occur at the same time. This is accomplished by using an order of -1, making it lower than the order values of the other two resets, which also illustrates the idea that orders can be negative numbers (where the most negative is the most “important”).

Enacting the reset algorithm

Behind the syntax of the resets is an algorithm which determines how they are interpreted. This algorithm is outlined below.

  1. For each reset item, determine whether its test criterion (the “when” idea above) has been met.

    1. If yes, the reset is said to be “active”.

    2. If not, it is “inactive”.

  2. Collect all active resets for a variable and its equivalent variables into a “variable active set”.

  3. For each variable, select the lowest order reset from the variable active set and designate it “pending”.

  4. Calculate, but do not apply, the update changes specified by each pending reset based on the current state of the model.

  5. Apply the updates calculated in (4). This step means that the order in which the variables’ values are altered does not affect the overall behaviour of the resets, as all of the updates are based on the unchanged state of the system.

  6. Test whether the set of variable values in the model has changed:

    1. If yes, repeat the steps above from (1) using the updated values as the basis for the tests.

    2. If not, continue the modelling process with the updated values.

Let’s apply this to the example and see how it works. Consider the state when the roomba has reached the other side of the room, and the battery level has fallen to 10.

  • Applying (1), both resets A and C are designated active.

  • Applying (2), both resets A and C explicitly reference the variable velocity, so are in the same active set for that variable.

  • Applying (3), we select reset C as having the lower order within the active set, and call it pending.

  • Applying (4), we evaluate the new value for the velocity variable to be zero because of the pending reset C.

  • Applying (5), set the velocity to zero.

  • Applying (6), this loop would be checked through again, but with the same result. The second time through the loop, we exit as there would be no further changes to the variables’ values.

There are alternative ways of arranging resets which would have the same functional outcome. These are described in 5.1 Using resets.

  1. The condition under which a reset occurs SHALL be defined by the equality of evaluation of the test variable and the evaluation of the MathML expression encoded in the test_value.

  2. When a reset occurs, the reset variable SHALL be set to the result of evaluating the MathML expression encoded in the reset_value.

See more

Understanding the test_value and reset_value elements

In the previous block we introduced a model which showed how both a low battery and an encounter with a wall could affect the velocity of the Roomba, and used the order attribute to determine which reset was followed. Now we’d like to make sure that the low-battery Roomba is only ever stopped at a wall, so that people don’t trip up on it.

Conditional statements are legal inside both the test_value block and reset_value blocks, so we can write this (excluding units) as:

model: CleaningTheHouse
  └─ component: Roomba
      ├─ variable: position
      ├─ variable: width
      ├─ variable: battery_level [charge], initially 100
      │
      ├─ variable: velocity
      │    ├─ reset A1: order = 1
      │    │   ├─ "when IF position equals width AND battery level is more than 10"
      │    │   └─ "then set negative velocity"
      │    ├─ reset A2: order = 2
      │    │   ├─ "when IF position equals width AND battery level is 10 or less"
      │    │   └─ "then stop"
      │    │
      │    └─ reset B: order = 2
      │        ├─ "when position equals start AND battery level is more than 10"
      │        └─ "then IF battery is more than 10 set positive velocity ELSE stop"
      │
      └─ math:
          ├─ ode(position, time) = velocity
          └─ ode(battery_level, time) = -abs(velocity)

In this situation, reset A1 is only ever active when there is sufficient battery to change direction. This means that we’ve had to add a second reset A2 that tests for when the far wall is encountered and the battery is low. In contrast, similar behaviour is found in reset B using just one reset, where the conditional statement is in the reset value rather than the test value.

While they will work in this situation, it’s better to avoid using conditional statements with resets if possible. The situation above could be reframed to supply the extra battery conditions like this:

model: CleaningTheHouse
  └─ component: Roomba
      ├─ variable: position [metre], initially 0
      ├─ variable: width [metre], constant 5
      ├─ variable: battery_level [charge], initially 100
      │
      ├─ variable: battery_check [dimensionless], initially 1
      │    └─ reset C: order = 1
      │        ├─ "when battery_level equals 10"
      │        └─ "then set battery_check to 0"
      │
      ├─ variable: velocity [metre_per_second], initially 0.1
      │    ├─ reset A: order = 1
      │    │   ├─ "when position equals width"
      │    │   └─ "then velocity to be negative product with battery_check"
      │    └─ reset B: order = 2
      │        ├─ "when position equals start"
      │        └─ "then set to be positive product with battery_check"
      │
      └─ math:
          ├─ ode(position, time) = velocity
          └─ ode(battery_level, time) = -abs(velocity)

Show CellML syntax

<model name="CleaningTheHouse">
  <component name="Roomba">
      <variable name="position" units="metre" />
      <variable name="width" units="metre" />
      <variable name="battery_level" units="charge" initial_value="100" />
      <variable name="velocity" units="metre_per_second" initial_value="0.1" />
      <variable name="battery_check" units="dimensionless" initial_value="0" />

      <!-- Resets active at any position tell when the battery check means it should
      be stopped at the next wall encounter. Note that this doesn't actually affect
      the velocity of the Roomba until either reset A or B is active. -->
      <reset variable="battery_check" test_variable="battery_level" order="1">
          <test_value>
              <cn cellml:units="charge">10</cn>
          </test_value>
          <reset_value>
              <ci cellml:units="dimensionless">1</ci>
          </reset_value>
      </reset>

      <!-- Reset A will set a negative velocity when the Roomba reaches
          the opposite wall, provided the battery_check is not zero. -->
      <reset variable="velocity" test_variable="position" order="1">
          <test_value>
              <ci>width</ci>
          </test_value>
          <reset_value>
              <apply>
                  <times/>
                  <cn cellml:units="metre_per_second">-0.1</cn>
                  <ci>battery_check</ci>
              </apply>
          </reset_value>
      </reset>
      <!-- Reset B2 will set a positive velocity when the Roomba reaches
          the starting wall, provided the battery_check is not zero. -->
      <reset variable="velocity" test_variable="position" order="2">
          <test_value>
              <cn cellml:units="metre">0</cn>
          </test_value>
          <reset_value>
              <apply>
                  <times/>
                  <cn cellml:units="metre_per_second">0.1</cn>
                  <ci>battery_check</ci>
              </apply>
          </reset_value>
      </reset>

      <math>
          <!-- Setting the width of the room as a constant: -->
          <apply>
              <eq/>
              <ci>width</ci>
              <cn cellml:units="metre">5</cn>
          </apply>

          <!-- Simple ODE for position of the Roomba with time: -->
          <apply>
              <eq/>
              <diff>
                  <ci>position</ci>
                  <bvar>time</bvar>
              </diff>
              <ci>velocity</ci>
          </apply>

          <!-- Simple ODE for charge of the Roomba with time: -->
          <apply>
              <eq/>
              <diff>
                  <ci>battery_level</ci>
                  <bvar>time</bvar>
              </diff>
              <apply>
                  <times/>
                  <apply>
                      <abs/>
                      <ci>velocity</ci>
                  </apply>
                  <cn units:cellml="charge_second_per_metre">-1</cn>
              </apply>
          </apply>
      </math>
  </component>

  <!-- Custom units needed: -->
  <units name="metre_per_second">
      <unit units="metre" />
      <unit units="second" exponent="-1" />
  </units>

  <units name="charge"/>

  <units name="charge_second_per_metre">
      <unit units="charge" />
      <unit units="metre_per_second" exponent="-1"/>
  </units>
</model>