-
Notifications
You must be signed in to change notification settings - Fork 1
Time
Quartz features a multiscale representation of simulated time, as described in the following paper. The approach addresses the drawbacks of fixed-point or floating-point numbers while having restrained performances penalty compared to full arbitrary-precision approaches.
Without going into too much details, the idea is that the simulator keeps a reference to a precise representation of the current simulated time while models perceive and express time relative to their associated precision level, with duration values.
A precise point in simulated time is implemented as an arbitrary-precision integer under the TimePoint class. Its main purpose is to describe event times as offsets from a common reference point and it is meant to be used internally. The modeller should not directly manipulate such values. Instead, modellers may perturbate simulated time through Duration values.
A Duration is a fixed-point time data type which encapsulates a multiplier and a precision level (a Scale value).
Models express time values relative to the current simulated time. If an event is measured from the most recent past event, a duration value can express the elapsed time since this past event. Similarly, a duration can represent a time value from the current point in simulated time to an imminent future event, a.k.a. as a planned duration.
Duration values are typically constructed with a number and a scale (Scale | Int):
Duration.new(30, Scale::MILLI) # 30 milli time units
Duration.new(30, -2) # 30 micro time units
Duration.new(2) # 2 time unitsScale values are used to represent the precision level at which a Model is able to process inputs, as well as express the duration of its internal state. It is an approximation of the degree to which Duration values are altered.
The multiscale time representation rely on a base-1000 SI time unit, which is used to represent one scale from the next.
Scale values store precision levels with a Int8 integer, which means scales can go from 1000^-128 to 1000^127. The base scale is 1000^0.
Convenient constants are defined for common scales, ranging from zepto (1000^-7) to zetta (1000^7), e.g. Scale::ZEPTO, ..., Scale::MILLI, Scale::BASE, Scale::KILO, ..., Scale::ZETTA.
Otherwise, scale values can be initialized with an integer, e.g. Scale.new(-3).
The maximum representable duration for a chosen scale is 1000^5 - 1. If a duration represents microseconds, the representable range goes from 0us to 999,999,999.999999us (~ 31 years).
A duration is considered infinite when the 1000^5 limit is reached.
Duration.new(1000**5).infinite? # => trueAn infinite duration can also be constructed from infinite float constant value or via the .infinity static method.
Duration.new(Float64::INFINITY).infinite? # => true
Duration.infinityBy default, operations on durations will return a duration with a coarser or a refined scale:
Duration.new(4) + Duration.new(10, -1) # => Duration.new(4010, -1)
1000000 * Duration.new(1000000000, -5) # => Duration.new(1000000000000, -4)
(Duration(1000**5 - 1) + 1).infinite? # => false, (value is 1_000_000_000_000 kilo time units)In other words, is has wrapping arithmetic semantics.
You may want to change the semantics of Duration to saturating arithmetic semantics.
This can be done via the constructor, e.g.
(Duration.new(1000**5-1, -4, true) + 1).infinite? # => trueor through the #fixed and #fixed_at methods:
(Duration.new(1000**5-1, -4).fixed + 1).infinite? # => trueQuartz also extends numbers with convenient methods to build duration values:
30_milli_time_units # 30 milli time units
30_micro_time_units # 30 micro time units
2.time_units # 2 time unitsFinally, the Quartz.duration is provided, and must receive a number literal along with an
optional scale unit. The scale unit can be specified with a constant expression (e.g. "kilo")
or with a Scale value.
If you use a floating-point value to initialize the multiplier of the duration, only the integer part is used to initialize the duration value:
Duration.new(10.84512, kilo) # => 10000 time unitsAs it is common to manipulate such values in models, it is possible to "guess" a scale
from a float using Duration.from:
Duration.from(0.000000752) # => 752 nano time unitsAllowing models to operate at disparate scales in the same simulation have several implications.
Models in a multiscale time environment requires an explicit precision to be defined.
This can be done via the precision macro for discrete event models:
class DiscreteEventModel < Quartz::AtomicModel
precision nano
endFor discrete time models, the precision is included when you choose a time base using the delta macro:
class DiscreteTimeModel
delta 10, milli
endThe virtual time at which a receiver model gets an input from a model with a finer precision is truncated to the receiver precision. For example, consider the two following models with different assigned precisions:
+---------+ +---------+
| | | |
| A +---->+ B |
| | | |
| micro | | milli |
+---------+ +---------+
Model 'A' generates values sent to the passive model 'B'. If the base time unit represents seconds, when model 'A' sends its first output value, say at time 5320μs, the model 'B', which operates at a greater scale will perceive the input event from 'A' in milliseconds. The elapsed time since last 'B' activation is then 5ms.
Note there is no going back in time since the simulator always maintains a precise value. However this value can appear truncated to match the chosen time abstraction of a model. Causality is still guaranteed.
To avoid such behavior and perceive finer elapsed times, the model precision has to be adjusted accordingly. As a rule of thumb, substract 3 from the scale at which your model operate autonomously.
When choosing a precision level for your model, bear in mind that duration values cannot exceed 1000^5 - 1.
As such, the next planned internal event of a model cannot exceed this limit.
When coupling models with extremely disparate scales, it is possible that a model perceive an infinite elapsed duration.
This means a model with a coarser precision sends an output event that is perceived as an input event by a model with a finer precision. The latter model was inactive for a duration that exceeds the 1000**5 - 1 limit when the duration is converted to the finer precision.