From aa6cbcb628b2d11930ed828fb095b60abcca032a Mon Sep 17 00:00:00 2001 From: Laurent Courty Date: Thu, 3 Sep 2026 08:32:32 -0600 Subject: [PATCH 1/5] Test expose hotstart record inconsistency --- tests/test_hotstart_integration.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/test_hotstart_integration.py b/tests/test_hotstart_integration.py index 087255b..10ce14b 100644 --- a/tests/test_hotstart_integration.py +++ b/tests/test_hotstart_integration.py @@ -74,7 +74,7 @@ def create_sim_config( ), output_map_names=helpers.make_output_map_names( "out_hotstart", - ["water_depth", "qx", "qy", "created_volume"], + ["water_depth", "qx", "qy", "created_volume", "mean_rainfall"], ), surface_flow_parameters=SurfaceFlowParameters(hmin=0.0001, dtmax=0.3, cfl=0.2), infiltration_model=InfiltrationModelType.GREEN_AMPT, @@ -351,6 +351,15 @@ def test_roundtrip_state_restoration_and_match( # Calling initialize() would overwrite restored state (old_domain_volume, accum arrays, etc.) run_simulation_to_end(sim_b, skip_initialize=True) + resumed_output = sim_b.report.raster_provider + assert isinstance(resumed_output, MemoryRasterOutputProvider) + _, mean_rainfall = resumed_output.output_maps_dict["mean_rainfall"][0] + np.testing.assert_allclose( + mean_rainfall, + domain_5by5.arr_rain * 3_600_000, + rtol=1e-5, + ) + # Step 4: Verify final results match uninterrupted reference # Use qe/qs (internal flow arrays) instead of qx/qy (output arrays computed on-the-fly) assert_final_state_matches(sim_b, uninterrupted_simulation) From e672b99c66cf2c14489f89a33268946b4a0b3ba0 Mon Sep 17 00:00:00 2001 From: Laurent Courty Date: Thu, 3 Sep 2026 11:02:33 -0600 Subject: [PATCH 2/5] hotstart test exposes wrong mean maps when record cadence changes --- tests/test_hotstart_integration.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_hotstart_integration.py b/tests/test_hotstart_integration.py index 10ce14b..1aa39dc 100644 --- a/tests/test_hotstart_integration.py +++ b/tests/test_hotstart_integration.py @@ -481,6 +481,12 @@ def test_resume_applies_new_record_step_cadence( ] assert resumed_record_times[: len(expected_record_times)] == expected_record_times + _, mean_rainfall = resumed_output.output_maps_dict["mean_rainfall"][0] + np.testing.assert_allclose( + mean_rainfall, + domain_5by5.arr_rain * 3_600_000, + rtol=1e-5, + ) assert_state_differs(sim_b, record_step_hotstart_run["simulation"]) From daebda6da15d2148eeb617e277abcfc3f53c22ed Mon Sep 17 00:00:00 2001 From: Laurent Courty Date: Thu, 3 Sep 2026 11:41:56 -0600 Subject: [PATCH 3/5] maintain overall record interval after hotstart when record step does not change --- src/itzi_core/simulation.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/itzi_core/simulation.py b/src/itzi_core/simulation.py index a5286c9..956d350 100644 --- a/src/itzi_core/simulation.py +++ b/src/itzi_core/simulation.py @@ -569,6 +569,9 @@ def reconcile_hotstart_resume(self, hotstart_config: SimulationConfig) -> Self: "record", min(self.end_time, self.sim_time + self.report.dt) ) self.report.last_step = copy.copy(self.sim_time) + else: + # The restored deadline is the next regular report after the last one. + self.report.last_step = self.schedule.deadline("record") - self.report.dt if self.hydrology_model.dt != timedelta(seconds=hotstart_config.dtinf): self.schedule.set_deadline( From 0ad3e91408b72ed4d7918c5611fc336a7e3c0ac0 Mon Sep 17 00:00:00 2001 From: Laurent Courty Date: Thu, 3 Sep 2026 12:17:24 -0600 Subject: [PATCH 4/5] hotstart: fix acucmulator with changing report cadence --- src/itzi_core/simulation.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/itzi_core/simulation.py b/src/itzi_core/simulation.py index 956d350..469ca15 100644 --- a/src/itzi_core/simulation.py +++ b/src/itzi_core/simulation.py @@ -565,10 +565,12 @@ def reconcile_hotstart_resume(self, hotstart_config: SimulationConfig) -> Self: self.schedule.set_deadline("drainage", self.end_time) if self.report.dt != hotstart_config.record_step: + # Accumulators still begin at the last archived report boundary, + # independently of the cadence selected for the resumed run. + self.report.last_step = self.schedule.deadline("record") - hotstart_config.record_step self.schedule.set_deadline( "record", min(self.end_time, self.sim_time + self.report.dt) ) - self.report.last_step = copy.copy(self.sim_time) else: # The restored deadline is the next regular report after the last one. self.report.last_step = self.schedule.deadline("record") - self.report.dt From db53b200f6b54457997b2d3f0ce39e052c9bc58b Mon Sep 17 00:00:00 2001 From: Laurent Courty Date: Thu, 3 Sep 2026 12:36:11 -0600 Subject: [PATCH 5/5] simplify record hotstart resume --- src/itzi_core/simulation.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/itzi_core/simulation.py b/src/itzi_core/simulation.py index 469ca15..583d096 100644 --- a/src/itzi_core/simulation.py +++ b/src/itzi_core/simulation.py @@ -564,16 +564,13 @@ def reconcile_hotstart_resume(self, hotstart_config: SimulationConfig) -> Self: if not self.drainage_model: self.schedule.set_deadline("drainage", self.end_time) + # Accumulators begin at the last archived report boundary, independently + # of the cadence selected for the resumed run. + self.report.last_step = self.schedule.deadline("record") - hotstart_config.record_step if self.report.dt != hotstart_config.record_step: - # Accumulators still begin at the last archived report boundary, - # independently of the cadence selected for the resumed run. - self.report.last_step = self.schedule.deadline("record") - hotstart_config.record_step self.schedule.set_deadline( "record", min(self.end_time, self.sim_time + self.report.dt) ) - else: - # The restored deadline is the next regular report after the last one. - self.report.last_step = self.schedule.deadline("record") - self.report.dt if self.hydrology_model.dt != timedelta(seconds=hotstart_config.dtinf): self.schedule.set_deadline(