From 7e51d5a288c6e96810ede319c5790df02c9ccd36 Mon Sep 17 00:00:00 2001 From: Florian Pfaff <6773539+FlorianPfaff@users.noreply.github.com> Date: Wed, 15 Jul 2026 16:15:46 +0200 Subject: [PATCH 1/5] Validate MEM-RBPF control input dimensions --- src/pyrecest/filters/mem_rbpf_tracker.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/pyrecest/filters/mem_rbpf_tracker.py b/src/pyrecest/filters/mem_rbpf_tracker.py index fec88b01d..056ec5f9c 100644 --- a/src/pyrecest/filters/mem_rbpf_tracker.py +++ b/src/pyrecest/filters/mem_rbpf_tracker.py @@ -317,6 +317,13 @@ def predict_linear( shape_system_matrix=None, shape_sys_noise=None, ): + if inputs is not None: + inputs = array(inputs) + expected_shape = self.kinematic_state.shape + if inputs.shape != expected_shape: + raise ValueError( + f"inputs must have shape {expected_shape}, got {inputs.shape}" + ) if system_matrix is not None: self.system_matrix = array(system_matrix) self._validate_system_matrix(self.system_matrix) @@ -326,7 +333,7 @@ def predict_linear( ) self.kinematic_state = self.system_matrix @ self.kinematic_state if inputs is not None: - self.kinematic_state = self.kinematic_state + array(inputs) + self.kinematic_state = self.kinematic_state + inputs self.covariance = self._symmetrize( self.system_matrix @ self.covariance @ self.system_matrix.T + self.sys_noise ) From 14c7a13373a72aefd9487f884d23c88195237982 Mon Sep 17 00:00:00 2001 From: Florian Pfaff <6773539+FlorianPfaff@users.noreply.github.com> Date: Wed, 15 Jul 2026 16:16:02 +0200 Subject: [PATCH 2/5] Add MEM-RBPF control-input regression --- .../test_mem_rbpf_control_input_validation.py | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 tests/filters/test_mem_rbpf_control_input_validation.py diff --git a/tests/filters/test_mem_rbpf_control_input_validation.py b/tests/filters/test_mem_rbpf_control_input_validation.py new file mode 100644 index 000000000..15148abb3 --- /dev/null +++ b/tests/filters/test_mem_rbpf_control_input_validation.py @@ -0,0 +1,56 @@ +import unittest + +import numpy.testing as npt +import pyrecest.backend +from pyrecest.backend import array, diag, eye +from pyrecest.filters.mem_rbpf_tracker import MEMRBPFTracker + + +@unittest.skipIf( + pyrecest.backend.__backend_name__ != "numpy", + reason="MEM-RBPF tracker tests currently use numpy.testing assertions", +) +class MEMRBPFControlInputValidationTest(unittest.TestCase): + @staticmethod + def _tracker(): + return MEMRBPFTracker( + kinematic_state=array([0.0, 0.0, 1.0, -0.5]), + covariance=eye(4), + shape_state=array([0.2, 2.0, 1.0]), + shape_covariance=diag(array([0.05, 0.1, 0.1])), + meas_noise_cov=0.05 * eye(2), + sys_noise=0.01 * eye(4), + shape_sys_noise=diag(array([0.01, 0.01, 0.01])), + n_particles=8, + resampling_threshold=0, + rng=0, + ) + + def test_rejects_broadcastable_wrong_sized_input_without_mutating_state(self): + tracker = self._tracker() + prior_state = tracker.kinematic_state.copy() + prior_covariance = tracker.covariance.copy() + prior_system_matrix = tracker.system_matrix.copy() + prior_sys_noise = tracker.sys_noise.copy() + prior_theta = tracker.theta.copy() + prior_axis = tracker.axis.copy() + prior_axis_covariances = tracker.axis_covariances.copy() + + with self.assertRaisesRegex(ValueError, r"inputs must have shape \(4,\)"): + tracker.predict_linear( + system_matrix=2.0 * eye(4), + sys_noise=3.0 * eye(4), + inputs=array([2.0]), + ) + + npt.assert_array_equal(tracker.kinematic_state, prior_state) + npt.assert_array_equal(tracker.covariance, prior_covariance) + npt.assert_array_equal(tracker.system_matrix, prior_system_matrix) + npt.assert_array_equal(tracker.sys_noise, prior_sys_noise) + npt.assert_array_equal(tracker.theta, prior_theta) + npt.assert_array_equal(tracker.axis, prior_axis) + npt.assert_array_equal(tracker.axis_covariances, prior_axis_covariances) + + +if __name__ == "__main__": + unittest.main() From 199e34f2379cb0ebe19c02be3539e15a30b82217 Mon Sep 17 00:00:00 2001 From: Florian Pfaff <6773539+FlorianPfaff@users.noreply.github.com> Date: Wed, 15 Jul 2026 17:32:28 +0200 Subject: [PATCH 3/5] Carry uniform overflow CI fix into PR branch --- src/pyrecest/_backend/_shared_numpy/random.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pyrecest/_backend/_shared_numpy/random.py b/src/pyrecest/_backend/_shared_numpy/random.py index dc5569f25..02db1e8cc 100644 --- a/src/pyrecest/_backend/_shared_numpy/random.py +++ b/src/pyrecest/_backend/_shared_numpy/random.py @@ -90,7 +90,10 @@ def _validate_uniform_bounds(low, high): def _uniform(low=0.0, high=1.0, size=None): _validate_uniform_bounds(low, high) - return _np.random.uniform(low, high, _normalize_size(size)) + try: + return _np.random.uniform(low, high, _normalize_size(size)) + except OverflowError as exc: + raise OverflowError("high - low range exceeds valid bounds") from exc uniform = _modify_func_default_dtype( From fe073b4915f5b783190c044385d8140121eaa8b9 Mon Sep 17 00:00:00 2001 From: Florian Pfaff <6773539+FlorianPfaff@users.noreply.github.com> Date: Wed, 15 Jul 2026 20:09:33 +0200 Subject: [PATCH 4/5] Trigger CI against updated main --- .github/ci-refresh/4437.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/ci-refresh/4437.txt diff --git a/.github/ci-refresh/4437.txt b/.github/ci-refresh/4437.txt new file mode 100644 index 000000000..4ee0bfc89 --- /dev/null +++ b/.github/ci-refresh/4437.txt @@ -0,0 +1 @@ +Temporary CI refresh marker for PR #4437. From 735bd972b16d280af16e10ff2b57f040cd36d6e8 Mon Sep 17 00:00:00 2001 From: Florian Pfaff <6773539+FlorianPfaff@users.noreply.github.com> Date: Wed, 15 Jul 2026 20:09:43 +0200 Subject: [PATCH 5/5] Remove temporary CI refresh marker --- .github/ci-refresh/4437.txt | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .github/ci-refresh/4437.txt diff --git a/.github/ci-refresh/4437.txt b/.github/ci-refresh/4437.txt deleted file mode 100644 index 4ee0bfc89..000000000 --- a/.github/ci-refresh/4437.txt +++ /dev/null @@ -1 +0,0 @@ -Temporary CI refresh marker for PR #4437.