Modeling time in ABS
====================
ABS provides a logical clock, a rational-valued global counter
starting at 0 –- see :ref:`sec:timed-abs` in the language manual for
details. This example shows how to use ABS to run simulations modeling
real (calendar) time and visualize the resulting data in a timeline.
The code can be found at
``__.
The following short ABS model creates a list of values together with
the time when the value was produced. The results are stored in a
``Calendar`` object that is accessible via the ABS Model API (see
:ref:`sec:model-api`).
During the simulation, the method ``addValue`` is called with random
values. The calendar object keeps track of the value and the time when
it was added.
The calendar also makes two methods callable via the Model API:
``getValues`` returns a list of pairs of recorded time and value. The
method ``getStartDate`` returns a string containing a date in ISO 8601
notation; this is the “real” start date that will be used in the
simulation. The time values in the list returned by ``getValues`` will
be interpreted as *offset* from that point in time::
module CalendarTime;
def String start_date() = "2020-04-01";
interface Calendar {
[HTTPCallable] String getStartDate();
[HTTPCallable] List> getValues();
Unit addValue(Int value);
}
class Calendar implements Calendar {
List> values = list[];
String getStartDate() { return start_date(); }
List> getValues() { return reverse(values); }
Unit addValue(Int value) {
values = Cons(Pair(timeValue(now()), value), values);
}
}
{
[HTTPName: "Calendar"] Calendar c = new Calendar();
Int nRounds = 16;
while (nRounds > 0) {
c!addValue(random(50));
await duration(1/4, 1/4);
nRounds = nRounds - 1;
}
println(`Finished at $now()$`);
}
The main block records in-between advancing the clock by ¼ (0.25).
Converting time values
----------------------
To visualize times properly, they must be converted to a format that
the visualization library expects.
In addition to the chosen offset (2020-04-01 in our case), we also
need a *scaling factor* to convert the logical clock values of ABS to
real date and time values. We choose to represent each day by an
increment of :math:`1`; fractional values of the clock represent the
time of day. Since our model advances the clock by ¼ sixteen times,
the values are recorded at 6am, 12pm, 6pm and midnight for four days.
As an example, here is a Javascript function that takes the startdate
(as a string parseable via ``new Date``) and offset (as a floating
point number, with the integer part the number of days and the
fractional part the time of day, with ``0.5`` exactly noon) and
returns a Javascript ``Date`` object:
.. code:: javascript
function absolute_date(startdate, offset) {
let days = Math.floor(offset);
let time = offset - days;
let date = new Date(startdate);
date.setDate(date.getDate() + days);
date.setTime(date.getTime() + time * 24 * 60 * 60 * 1000);
return date;
}
Visualizing the time series
---------------------------
The following is a complete HTML file that uses the `Highcharts
library `__ to
draw a line diagram of the random values generated by ABS. The
function ``drawChart`` retrieves the starting offset and value list
via the Model API, then creates a Highcharts chart with the downloaded
values.
.. code:: html
Date and Time in ABS
Date and Time in ABS
Running the example
-------------------
The following small Makefile can be used to compile and start the model.
Make sure to store the ABS model in a file ``CalendarTime.abs`` and the
html in a file ``index.html``, then type ``make run`` and connect the
browser to http://localhost:8080:
.. code:: make
.PHONY: all
all: run ## Generate data and start model (default)
.PHONY: compile
compile: CalendarTime.abs index.html ## Compile the model
absc --erlang CalendarTime.abs --modelapi-index-file index.html
.PHONY: compile
run: compile ## Execute the model
gen/erl/run -p 8080 -v
.PHONY: help
help: ## Display this message
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'