Entity Tables - openmpp/openmpp.github.io GitHub Wiki

Home > Model Development Topics > Entity Tables

This topic describes entity tables in depth. This topic is under construction and consists mostly of stub subtopics.

Related topics

Topic contents

Introduction and concepts

Content to follow. Topic outline is incomplete. The only complete subtopic is Increment Validity.

[back to topic contents]

Run-level results

Entity tables are computed for each sub, and run-level results computed from the sub-level results. Several methods are available to compute run-level results through the measures_method option, as follows:

measures_method Description of run result
average Measures are averaged across subs.
ex #1: measure count run: AVG(count)
ex #2: measure income run: AVG(income)
ex #3: measure: income/count run: AVG(income/count)
aggregate accumulators are averaged over subs, then the measure calculated using those.
ex #1: measure: count run: AVG(count)
ex #2: measure: income run: AVG(income)
ex #3: measure: income/count run: AVG(income)/(AVG(count)
assemble accumulators are summed over subs, and the measure computed using those
ex #1: measure: count run: SUM(count)
ex #2: measure: income run: SUM(income)
ex #2: measure: income/count run: SUM(income)/SUM(count)

In these examples, income and count are sums which vary over subs, and the run result shows the operations performed on those subs to produce the run result.

Increments

An increment is based on the value of an attribute when an entity enters a table cell and the value when it exits the cell. An entity enters a table cell at simulation entry or when the table filter becomes true. An entity leaves a table cell at simulation exit or when the table filter becomes false. An entity changes cell when attributes used in the classification dimensions of the table change value.

Keyword Description
value_in The attribute value when the entity enters the table cell.
value_out The attribute value when the entity leaves the table cell.
delta The difference in the attribute value between cell exit and cell entrance.
nz_value_in The non-zero count of attribute value on entrance.
nz_value_out The non-zero count of attribute value on exit.
nz_delta The non-zero count of the difference in value between exit and entrance.
value_in2 The square of the value on entrance.
value_out2 The square of the value on exit.
delta2 The square of the difference in value between exit and entrance.

[back to topic contents]

Accumulators

Keyword Description
unit
sum
minimum
maximum
gini
P1
P2
P5
P10
P20
P25
P30
P40
P50 median
P60
P70
P75
P80
P90
P95
P98
P99

[back to topic contents]

Shorthand

These keywords are a more compact way of specifying commonly-used combinations of increments and accumulators.

Keyword Equivalent
delta(x) sum(delta(x))
delta2(x) sum(delta2(x))
nz_delta(x) sum(nz_delta(x))
value_in(x) sum(value_in(x))
value_in2(x) sum(value_in2(x))
nz_value_in(x) sum(nz_value_in(x))
value_out(x) sum(value_out(x))
value_out2(x) sum(value_out2(x))
nz_value_out(x) sum(nz_value_out(x))
max_delta(x) maximum(delta(x))
max_value_in(x) maximum(value_in(x))
max_value_out(x) maximum(value_out(x))
min_delta(x) minimum(delta(x))
min_value_in(x) minimum(value_in(x))
min_value_out(x) minimum(value_out(x))

[back to topic contents]

Increment Validity

This subtopic contains the following sections:

[back to topic contents]

Non-numeric floating point values

A floating point number in OpenM++ is a C++ IEEE double or float, which is supported on most CPU hardware. It can hold an exact value like 123 or 0.5 or an approximation to a Real number like 0.1 or pi.

A floating point number can also hold one of three special non-numeric values: +inf, -inf, or NaN (indeterminate).

Non-numeric values arise naturally from arithmetic operations or function calls, e.g.

Expression Result
1.0 / 0.0 +inf
-1.0 / 0.0 -inf
0.0 / 0.0 NaN
log(0.0) -inf
log(-1.0) NaN
sqrt(-1.0) NaN
exp(710.0) +inf

Note: The C++ specification for std::exp guarantees a result of +inf if the argument is greater than 709.8.

Numeric values can sometimes result from floating point operations or function calls with non-numeric arguments, e.g.

Expression Result
1.0 / +inf 0.0
atan(+inf) pi/2
exp(-inf) 0.0
exp(log(0.0)) 0.0

