Skip to content
Permalink
Browse files

incorporate changes from wc_sim fall 2018; in TemplatePeriodicSimulat…

…ionObject compute event times as (period number * period) to avoid cumulative roundoff errors in event times from repeated addition
  • Loading branch information
artgoldberg committed Nov 7, 2019
1 parent a792631 commit 7cf887b0a8569341366d0a833e4b54c61263d27d
@@ -211,7 +211,8 @@ def simulate(self, end_time, epsilon=None, stop_condition=None, progress=False):
Returns:
:obj:`int`: the number of times a simulation object executes `_handle_event()`. This may
be smaller than the number of events sent, because simultaneous events are handled together.
be smaller than the number of events sent, because simultaneous events at one
simulation object are handled together.
Raises:
:obj:`SimulatorError`: if the ratio of `end_time` to `epsilon` is so large that `epsilon`
@@ -20,20 +20,26 @@ class TemplatePeriodicSimulationObject(ApplicationSimulationObject):
Events occur at time 0, `period`, `2 x period`, ...
To avoid cumulative roundoff errors in event times from repeated addition, event times are
computed by multiplying period number times period.
Attributes:
period (:obj:`float`): interval between events, in simulated seconds
period_count (:obj:`int`): count of the next period
"""

def __init__(self, name, period):
if period <= 0:
raise SimulatorError("period must be positive, but is {}".format(period))
self.period = period
self.period_count = 0
super().__init__(name)

def schedule_next_event(self):
""" Schedule the next event in `self.period` simulated seconds
"""
self.send_event(self.period, self, NextEvent())
self.send_event_absolute(self.period_count * self.period, self, NextEvent())
self.period_count += 1

def handle_event(self):
""" Handle the periodic event
@@ -44,7 +50,6 @@ def handle_event(self):

def send_initial_events(self):
# create the initial event
self.handle_event()
self.schedule_next_event()

def handle_simulation_event(self, event):
@@ -14,8 +14,8 @@
from wc_utils.util.rand import RandomStateManager
from de_sim.simulation_engine import SimulationEngine
from de_sim.simulation_checkpoint_object import (AbstractCheckpointSimulationObject,
CheckpointSimulationObject,
AccessStateObjectInterface)
CheckpointSimulationObject,
AccessStateObjectInterface)
from de_sim.simulation_message import SimulationMessage
from de_sim.simulation_object import ApplicationSimulationObject
from de_sim.errors import SimulatorError
@@ -51,7 +51,7 @@ def __init__(self, name, delay, simulation_state, a, b):
super().__init__(name)

def send_initial_events(self):
self.send_event(self.delay, self, MessageSentToSelf())
self.send_event(0, self, MessageSentToSelf())

def handle_simulation_event(self, event):
self.simulation_state.set(self.a * self.time + self.b)
@@ -129,25 +129,31 @@ def test_abstract_checkpoint_simulation_object(self):
self.assertTrue(linear_prediction - self.a * self.update_period <= value <= linear_prediction)

def test_checkpoint_simulation_object(self):
'''
Run a simulation with CheckpointSimulationObject and another object.
Take checkpoints and test them.
'''
# Run a simulation with a CheckpointSimulationObject and another object.
# Take checkpoints and test them.

# prepare
checkpointing_obj = CheckpointSimulationObject('checkpointing_obj', self.checkpoint_period,
self.checkpoint_dir, self.state)
self.simulator.add_objects([self.updating_obj, checkpointing_obj])
self.simulator.initialize()

def endpoints(duration, period):
# provide the number of end points at 0, period, 2 * period, ... in [0, duration]
quotient = duration / period
return int(quotient) + 1

# run
run_time = 241
expected_num_events = int(run_time/self.update_period) + int(run_time/self.checkpoint_period)
run_time = 22
expected_num_events = endpoints(run_time, self.update_period) + \
endpoints(run_time, self.checkpoint_period)
num_events = self.simulator.run(run_time)

