Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion src/pyfmi/fmi_algorithm_drivers.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,11 +374,22 @@ def __init__(self,
solver_options = self.solver_options)
number_of_diagnostics_variables = len(_diagnostics_vars)

#See if there is an time event at start time
#Check for events at start time
if isinstance(self.model, FMUModelME1):
event_info = self.model.get_event_info()
if event_info.upcomingTimeEvent and event_info.nextEventTime == model.time:
self.model.event_update()
elif isinstance(self.model, (FMUModelME2, CoupledFMUModelME2, FMUModelME3)):
if not self.options['initialize']:
self.model.enter_event_mode()
self.model.event_update()
self.model.enter_continuous_time_mode()
else:
event_info = self.model.get_event_info()
if event_info.nextEventTimeDefined and abs(event_info.nextEventTime - model.time) <= 1e-14:
self.model.enter_event_mode()
self.model.event_update()
self.model.enter_continuous_time_mode()

if abs(start_time - model.time) > 1e-14:
logging_module.warning('The simulation start time (%f) and the current time in the model (%f) is different. Is the simulation start time correctly set?'%(start_time, model.time))
Expand Down
3 changes: 3 additions & 0 deletions src/pyfmi/test_util.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,9 @@ class Dummy_FMUModelME2(_ForTestingFMUModelME2):
def event_update(self, *args, **kwargs):
pass

def enter_event_mode(self, *args, **kwargs):
pass

def enter_continuous_time_mode(self, *args, **kwargs):
pass

Expand Down
39 changes: 38 additions & 1 deletion tests/test_fmi2.py
Original file line number Diff line number Diff line change
Expand Up @@ -1126,4 +1126,41 @@ def test_no_state_fmu_eval_failure_caught(fmu_path):
fmu = load_fmu(fmu_path)
expected_err = "The right-hand side function had repeated recoverable errors"
with pytest.raises(CVodeError, match = re.escape(expected_err)):
fmu.simulate()
fmu.simulate()

def test_consecutive_simulation_with_initialize_false_time_events():
"""Test initialize=False continuation with a time event at start time.

After set_fmu_state + advancing model.time to a time-event boundary,
the fix must process the pending event before resuming integration.
Regression test for FMI2/ME2 time-event processing on continuation.
"""
fmu = load_fmu(REFERENCE_FMU_FMI2_PATH / "Stair.fmu")

# Simulate to a point before the first time event (t=1.0)
res = fmu.simulate(0, 0.8, options={"ncp": 0})
assert fmu.get('counter')[0] == 1
assert fmu.get_event_info().nextEventTime == 1.0

# Save, restore, then advance model time to the event boundary
state = fmu.get_fmu_state()
fmu.set_fmu_state(state)
fmu.time = 1.0
assert fmu.time == 1.0
assert fmu.get_event_info().nextEventTimeDefined
assert fmu.get_event_info().nextEventTime == 1.0

# Continue with initialize=False - the fix must process the pending
# time event at t=1.0 before the solver integrates from t=1.0 onward.
# Use ExplicitEuler because CVode's built-in time-event detection masks
# the bug: CVode catches past-due events during its initial step, while
# ExplicitEuler skips them, revealing the omission.
fmu.simulate(1.001, 2.001, options={
"ncp": 0, "initialize": False, "solver": "ExplicitEuler"
})
assert fmu.get('counter')[0] == 3, (
"Expected counter=3 (start=1 + event at 1.0 + event at 2.0), "
"got %d. Time events at continuation start were missed."
% fmu.get('counter')[0]
)
fmu.free_fmu_state(state)
37 changes: 37 additions & 0 deletions tests/test_fmi3_sim.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,3 +353,40 @@ def test_dynamic_diagnostics_no_time_per_step_should_not_set_cpu_time(self):
res = model.simulate(options = opts)

assert "@Diagnostics.cpu_time" not in res.keys()

def test_consecutive_simulation_with_initialize_false_time_events(self):
"""Test initialize=False continuation with a time event at start time.

After set_fmu_state + advancing model.time to a time-event boundary,
the fix must process the pending event before resuming integration.
Regression test for FMI3 time-event processing on continuation.
"""
fmu = load_fmu(FMI3_REF_FMU_PATH / "Stair.fmu")

# Simulate to a point before the first time event (t=1.0)
res = fmu.simulate(0, 0.8, options={"ncp": 0})
assert fmu.get('counter')[0] == 1
assert fmu.get_event_info().nextEventTime == 1.0

# Save, restore, then advance model time to the event boundary
state = fmu.get_fmu_state()
fmu.set_fmu_state(state)
fmu.time = 1.0
assert fmu.time == 1.0
assert fmu.get_event_info().nextEventTimeDefined
assert fmu.get_event_info().nextEventTime == 1.0

# Continue with initialize=False - the fix must process the pending
# time event at t=1.0 before the solver integrates from t=1.0 onward.
# Use ExplicitEuler because CVode's built-in time-event detection masks
# the bug: CVode catches past-due events during its initial step, while
# ExplicitEuler skips them, revealing the omission.
fmu.simulate(1.001, 2.001, options={
"ncp": 0, "initialize": False, "solver": "ExplicitEuler"
})
assert fmu.get('counter')[0] == 3, (
"Expected counter=3 (start=1 + event at 1.0 + event at 2.0), "
"got %d. Time events at continuation start were missed."
% fmu.get('counter')[0]
)
fmu.free_fmu_state(state)
Loading