SimulationFunctions that are defined as functions of time currently operate on the simulation day index. That is, in $f(t)$, $t$ is a monotonically-increasing integer in the range 0 up until the number of total days being simulated. Notably the index ignores the calendar dates associated with the simulation. This works for many kinds of simulation functions (movement clauses for instance), but causes difficulties for others. Especially in multi-stage pipeline situations like are currently in development.
For example, defining a parameter as a sine wave whose value fluctuates seasonally over the calendar year is more difficult than we would like. A naive implementation might look like this:
from typing_extensions import override
from epymorph.kit import *
import numpy as np
import matplotlib.pyplot as plt
class SeasonalWaveA(ParamFunctionTime[np.float64]):
@override
def evaluate1(self, day: int) -> float:
return np.sin(2 * np.pi * day / 365)
def f_a(time_frame, start_date_index=0):
"""Eval function for time_frame and return (x_values, y_values) for plotting."""
t = np.arange(start_date_index, start_date_index + time_frame.days)
ft = SeasonalWaveA().with_context(time_frame=time_frame).evaluate()
return t, ft
tf1 = TimeFrame.rangex("2020-01-01", "2020-04-01")
tf2 = TimeFrame.rangex("2020-04-01", "2020-07-01")
# Plot the function over the first time frame (blue)
plt.plot(*f_a(tf1))
# Plot the function over the second time frame (orange)
plt.plot(*f_a(tf2, tf1.days))
plt.title("Problematic Example A")
plt.show()
Evaluating the functions over different time frames does not produce a consistent seasonal pattern as we might hope because, during evaluation, the day index always starts from 0.
Fixing this requires encoding explicit calendar date handling into the function logic:
from datetime import date
class SeasonalWaveB(ParamFunctionTime[np.float64]):
def __init__(self, time_zero: date):
self.time_zero = time_zero
@override
def evaluate1(self, day: int) -> float:
offset = (self.time_frame.start_date - self.time_zero).days
return np.sin(2 * np.pi * (offset + day) / 365)
def f_b(time_frame, time_zero: date, start_date_index=0):
"""Eval function for time_frame and return (x_values, y_values) for plotting."""
t = np.arange(start_date_index, start_date_index + time_frame.days)
ft = SeasonalWaveB(time_zero).with_context(time_frame=time_frame).evaluate()
return t, ft
tf1 = TimeFrame.rangex("2020-01-01", "2020-04-01")
tf2 = TimeFrame.rangex("2020-04-01", "2020-07-01")
# Plot the function over the first time frame (blue)
plt.plot(*f_b(tf1, tf1.start_date))
# Plot the function over the second time frame (orange)
plt.plot(*f_b(tf2, tf1.start_date, tf1.days))
plt.title("Nicely Continuous Example B")
plt.show()
We now get seasonal behavior regardless of the time frame evaluated, however this implementation is verbose and requires us to provide a construction-time parameter which might be prone to user error.
This task is to investigate improvements to the way dates are handled for these use-cases.
SimulationFunctions that are defined as functions of time currently operate on the simulation day index. That is, in$f(t)$ , $t$ is a monotonically-increasing integer in the range 0 up until the number of total days being simulated. Notably the index ignores the calendar dates associated with the simulation. This works for many kinds of simulation functions (movement clauses for instance), but causes difficulties for others. Especially in multi-stage pipeline situations like are currently in development.
For example, defining a parameter as a sine wave whose value fluctuates seasonally over the calendar year is more difficult than we would like. A naive implementation might look like this:
Evaluating the functions over different time frames does not produce a consistent seasonal pattern as we might hope because, during evaluation, the day index always starts from 0.
Fixing this requires encoding explicit calendar date handling into the function logic:
We now get seasonal behavior regardless of the time frame evaluated, however this implementation is verbose and requires us to provide a construction-time parameter which might be prone to user error.
This task is to investigate improvements to the way dates are handled for these use-cases.