# check results
self.assertEqual(expected_num_events, num_events)
expected_checkpoint_times = [float(t) for t in
range(0, self.checkpoint_period * int(run_time/self.checkpoint_period) + 1, self.checkpoint_period)]
range(0, self.checkpoint_period * int(run_time/self.checkpoint_period) + 1,
self.checkpoint_period)]
checkpoints = Checkpoint.list_checkpoints(self.checkpoint_dir)
self.assertEqual(expected_checkpoint_times, checkpoints)
checkpoint = Checkpoint.get_checkpoint(self.checkpoint_dir)
@@ -234,7 +234,7 @@ def test_simulation_stop_condition(self):
simulator.initialize()
end_time = 10
# execute to time <= end_time, with 1st event at time = 1
self.assertEqual(simulator.simulate(end_time), end_time)
self.assertEqual(simulator.simulate(end_time), end_time + 1)

# TODO(Arthur): fix: this is misleading, because __stop_cond_end is treated like a time, but
# simulate() returns a count of events executed
@@ -244,12 +244,12 @@ def stop_cond_eg(time):
simulator = SimulationEngine(stop_condition=stop_cond_eg)
simulator.add_object(PeriodicSimulationObject('name', 1))
simulator.initialize()
self.assertEqual(simulator.simulate(end_time), __stop_cond_end)
self.assertEqual(simulator.simulate(end_time), __stop_cond_end + 1)

simulator = SimulationEngine()
simulator.add_object(PeriodicSimulationObject('name', 1))
simulator.initialize()
self.assertEqual(simulator.simulate(end_time, stop_condition=stop_cond_eg), __stop_cond_end)
self.assertEqual(simulator.simulate(end_time, stop_condition=stop_cond_eg), __stop_cond_end + 1)
# TODO(Arthur): test that the 'Terminate with stop condition satisfied' message is logged

with self.assertRaisesRegex(SimulatorError, 'stop_condition is not a function'):
@@ -264,7 +264,7 @@ def test_progress_bar(self):
with CaptureOutput(relay=True) as capturer:
try:
end_time = 10
self.assertEqual(simulator.simulate(end_time, progress=True), end_time)
self.assertEqual(simulator.simulate(end_time, progress=True), end_time + 1)
self.assertTrue("/{}".format(end_time) in capturer.get_text())
self.assertTrue("end_time".format(end_time) in capturer.get_text())
except ValueError as e:
@@ -0,0 +1,55 @@
"""
:Author: Arthur Goldberg, Arthur.Goldberg@mssm.edu
:Date: 2018-10-19
:Copyright: 2018, Karr Lab
:License: MIT
"""

import unittest
import os
import numpy as np

from de_sim.simulation_engine import SimulationEngine
from de_sim.template_sim_objs import TemplatePeriodicSimulationObject
from de_sim.errors import SimulatorError


class PeriodicSimulationObject(TemplatePeriodicSimulationObject):
def __init__(self, name, period):
self.times = []
super().__init__(name, period)

def handle_event(self):
"""Handle the periodic event"""
self.times.append(self.time)


class TestTemplatePeriodicSimulationObject(unittest.TestCase):

def test_TemplatePeriodicSimulationObject(self):

simulator = SimulationEngine()
end_time = 5
expected = []

# int period
period = 1
pso_1 = PeriodicSimulationObject('pso_1', period)
expected.append(np.linspace(0, end_time, end_time + 1))

# float period
period = .1
pso_2 = PeriodicSimulationObject('pso_2', period)
expected.append(np.linspace(0, end_time, end_time * 10 +1))

psos = [pso_1, pso_2]
simulator.add_objects(psos)
simulator.initialize()
simulator.simulate(end_time)

for pso, expect in zip(psos, expected):
self.assertEqual(pso.times, list(expect))

def test_exceptions(self):
with self.assertRaisesRegexp(SimulatorError, 'period must be positive'):
PeriodicSimulationObject('pso', -1)

0 comments on commit 7cf887b

Please sign in to comment.
You can’t perform that action at this time.