From 389cbf1d51c10d659e2ffbac9ed4a8202ea06c01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yasunori=20Morishima=EF=BC=88=E7=9B=9B=E5=B3=B6=E5=BA=B7?= =?UTF-8?q?=E5=BE=B3=EF=BC=89?= Date: Thu, 19 Mar 2026 11:55:41 +0000 Subject: [PATCH 1/4] fix: handle 1D interpolation result in resample for single-state ODEs When an ODESystem has only a single state variable, scipy interpolation returns a 1D array (n,) instead of 2D (n, 1). This causes a broadcast error when assigning to the pre-allocated 2D xs array. Fix by checking ndim and adding a column axis when needed. Fixes #70 --- src/condor/contrib.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/condor/contrib.py b/src/condor/contrib.py index bfabc9e7..9acfe99c 100644 --- a/src/condor/contrib.py +++ b/src/condor/contrib.py @@ -722,7 +722,10 @@ def resample(self, dt, include_output=True, include_events=True, max_deg=3): # TODO figure out how to get root info ts_to_call = new_self.t[idx0:idx1] - xs[idx0:idx1] = x_interp_segment(ts_to_call) + interp_result = x_interp_segment(ts_to_call) + if interp_result.ndim == 1: + interp_result = interp_result[:, np.newaxis] + xs[idx0:idx1] = interp_result if include_output: for idx, t, x in zip(range(idx0, idx1), ts_to_call, xs[idx0:idx1]): ys[idx, None] = dynamic_output(p, t, x).T From 5a9745e915dbb61b222689026401752e1a240dc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yasunori=20Morishima=EF=BC=88=E7=9B=9B=E5=B3=B6=E5=BA=B7?= =?UTF-8?q?=E5=BE=B3=EF=BC=89?= Date: Thu, 19 Mar 2026 12:28:21 +0000 Subject: [PATCH 2/4] chore: trigger CodeRabbit review From 8f706bf5be6d2937efa91ac3c7040a43b079160f Mon Sep 17 00:00:00 2001 From: yasumorishima Date: Thu, 19 Mar 2026 23:56:33 +0900 Subject: [PATCH 3/4] Add tests for single-state ODE resample (issue #70) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 6 tests covering: - Basic single-state resample - Resampled values match analytical solution (exponential decay) - include_output=False path - dynamic_output with single-state ODE - Different dt values - Multi-state ODE regression (harmonic oscillator) Also discovered that include_events=True triggers a separate IndexError (issue #71, t_size calculation off-by-2) — tests use include_events=False to isolate the ndim fix from that pre-existing bug. Co-Authored-By: Claude Opus 4.6 (1M context) --- tests/test_trajectory_analysis.py | 101 ++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/tests/test_trajectory_analysis.py b/tests/test_trajectory_analysis.py index 762ff056..ccf2a93f 100644 --- a/tests/test_trajectory_analysis.py +++ b/tests/test_trajectory_analysis.py @@ -380,3 +380,104 @@ class Sim(odesys.TrajectoryAnalysis): tf = 10 Sim(wn=10, u_hold=0.8) + + +class TestResampleSingleState: + """Tests for TrajectoryAnalysis.resample with single-state ODEs (issue #70).""" + + def test_resample_single_state(self): + """Exact reproduction of issue #70: single-state ODE resample crashes.""" + class ODE(co.ODESystem): + a = parameter() + x = state() + dot[x] = -a * x + + class Traj(ODE.TrajectoryAnalysis): + tf = 10 + initial[x] = 1 + + sim = Traj(a=0.5) + resampled = sim.resample(1.0, include_events=False) + assert resampled.t.size > 0 + + def test_resample_single_state_values(self): + """Verify resampled values follow exponential decay.""" + class ODE(co.ODESystem): + a = parameter() + x = state() + dot[x] = -a * x + + class Traj(ODE.TrajectoryAnalysis): + tf = 10 + initial[x] = 1 + + sim = Traj(a=0.5) + resampled = sim.resample(1.0, include_events=False) + expected = np.exp(-0.5 * resampled.t) + np.testing.assert_allclose(resampled.x, expected, rtol=1e-4) + + def test_resample_single_state_no_output(self): + class ODE(co.ODESystem): + a = parameter() + x = state() + dot[x] = -a * x + + class Traj(ODE.TrajectoryAnalysis): + tf = 10 + initial[x] = 1 + + sim = Traj(a=0.5) + resampled = sim.resample(1.0, include_events=False, include_output=False) + assert resampled.t.size > 0 + + def test_resample_single_state_with_dynamic_output(self): + """Single-state ODE with dynamic_output — tests .T reshape path.""" + class ODE(co.ODESystem): + a = parameter() + x = state() + dynamic_output.velocity = -a * x + dot[x] = -a * x + + class Traj(ODE.TrajectoryAnalysis): + tf = 10 + initial[x] = 1 + vel = dynamic_output.velocity + + sim = Traj(a=0.5) + resampled = sim.resample(1.0, include_events=False, include_output=True) + assert resampled.t.size > 0 + + def test_resample_single_state_small_dt(self): + """Smaller dt produces more points.""" + class ODE(co.ODESystem): + a = parameter() + x = state() + dot[x] = -a * x + + class Traj(ODE.TrajectoryAnalysis): + tf = 10 + initial[x] = 1 + + sim = Traj(a=0.5) + r1 = sim.resample(2.0, include_events=False) + r2 = sim.resample(0.5, include_events=False) + assert r2.t.size > r1.t.size + + def test_resample_multi_state_still_works(self): + """Ensure multi-state ODEs are not broken by the ndim fix.""" + class ODE(co.ODESystem): + x = state() + v = state() + dot[x] = v + dot[v] = -x + + class Traj(ODE.TrajectoryAnalysis): + tf = 10 + initial[x] = 1 + initial[v] = 0 + + sim = Traj() + resampled = sim.resample(0.5, include_events=False) + assert resampled.t.size > 0 + # Simple harmonic oscillator: x(t) = cos(t) + np.testing.assert_allclose(resampled.x, np.cos(resampled.t), rtol=1e-3) From 28b4a63c93e8ee36db95abae08b4f735ad668a04 Mon Sep 17 00:00:00 2001 From: yasumorishima Date: Fri, 20 Mar 2026 00:05:19 +0900 Subject: [PATCH 4/4] Add value assertion for dynamic_output in resample test Address CodeRabbit review: assert resampled.velocity values match analytical solution (-0.5 * exp(-0.5 * t)), not just successful execution. Co-Authored-By: Claude Opus 4.6 (1M context) --- tests/test_trajectory_analysis.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_trajectory_analysis.py b/tests/test_trajectory_analysis.py index ccf2a93f..ebb9e3ec 100644 --- a/tests/test_trajectory_analysis.py +++ b/tests/test_trajectory_analysis.py @@ -446,6 +446,9 @@ class Traj(ODE.TrajectoryAnalysis): sim = Traj(a=0.5) resampled = sim.resample(1.0, include_events=False, include_output=True) assert resampled.t.size > 0 + # velocity = -a * x = -0.5 * exp(-0.5 * t) + expected_vel = -0.5 * np.exp(-0.5 * resampled.t) + np.testing.assert_allclose(resampled.velocity, expected_vel, rtol=1e-4) def test_resample_single_state_small_dt(self): """Smaller dt produces more points."""