Timestep Synchronization¶
Many models are time dependent with variables being updated as the time is incremented. When integrating two (or more) models that have timesteps, care must be taken to ensure that variables are correctly synchonized between the models at each timestep. To aid in this process yggdrasil provides a special timestep synchronization interface and model driver. Synchronizations proceeds as follows:
A client model requests synchronization at a given timestep by calling the synchronization interface with the time of the desired timestep (with units) and any local state variables to the synchronization driver.
The synchronization driver converts synonymous state variables from all models to a base set of state variables.
The synchronization driver interpolates state variables from all models to get values at the time received from the client.
The synchronization driver aggregates state variables across models for the requested time (including interpolated values).
The synchronization driver converts the aggregated base set of state variables to the synonymous state variables used by the client model.
The synchronization driver responds to the client model that issued the request with the resulting state variables.
Example Using Defaults¶
In the example below, two model are initialized from the same source code. Both models have two state variables (x
and y
) that are calculated as a sine and cosine with periods of 10 days and 5 days respectively; the two models differ only in the size of their timesteps and the units that they use to represent time (the timestep and units for each model are set by input arguments to the model as passed in the yaml).
In the yaml file below, the models are defined as usual, but they also have the timesync
parameter set to True
. Setting the timesync
parameter tells yggdrasil that the model has time dependent variables that need to be synchonized. The timesync
parameter can also be set to a string that will be used to group models that should be synchronized; in this way, sets of models can be independently synchronized (e.g. if there are unrelated processes that don’t need to be synced). For each unique value of the timesync
parameter in the provided yamls, yggdrasil sets up a specialized model with that name to handle synchronization between the models with the same timesync
parameter value (setting timesync
to True
creates a specialized timestep synchronization model with the name timesync
).
Model YAML:
1---
2
3models:
4 - name: modelA
5 language: python
6 args:
7 - ./src/timesync.py
8 - 7 # Pass the timestep in hours
9 - hr
10 timesync: True
11 outputs:
12 name: output
13 default_file:
14 name: modelA_output.txt
15 in_temp: True
16 filetype: table
17 - name: modelB
18 language: python
19 args:
20 - ./src/timesync.py
21 - 1 # Pass the timestep in days
22 - day
23 timesync: True
24 outputs:
25 name: output
26 default_file:
27 name: modelB_output.txt
28 in_temp: True
29 filetype: table
In addition to the yaml parameter, models performing timestep synchronization will need to make use of the timestep synchronization interface. In Python, this is YggTimesync
. At each timestep (including the initial time), the model executes the call
method for the timestep synchronization interface. The output variable (the variable being sent as a request by the call
method) is excepted to be the time of the timestep and a mapping type between state varaible names and their values at the timestep. The return variable (the variable received in response by the call
method) will be a mapping type between state variable names and their values that have been updated with information from the other models.
Model Code:
1import sys
2import numpy as np
3from yggdrasil import units
4from yggdrasil.interface.YggInterface import (
5 YggTimesync, YggOutput)
6
7
8def timestep_calc(t):
9 r"""Updates the state based on the time where x is a sine wave
10 with period of 10 days and y is a cosine wave with a period of 5 days.
11
12 Args:
13 t (float): Current time.
14
15 Returns:
16 dict: Map of state parameters.
17
18 """
19 state = {
20 'x': np.sin(2.0 * np.pi * t / units.add_units(10, 'day')),
21 'y': np.cos(2.0 * np.pi * t / units.add_units(5, 'day'))}
22 return state
23
24
25def main(t_step, t_units):
26 r"""Function to execute integration.
27
28 Args:
29 t_step (float): The time step that should be used.
30 t_units (str): Units of the time step.
31
32 """
33 print('Hello from Python timesync: timestep = %s %s' % (t_step, t_units))
34 t_step = units.add_units(t_step, t_units)
35 t_start = units.add_units(0.0, t_units)
36 t_end = units.add_units(5.0, 'day')
37 state = timestep_calc(t_start)
38
39 # Set up connections matching yaml
40 # Timestep synchonization connection will default to 'timesync'
41 timesync = YggTimesync('timesync')
42 out = YggOutput('output')
43
44 # Initialize state and synchronize with other models
45 t = t_start
46 ret, state = timesync.call(t, state)
47 if not ret:
48 raise RuntimeError("timesync(Python): Initial sync failed.")
49 print('timesync(Python): t = % 8s, x = %+ 5.2f, y = %+ 5.2f' % (
50 t, state['x'], state['y']))
51
52 # Send initial state to output
53 flag = out.send(dict(state, time=t))
54 if not flag:
55 raise RuntimeError("timesync(Python): Failed to send "
56 "initial output for t=%s." % t)
57
58 # Iterate until end
59 while t < t_end:
60
61 # Perform calculations to update the state
62 t = t + t_step
63 state = timestep_calc(t)
64
65 # Synchronize the state
66 ret, state = timesync.call(t, state)
67 if not ret:
68 raise RuntimeError("timesync(Python): sync for t=%f failed." % t)
69 print('timesync(Python): t = % 8s, x = %+ 5.2f, y = %+ 5.2f' % (
70 t, state['x'], state['y']))
71
72 # Send output
73 flag = out.send(dict(state, time=t))
74 if not flag:
75 raise RuntimeError("timesync(Python): Failed to send output for t=%s." % t)
76
77 print('Goodbye from Python timesync')
78
79
80if __name__ == '__main__':
81 # Take time step from the first argument
82 main(float(sys.argv[1]), sys.argv[2])
Below is the result of the synchronization between the two models. The states variable x
and y
are shown in blue and red respectively, solid lines show results for model A, dashed lines show results for model B, and the True values for x
and y
are plotted in black.
Model B has a larger timestep than model A so values for model B are interpolated to get values at the timesteps for model A. The default interpolation method assumes a linear relationship between values and the default aggregation method averages across all models; as a result, the values for the model with the smaller timestep (model A) end up being pulled away from the “True” value when aggregated with the interpolated with the values from the model with the larger timestep (model B). There are ways to control how timesteps are synchronized (as discussed below), but in practice, integrating two models of the same process with the same degree of accuracy that use different timesteps is unlikely to be useful.
Controlling Synchonization¶
There are several ways to customize how timesteps are merged between models. In order to set these options, an explicit entry must be added to the yaml for the timestep synchronization model. At a minimum the timestep synchronization model yaml entry should have language: timesync
and it’s name should be the same as the timesync
parameter value for the models it will synchronize ('timesync'
if the models have timesync: True
). Additional parameters in the timestep synchronization model yaml entry will be used to control how timesteps are synchronized.
In the example below, these parameters are used to modify how the state variables are synchronized. Models A and B are identical except:
Model A has state variables
x
andy
while Model B has state variablesxvar
andyvar
(set in the yaml)xvar
in Model B is equal to half ofx
in Model AModel A has state variables
z1
andz2
which can be used to calculatez
, a state variable calculated directly by Model B.Model A alone calculates state variable
a
, while model B alone calculates state variableb
. The models need to exchange these variables.
Model YAML:
1---
2
3models:
4 - name: statesync
5 language: timesync
6 synonyms:
7 modelB:
8 x:
9 alt: xvar
10 alt2base: ./src/timesync.py:xvar2x
11 base2alt: ./src/timesync.py:x2xvar
12 y: yvar
13 modelA:
14 z:
15 alt: [z1, z2]
16 alt2base: ./src/timesync.py:merge_z
17 base2alt: ./src/timesync.py:split_z
18 interpolation:
19 modelA: krogh
20 modelB:
21 method: spline
22 order: 5
23 aggregation:
24 x: ./src/timesync.py:xagg
25 y: sum
26 additional_variables:
27 modelA: [b]
28 modelB: [a]
29 - name: modelA
30 language: python
31 args:
32 - ./src/timesync.py
33 - 7 # Pass the timestep in hours
34 - hr
35 - A
36 timesync: statesync
37 outputs:
38 name: output
39 default_file:
40 name: modelA_output.txt
41 in_temp: True
42 filetype: table
43 - name: modelB
44 language: python
45 args:
46 - ./src/timesync.py
47 - 1 # Pass the timestep in days
48 - day
49 - B
50 timesync: statesync
51 outputs:
52 name: output
53 default_file:
54 name: modelB_output.txt
55 in_temp: True
56 filetype: table
(The source code associated with this model is very similar to the souce code above, but can be found here for reference.)
Synonyms (Conversion)¶
It is unlikely that variables will match perfectly between models, beit in name or definition. For example, in the model defined above, model A and B use different names to describe the same variable (y
and yvar
respectively). Similarly the variables x
and xvar
used by models A and B respectively are analagous, but xvar
is defined slightly differently (namely xvar=x/2
).
To handle reconcilation between analagous variables, yggdrasil allows users to define relationships between variables in different models using the synonyms
parameter. The value for the synonyms
parameter should be a mapping between model names and mapping from base variable names (these should be variables named by one or more models) and the analogous variable that the model uses. If the model just uses a different name for the same concept as the base, this can be just a string (e.g. the synonyms
entry for y
in modelB
above). If additional calculations are required to convert between the variables used in the models, a mapping can be provided (e.g. the synonyms
entry for x
in modelB
). In addition, calculation mapping from one model to another can also involve more than one variable (e.g. the synonyms
entry for z
in modelA
). The keys in the entry should be:
alt
: One or more state variables used by the model to calculate the base state variable.alt2base
: Function for converting from the state variable(s) used by the model to the base variable.base2alt
: Function for converting from the base variable to the state variable(s) used by the model.
Note
Units conversions will be handled by yggdrasil and do not need to be addressed by the synonyms
parameters so long as the state variables have units when passed to the timestep synchronization interface call
method.
Additional Variables¶
Models can also request state variables from other models that they do not calculate themselves via the additional_variables
parameter in the yaml. The value for this parameter should be a mapping from model name to a list of external variables that should be returned to the model. For example, in the above yaml, model A requests state variable b
from model B and model B requests state variable a
from model A.
Interpolation¶
The interpolation
parameter controls how data is obtained for timesteps that are not sampled by the model. This is particularly important for models that have very different timesteps. Values for the interpolation
parameter can be strings specifying the method that should be used to interpolate or mapping of keyword arguments to pandas.DataFrame.interpolate. A single interpolation
parameter can be used for all models or specialed interpolation
parameters can be specified a model-by-model basis using a mapping as in the example above. Interpolation is done on the data from each model independently after applying the transformations described by the synonyms
parameter.
Aggregation¶
After the model data is interpolated to get missing timesteps, the data samples at each timestep are aggregated. By default, the samples from each model are averaged, but there are many ways to aggregate data. In the example above, the data for the x
variable are aggregated using a function xagg
from the ./src/timesync.py
file which always returns the largest absolute value. The data for the y
variable are aggregated by summing as would be the case when two models represent separate processes that modify a cumulative variable (e.g. mass added/subtracted by different processes).
Additional Tips¶
In addition to dictionaries mapping from variable to method, a single value can be provided for the aggregation
and interpolation
parameter; the same method will the be used for all of the variables e.g.:
- name: statesync
language: timesync
synonyms:
modelB:
x:
alt: xvar
alt2base: ./src/timesync.py:xvar2x
base2alt: ./src/timesync.py:x2xvar
y: yvar
interpolation: nearest
aggregation: min
Note
Since each synchronization call invokes overhead, it is not advised that the call
method be executed inside integration methods. Instead, synchonization call
methods should be executed at larger timesteps.