Illustrative Model Align1
Home > Model Development Topics > Illustrative Model Align1
Align1
is an experimental model which manipulates the event queue to align with external counts.
This topic describes the approach and implementation, and includes some experiments and notes.
- Introduction and description
- Experiment #1 Illustrative run
- Experiment #2 Computational cost
- Remarks Some notes on the approach and possible future steps
-
Align1
code Model code moduleAlignment.mpp
-
Align1
input Default parameter valuesAlignment.dat
The Align1
model is a proof of concept and testbed for dynamic alignment of time-based models using event queue look-ahead. Align1
steers itself to aggregate annual targets by reading and modifying the event queue dynamically at the beginning of each year.
Align1
is based on the NewTimeBased
model which is part of the OpenM++ distribution. It adds alignment apparatus in a new module Alignment.mpp
and associated parameters in Alignment.dat
but is otherwise unmodified. NewTimeBased
has a Ticker
entity with a timekeeping TickEvent
and Person
entities with Mortality
events. Align1
adds a new event AlignmentEvent
to Ticker
, which occurs at the same time as TickEvent
but at lower priority. The input parameter MortalityAlignmentTarget
contains target mortality counts by year. At the beginning of each year AlignmentEvent
reads the event queue and counts the number of deaths scheduled to occur during that year. If the count is higher than the target for the year, enough scheduled events are deferred to the subsequent year to hit the target. If the count is lower than the target, enough scheduled events are advanced from subsequent years to the current year to hit the target. The exact rules are mechanical and described in model code comments. This process is repeated at the beginning of each year.
The events which are deferred or advanced to hit alignment targets are those which are closest to the upper boundary of the current alignment year.
The Default run has 10,000 Persons and sets MortalityAlignmentTarget
to the mortality counts which would occur in the absence of alignment (using table MortalityCounts from a previous run with alignment off).
This experiment sets target mortality to 122 in each of the 20 years. 122 is the average annual mortality in the 20 years in the Default run with no alignment. The first column shows mortality counts in the Default run with no alignment. One observes a secular decrease (with some noise) because the population is progressively smaller so fewer die each year due to the lower base population (the morality hazard is constant). The target column, in contrast, sets the number of deaths to a fixed value in each year. Compared to the Default run, deaths need to be decreased in early years and increased in later years to hit the target.
Year | Mortality (Default) | Target | Mortality (Aligned) | Events Deferred | Events Advanced |
---|---|---|---|---|---|
0 | 141 | 122 | 122 | 19 | 0 |
1 | 144 | 122 | 122 | 41 | 0 |
2 | 114 | 122 | 122 | 33 | 0 |
3 | 146 | 122 | 122 | 57 | 0 |
4 | 142 | 122 | 122 | 77 | 0 |
5 | 126 | 122 | 122 | 81 | 0 |
6 | 129 | 122 | 122 | 88 | 0 |
7 | 139 | 122 | 122 | 105 | 0 |
8 | 111 | 122 | 122 | 94 | 0 |
9 | 118 | 122 | 122 | 90 | 0 |
10 | 112 | 122 | 122 | 80 | 0 |
11 | 118 | 122 | 122 | 76 | 0 |
12 | 102 | 122 | 122 | 56 | 0 |
13 | 110 | 122 | 122 | 44 | 0 |
14 | 104 | 122 | 122 | 26 | 0 |
15 | 116 | 122 | 122 | 20 | 0 |
16 | 114 | 122 | 122 | 12 | 0 |
17 | 124 | 122 | 122 | 14 | 0 |
18 | 112 | 122 | 122 | 4 | 0 |
19 | 112 | 122 | 122 | 0 | 6 |
The algorithm attained the annual targets by deferring mortality events in each of the first 19 years and advancing 6 mortality events for the final year. Mortality events were advanced only in the final year of the run because for all other years sufficient deaths had been deferred in previous years. The annual targets are hit exactly because event times are not recalculated during the year-in-progress in this model.
This second experiment explored computational cost and scaling behaviour, using a run with 10 million Person
entities.
With this population size, there were over 100,000 mortality events per year.
Three runs were done.
Run | Description | Time |
---|---|---|
1 | No alignment | 1m25s |
2 | Alignment with targets=actual | 1m27s |
3 | Alignment with targets=+/- 5% actual | 1m26s |
Run 1 had alignment disabled. Run 2 had alignment enabled, but the targets were the same as the results without alignment. So, no adjustment of the event queue was done, but the event queue was probed each year. Run 3 had random targets within +/- 5% of the original mortality results. So alignment was doing some work with the event queue to hit the targets in run 3.
The table shows that the run times were indistinguishable. For this model, anyway, the incremental cost of alignment was barely detectable.
- A natural way for continuous time models to align is to tinker with the timing of events which “would have occurred anyway”. This helps preserve aspects of model logic, since prohibited events remain prohibited under alignment.
- An event which was deferred or advanced by alignment will still have its event time recomputed if entity attributes change (perhaps to +inf if the entity is no longer eligible for the event). This maintains aspects of the internal causative logic of the model, even under alignment.
- The ‘event censoring’ optimization should perhaps not be used in models using this alignment technique, since that might deplete the pool of future events which can be advanced into an alignment interval. For example, in experiment 1 above, the 6 events which were advanced to year 19 to hit the target had original times beyond the end of the run (which ended at time=20). They would have been right censored hence never placed in the queue.
- In the current version of
Align1
, event times do not change during the simulation within an alignment window (year). It might be interesting to add a birthday event to the model and have a mortality schedule which varies by single year of age, to make tests more realistic. - The algorithm could be adapted to split an alignment interval (year) into sub-intervals (e.g. 10 equal intervals in each year), with recalculation of progress to the target for the current year. That would allow the algorithm to adjust for interacting events during the simulation of the current year. That would require counting events as they occur during the alignment interval, which was not needed in this version of
Align1
. - As the number of alignment targets increases, and if targets are classified by entity characteristics (e.g. age group), the code volume for alignment could become massive and error-prone (even though mechanical). That makes it a tempting candidate for new supporting functionality to automate some aspects.
Below is the model source code for the module Alignment.mpp
:
//LABEL (Alignment, EN) Alignment using event queue
#include "omc/optional_IDE_helper.h" // help an IDE editor recognize model symbols
#if 0 // Hide non-C++ syntactic island from IDE
parameters {
bool EnableAlignment;
int MortalityAlignmentTarget[REPORT_TIME];
};
actor Ticker
{
//EN Time of next Alignment event
TIME next_alignment;
//EN Mortality events deferred by alignment (cumulative)
int mortality_deferred;
//EN Mortality events advanced by alignment (cumulative)
int mortality_advanced;
//EN Deficit in advanced mortality events because queue was exhausted (cumulative)
int mortality_deficit;
// AlignmentEvent should be lower priority than any other event
// so that it executes after other tied events which might influence
// events in the current alignment interval.
event timeAlignmentEvent, AlignmentEvent, 1; //EN Alignment event
};
table Person MortalityCounts
{
report_time
* {
entrances(alive, false) //EN Mortality events
}
};
table Ticker AlignmentReport
{
report_time
* {
mortality_deferred, //EN Mortality events deferred
mortality_advanced, //EN Mortality events advanced
mortality_deficit //EN Mortality target deficit
}
};
#endif // Hide non-C++ syntactic island from IDE
TIME Ticker::timeAlignmentEvent()
{
// is synchronous with TickEvent, but lower priority
return EnableAlignment ? next_alignment : time_infinite;
}
void Ticker::AlignmentEvent(void)
{
// get event_id of MortalityEvent
int event_id_mortality = omr::event_name_to_id("MortalityEvent");
assert(event_id_mortality != -1); // MortalityEvent not found
// width of the target window
TIME alignment_window_width = 1.0;
// The upper bound of the target window, NB is just beyond the current alignment window
TIME alignment_window_upper_bound = time + alignment_window_width;
int alignment_window_target_count = MortalityAlignmentTarget[report_time];
//
// walk the event queue, from present to future
//
// add events to defer_list or advance_list
// as needed to hit the target count in the alignment time window
//
auto& event_queue = *BaseEvent::event_queue; // alias for the model event queue
std::forward_list<BaseEvent*> defer_list; // list of events to defer
std::forward_list<BaseEvent*> advance_list; // list of events to advance
int unadjusted_count = 0; // count of scheduled events in the alignment time window before alignment
int deferred_events = 0; // count of scheduled events deferred to the future beyond the alignment time window
int advanced_events = 0; // count of scheduled events advanced from the future to within the alignment time window
for (auto evt : event_queue) {
int id = evt->get_event_id();
if (id != event_id_mortality) {
// not a mortality event, skip
continue;
}
double evt_time = evt->event_time;
if (evt_time < alignment_window_upper_bound) {
// we are inside the target alignment time window
// update the count of currently scheduled events within the window
++unadjusted_count;
if (unadjusted_count > alignment_window_target_count) {
// there is an excess of scheduled events within the alignment time window
// so add this event to the defer list
defer_list.push_front(evt);
++deferred_events;
}
}
else {
// we are beyond the alignment time window
if (advanced_events + unadjusted_count >= alignment_window_target_count) {
// no need to find more events to advance, have found what's needed
// so stop queue walk
break;
}
// there is a deficit of events within the alignment time window
// so add this event to the advance list
advance_list.push_front(evt);
++advanced_events;
}
}
if (alignment_window_target_count > unadjusted_count && unadjusted_count - alignment_window_target_count != advanced_events) {
// there were insufficient events in the queue beyond the alignment window to meet the target
mortality_deficit += alignment_window_target_count - unadjusted_count - advanced_events; // for AlignmentReport
}
if (deferred_events > 0) {
mortality_deferred += deferred_events; // for AlignmentReport
assert(advanced_events == 0);
for (auto evt : defer_list) {
// defer this event
// remove it from the event queue
event_queue.erase(evt);
// postpone the event time by one alignment interval
evt->event_time += alignment_window_width;
// re-insert it to the event queue
event_queue.insert(evt);
}
}
else if (advanced_events > 0) {
mortality_advanced += advanced_events; // for AlignmentReport
// advance this event
for (auto evt : advance_list) {
// advance this event
// remove it from the event queue
event_queue.erase(evt);
// advance the event time by an integral number of alignment intervals
// until it falls within the alignment window
TIME new_time = evt->event_time;
while (new_time >= alignment_window_upper_bound) {
new_time -= alignment_window_width;
}
evt->event_time = new_time;
// re-insert it to the event queue
event_queue.insert(evt);
}
}
else {
// nothing to do
}
{
// schedule next alignment
Time t = next_alignment + alignment_window_width;
if (t >= SimulationEnd) {
next_alignment = time_infinite;
}
else {
next_alignment = t;
}
}
}
Below is the Default model input parmeters in Alignment.dat
:
parameters {
bool EnableAlignment = true;
int MortalityAlignmentTarget[REPORT_TIME] = {
141, //141,
144, //144,
114, //114,
146, //146.
142, //142,
126, //126,
129,
139,
111,
118,
112,
118,
102,
110,
104,
116,
114,
124,
112,
112,
};
};
- Windows: Quick Start for Model Users
- Windows: Quick Start for Model Developers
- Linux: Quick Start for Model Users
- Linux: Quick Start for Model Developers
- MacOS: Quick Start for Model Users
- MacOS: Quick Start for Model Developers
- Model Run: How to Run the Model
- MIT License, Copyright and Contribution
- Model Code: Programming a model
- Windows: Create and Debug Models
- Linux: Create and Debug Models
- MacOS: Create and Debug Models
- MacOS: Create and Debug Models using Xcode
- Modgen: Convert case-based model to openM++
- Modgen: Convert time-based model to openM++
- Modgen: Convert Modgen models and usage of C++ in openM++ code
- Model Localization: Translation of model messages
- How To: Set Model Parameters and Get Results
- Model Run: How model finds input parameters
- Model Output Expressions
- Model Run Options and ini-file
- OpenM++ Compiler (omc) Run Options
- OpenM++ ini-file format
- UI: How to start user interface
- UI: openM++ user interface
- UI: Create new or edit scenario
- UI: Upload input scenario or parameters
- UI: Run the Model
- UI: Compare model run results
- UI: Aggregate and Compare Microdata
- UI: Disk space usage and cleanup
- UI Localization: Translation of openM++
- Authored Model Documentation
- Built-in Attributes
- Censor Event Time
- Create Import Set
- Derived Tables
- Entity Attributes in C++
- Entity Function Hooks
- Entity Member Packing
- Entity Tables
- Events
- Event Trace
- External Names
- Generated Model Documentation
- Illustrative Model
Align1
- Lifecycle Attributes
- Local Random Streams
- Memory Use
- Microdata Output
- Model Code
- Model Documentation
- Model Languages
- Model Localization
- Model Metrics Report
- Model Resource Use
- Model Symbols
- Parameter and Table Display and Content
- Population Size and Scaling
- Symbol Labels and Notes
- Tables
- Test Models
- Time-like and Event-like Attributes
- Use Modules
- Weighted Tabulation
- File-based Parameter Values
- Oms: openM++ web-service
- Oms: openM++ web-service API
- Oms: How to prepare model input parameters
- Oms: Cloud and model runs queue
- Use R to save output table into CSV file
- Use R to save output table into Excel
- Run model from R: simple loop in cloud
- Run RiskPaths model from R: advanced run in cloud
- Run RiskPaths model in cloud from local PC
- Run model from R and save results in CSV file
- Run model from R: simple loop over model parameter
- Run RiskPaths model from R: advanced parameters scaling
- Run model from Python: simple loop over model parameter
- Run RiskPaths model from Python: advanced parameters scaling
- Windows: Use Docker to get latest version of OpenM++
- Linux: Use Docker to get latest version of OpenM++
- RedHat 8: Use Docker to get latest version of OpenM++
- Quick Start for OpenM++ Developers
- Setup Development Environment
- 2018, June: OpenM++ HPC cluster: Test Lab
- Development Notes: Defines, UTF-8, Databases, etc.
- 2012, December: OpenM++ Design
- 2012, December: OpenM++ Model Architecture, December 2012
- 2012, December: Roadmap, Phase 1
- 2013, May: Prototype version
- 2013, September: Alpha version
- 2014, March: Project Status, Phase 1 completed
- 2016, December: Task List
- 2017, January: Design Notes. Subsample As Parameter problem. Completed
GET Model Metadata
- GET model list
- GET model list including text (description and notes)
- GET model definition metadata
- GET model metadata including text (description and notes)
- GET model metadata including text in all languages
GET Model Extras
GET Model Run results metadata
- GET list of model runs
- GET list of model runs including text (description and notes)
- GET status of model run
- GET status of model run list
- GET status of first model run
- GET status of last model run
- GET status of last completed model run
- GET model run metadata and status
- GET model run including text (description and notes)
- GET model run including text in all languages
GET Model Workset metadata: set of input parameters
- GET list of model worksets
- GET list of model worksets including text (description and notes)
- GET workset status
- GET model default workset status
- GET workset including text (description and notes)
- GET workset including text in all languages
Read Parameters, Output Tables or Microdata values
- Read parameter values from workset
- Read parameter values from workset (enum id's)
- Read parameter values from model run
- Read parameter values from model run (enum id's)
- Read output table values from model run
- Read output table values from model run (enum id's)
- Read output table calculated values from model run
- Read output table calculated values from model run (enum id's)
- Read output table values and compare model runs
- Read output table values and compare model runs (enun id's)
- Read microdata values from model run
- Read microdata values from model run (enum id's)
- Read aggregated microdata from model run
- Read aggregated microdata from model run (enum id's)
- Read microdata run comparison
- Read microdata run comparison (enum id's)
GET Parameters, Output Tables or Microdata values
- GET parameter values from workset
- GET parameter values from model run
- GET output table expression(s) from model run
- GET output table calculated expression(s) from model run
- GET output table values and compare model runs
- GET output table accumulator(s) from model run
- GET output table all accumulators from model run
- GET microdata values from model run
- GET aggregated microdata from model run
- GET microdata run comparison
GET Parameters, Output Tables or Microdata as CSV
- GET csv parameter values from workset
- GET csv parameter values from workset (enum id's)
- GET csv parameter values from model run
- GET csv parameter values from model run (enum id's)
- GET csv output table expressions from model run
- GET csv output table expressions from model run (enum id's)
- GET csv output table accumulators from model run
- GET csv output table accumulators from model run (enum id's)
- GET csv output table all accumulators from model run
- GET csv output table all accumulators from model run (enum id's)
- GET csv calculated table expressions from model run
- GET csv calculated table expressions from model run (enum id's)
- GET csv model runs comparison table expressions
- GET csv model runs comparison table expressions (enum id's)
- GET csv microdata values from model run
- GET csv microdata values from model run (enum id's)
- GET csv aggregated microdata from model run
- GET csv aggregated microdata from model run (enum id's)
- GET csv microdata run comparison
- GET csv microdata run comparison (enum id's)
GET Modeling Task metadata and task run history
- GET list of modeling tasks
- GET list of modeling tasks including text (description and notes)
- GET modeling task input worksets
- GET modeling task run history
- GET status of modeling task run
- GET status of modeling task run list
- GET status of modeling task first run
- GET status of modeling task last run
- GET status of modeling task last completed run
- GET modeling task including text (description and notes)
- GET modeling task text in all languages
Update Model Profile: set of key-value options
- PATCH create or replace profile
- DELETE profile
- POST create or replace profile option
- DELETE profile option
Update Model Workset: set of input parameters
- POST update workset read-only status
- PUT create new workset
- PUT create or replace workset
- PATCH create or merge workset
- DELETE workset
- POST delete multiple worksets
- DELETE parameter from workset
- PATCH update workset parameter values
- PATCH update workset parameter values (enum id's)
- PATCH update workset parameter(s) value notes
- PUT copy parameter from model run into workset
- PATCH merge parameter from model run into workset
- PUT copy parameter from workset to another
- PATCH merge parameter from workset to another
Update Model Runs
- PATCH update model run text (description and notes)
- DELETE model run
- POST delete model runs
- PATCH update run parameter(s) value notes
Update Modeling Tasks
Run Models: run models and monitor progress
Download model, model run results or input parameters
- GET download log file
- GET model download log files
- GET all download log files
- GET download files tree
- POST initiate entire model download
- POST initiate model run download
- POST initiate model workset download
- DELETE download files
- DELETE all download files
Upload model runs or worksets (input scenarios)
- GET upload log file
- GET all upload log files for the model
- GET all upload log files
- GET upload files tree
- POST initiate model run upload
- POST initiate workset upload
- DELETE upload files
- DELETE all upload files
User: manage user settings and data
Model run jobs and service state
- GET service configuration
- GET job service state
- GET disk usage state
- POST refresh disk space usage info
- GET state of active model run job
- GET state of model run job from queue
- GET state of model run job from history
- PUT model run job into other queue position
- DELETE state of model run job from history
Administrative: manage web-service state
- POST a request to refresh models catalog
- POST a request to close models catalog
- POST a request to close model database
- POST a request to open database file
- POST a request to cleanup database file
- GET the list of database cleanup log(s)
- GET database cleanup log file(s)
- POST a request to pause model run queue
- POST a request to pause all model runs queue
- PUT a request to shutdown web-service