Logical comparison operators can have non-numeric arguments, e.g.

Expression Result
+inf > 42.0 true
-inf < +inf true
NaN < +inf false
NaN == NaN false
NaN != NaN false

Note: The C++ specification states that all operators with NaN return NaN, including all comparison operators, notably the == operator in the preceding table. The C++ library contains functions to determine non-numeric values: std::isnan determines if a floating point number is NaN, std::isinf if it is +inf or -inf, and std::isfinite() if it is finite, i.e. a garden variety floating point value.

Arithmetic operations involving +inf or -inf typically result in non-numeric values, e.g.

Expression Result
+inf + 1.0 +inf
+inf - 1.0 +inf
+inf + +inf +inf
sqrt(+inf) +inf
inf / inf NaN

This is notably the case for NaN, which propagates in arithmetic operations and mathematical functions, e.g.

Expression Result
NaN + 1.0 NaN
+inf + NaN NaN
sqrt(NaN) NaN

Sometimes operations involving +inf or -inf can produce NaN, e.g.

Expression Result
+inf - +inf NaN
+inf / +inf NaN
+inf / 0.0 NaN

A general rule of thumb is that non-numeric floating point values propagate to results in arithmetic operations and mathematical functions.

[back to increment validity]
[back to topic contents]

Non-numeric values in OpenM++

Non-numeric values are used in several ways in OpenM++:

  1. Global time is initialized to -inf before each case or replicate/sub.
  2. All event times are initialized to +inf before each case or replicate/sub.
  3. Derived parameters of floating point type are initialized to NaN (all cells).
  4. An attribute of floating point type (float, double, real, or Time) can have a non-numeric value, depending on model logic.
  5. A maximum table accumulator is initialized to -inf, and a minimum table accumulator to +inf.

[back to increment validity]
[back to topic contents]

Increments and accumulators

Each cell of an entity table contains one or more accumulators specified in the expression dimension of the table. As attributes change value during a run, increments are pushed to accumulators of the current cell of the table. An accumulator might be a running count of increments, the running sum of increments, the current maximum value, or a collection of all pushed values. When a run completes, statistics are extracted from the table accumulators for each cell to compute final values for that table cell. For example, the median P50 for an attribute in a table cell is extracted from the accumulator underlying P50, which is a collection of all increments pushed to the cell during the run. The P50 statistic is computed by sorting that collection and finding the middle value (or the average of the two middle values if the number of increments in the collection is even).

[back to increment validity]
[back to topic contents]

Invalid table increments

Some accumulators handle increments of +inf or -inf in an expected and natural way, e.g. an increment of +inf to a maximum accumulator, or an increment of +inf or -inf to a P50 accumulator.

However, a non-numeric increment can cause an accumulator to become pegged to a non-numeric value. This is particularly true for a NaN increment.

Specifically,

  1. Pushing an increment with value +inf or -inf to a sum or gini accumulator nullifies the effect of any previous or subsequent increment.
  2. Pushing an increment with value NaN to any accumulator nullifies the effect of any previous or subsequent increments.

In other words, an increment of one single entity, perhaps the result of a rarely occurring corner condition in model code, can cause an entire table cell to become empty. OpenM++ treats that as an error in table design or model logic, halts the run, and writes a log message like

Simulation error: Invalid increment -inf in table 'IncrementTestTable' using attribute 'my_dbl' on or after event 'MortalityEvent' in entity_id 208 in simulation member 0 with combined seed 1637697257 when current time is 88.06346668067070

This particular error message manifested in a version of the Alpha2 model which was modified to deliberately produce an increment error in the table

table Person IncrementTestTable
[integer_age >= 50]
{
    {
        value_out(my_dbl)
    }
};

The root cause of an invalid increment often occurs in a different event than the one responsible for pushing the increment. In the error message above, the invalid increment was detected when the entity was exiting the simulation after the MortalityEvent. The attribute my_dbl likely assumed a non-numeric value earlier in the simulation causing the invalid increment later.

The root cause of an invalid increment can be probed using Event Trace to examine the evolution of the specific attribute in the specific entity given in the runtime error message.

To enable event trace in the model, the following statement must be added to model code:

