Modeling time in ABS¶
ABS provides a logical clock, a rational-valued global counter starting at 0 –- see 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 https://github.com/abstools/absexamples/tree/master/collaboratory/examples/time-and-date/.
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
The 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<Pair<Rat, Int>> getValues();
Unit addValue(Int value);
}
class Calendar implements Calendar {
List<Pair<Rat, Int>> values = list[];
String getStartDate() { return start_date(); }
List<Pair<Rat, Int>> 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 \(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:
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.
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Date and Time in ABS</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://code.highcharts.com/highcharts.js"></script>
<script src="https://code.highcharts.com/modules/exporting.js"></script>
<script src="https://code.highcharts.com/modules/export-data.js"></script>
</head>
<body>
<h1>Date and Time in ABS</h1>
<div id="chart-container">
</div>
<script type="text/javascript">
'use strict';
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.getTime(); // number of milliseconds, as needed by Highcharts
}
function drawChart(){
$.when(
$.getJSON("/call/Calendar/getStartDate"),
$.getJSON("/call/Calendar/getValues")
).done(function(resstartdate, resvalues) {
let startdate = resstartdate[0].result;
let data = resvalues[0].result.map(p => [absolute_date(startdate, p.fst), p.snd]);
Highcharts.chart('chart-container', {
type: 'line',
title: { text: 'Dates and values from ABS' },
xAxis: { type: 'datetime' },
yAxis: { title: { text: 'Value (random number)' } },
series: [{
name: 'random value', data: data
}]
});
});
}
$(document).ready(function(){
drawChart();
});
</script>
</body>
</html>
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:
.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}'