options event_trace = on;

and the model executable must be invoked with the argument -OpenM.IniAnyKey.

The following EventTrace settings (in an ini file) output all events and all changes in my_dbl in entity 208:

[OpenM]
LogToFile = true
TraceToFile = true

[EventTrace]
; format
ReportStyle = readable
NameColumnWidth = 20
; filters
SelectedEntities = 208
; events
ShowEvents = yes
; attributes
ShowAttributes = yes
SelectedAttributes = my_dbl

This produces the following trace file output:

         Time   Entity        Age       Id Trace               Value Name                     Remarks
     0.000000   Person   0.000000      208 ENTER                                         
     0.000000   Person   0.000000      208     attr                0   my_dbl                 initial
     0.000000   Person   0.000000      208   EVENT                   SpawnEvent          
     0.500000   Person   0.500000      208   EVENT                   EyeColourChangeEvent
     1.000000   Person   1.000000      208   EVENT                   FirstBirthdayEvent  
     2.632435   Person   2.632435      208   EVENT                   HappinessReversalEvent
     2.632435   Person   2.632435      208     attr             -inf   my_dbl                 was 0
    10.105425   Person  10.105425      208   EVENT                   MoveEvent           
    25.755745   Person  25.755745      208   EVENT                   HappinessReversalEvent
    26.741666   Person  26.741666      208   EVENT                   StartPlayingEvent   
    30.141256   Person  30.141256      208   EVENT                   MoveEvent           
    32.641206   Person  32.641206      208   EVENT                   MoveEvent           
    34.927079   Person  34.927079      208   EVENT                   HappinessReversalEvent
    54.778775   Person  54.778775      208   EVENT                   HappinessReversalEvent
    59.496134   Person  59.496134      208   EVENT                   StartPlayingEvent   
    60.500447   Person  60.500447      208   EVENT                   HappinessReversalEvent
    68.935493   Person  68.935493      208   EVENT                   MoveEvent           
    76.110163   Person  76.110163      208   EVENT                   HappinessReversalEvent
    78.448787   Person  78.448787      208   EVENT                   StartPlayingEvent   
    79.197382   Person  79.197382      208   EVENT                   MoveEvent           
    87.282644   Person  87.282644      208   EVENT                   HappinessReversalEvent
    88.063467   Person  88.063467      208   EVENT                   MortalityEvent      
    88.063467   Person  88.063467      208 EXIT                                          

This trace output shows that the my_dbl attribute first assumed the non-numeric value -inf during the HappinessReversalEvent at an early age, before the entity was in scope of the table filter.

Here's the model code responsible for the invalid increment error later in the simulation:

entity Person
{
    double my_dbl;
    void update_funny_numbers(void);
    hook update_funny_numbers, HappinessReversalEvent, 43;
};

void Person::update_funny_numbers(void)
{
    double x = 0.0;
    my_dbl = std::log(x); // is -inf
}

If necessary, a Debug version of the model can be built and run with a conditional break point added to the first line of code in the event identified by Event Trace. In this example, the break point could be set to the first line of HappinessReversalEvent, with condition entity_id == 208.

Here's the model source code of the event which was the root cause of the invalid increment.

void Person::HappinessReversalEvent()
{
    happy = !happy;
    if ( !happy && playing ) {
        // stop playing if unhappy
        playing = FALSE;
    }
    if (happy && my_first_happy_time == TIME_INFINITE) {
        my_first_happy_time = time;
    }
    hook_HappinessReversalEvent();
}

When the break point is hit, execution can be stepped line by line in the debugger until the code responsible for setting my_dbl to a non-numeric value is found.

[back to increment validity]
[back to topic contents]

Disabling table increment errors

Normal handling of an invalid table increment can be controlled by the following option:

options verify_valid_table_increment = off;  // default is on

If this option is off, a warning like the following will be written to the log on each run:

Warning : invalid table increment is not detected with verify_valid_table_increment = off

A table with a non-numeric cell will display as empty in the UI and in csv export.

[back to increment validity]
[back to topic contents]

Operators

Keyword Description
interval
event

[back to topic contents]

⚠️ **GitHub.com Fallback** ⚠️