From 05816b6d7c17fbd9a2efd3b6115c10c45956ea0e Mon Sep 17 00:00:00 2001 From: Florian Pfaff <6773539+FlorianPfaff@users.noreply.github.com> Date: Sat, 8 Aug 2026 00:44:02 +0800 Subject: [PATCH 1/3] Fix hyperhemisphere component state assignment --- .../hyperhemisphere_cart_prod_particle_filter.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/pyrecest/filters/hyperhemisphere_cart_prod_particle_filter.py b/src/pyrecest/filters/hyperhemisphere_cart_prod_particle_filter.py index 99e45616c..b5da5ef86 100644 --- a/src/pyrecest/filters/hyperhemisphere_cart_prod_particle_filter.py +++ b/src/pyrecest/filters/hyperhemisphere_cart_prod_particle_filter.py @@ -81,13 +81,6 @@ def set_state(self, new_state): ) self.filter_state = new_state return - if not isinstance(new_state, HyperhemisphereCartProdDiracDistribution): - new_state = HyperhemisphereCartProdDiracDistribution( - new_state.sample(self.filter_state.d.shape[0]), - w=ones(self.filter_state.d.shape[0]) / self.filter_state.d.shape[0], - dim_hemisphere=self.filter_state.dim_hemisphere, - n_hemispheres=self.filter_state.n_hemispheres, - ) self.filter_state = new_state @beartype @@ -163,5 +156,8 @@ def filter_state(self, new_state): if isinstance(new_state, AbstractHypersphericalDistribution): samples[samples[:, -1] < 0] = -samples[samples[:, -1] < 0] self._filter_state.d = samples.reshape(self.filter_state.d.shape) + self._filter_state.w = ( + ones(self._filter_state.w.shape[0]) / self._filter_state.w.shape[0] + ) else: AbstractParticleFilter.filter_state.fset(self, new_state) From 56d6a8ba7e102d0329caa8b68751f7023dbeb9f9 Mon Sep 17 00:00:00 2001 From: Florian Pfaff <6773539+FlorianPfaff@users.noreply.github.com> Date: Sat, 8 Aug 2026 00:44:25 +0800 Subject: [PATCH 2/3] Test hyperhemisphere component state assignment --- ...re_cart_prod_component_state_assignment.py | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 tests/filters/test_hyperhemisphere_cart_prod_component_state_assignment.py diff --git a/tests/filters/test_hyperhemisphere_cart_prod_component_state_assignment.py b/tests/filters/test_hyperhemisphere_cart_prod_component_state_assignment.py new file mode 100644 index 000000000..f7766189a --- /dev/null +++ b/tests/filters/test_hyperhemisphere_cart_prod_component_state_assignment.py @@ -0,0 +1,80 @@ +import unittest + +import numpy as np + +import pyrecest.backend # pylint: disable=no-name-in-module,no-member +from pyrecest.backend import array # pylint: disable=no-name-in-module,no-member +from pyrecest.distributions.cart_prod.hyperhemisphere_cart_prod_dirac_distribution import ( + HyperhemisphereCartProdDiracDistribution, +) +from pyrecest.distributions.hypersphere_subset.hyperhemispherical_watson_distribution import ( + HyperhemisphericalWatsonDistribution, +) +from pyrecest.filters.hyperhemisphere_cart_prod_particle_filter import ( + HyperhemisphereCartProdParticleFilter, +) + + +@unittest.skipIf( + pyrecest.backend.__backend_name__ # pylint: disable=no-name-in-module,no-member + in ("jax", "pytorch"), + reason="Backend not supported", +) +class HyperhemisphereCartProdComponentStateAssignmentTest(unittest.TestCase): + @staticmethod + def _filter_with_nonuniform_weights(): + particle_filter = HyperhemisphereCartProdParticleFilter(4, 2, 2) + particles = array( + [ + [0.0, 0.0, 1.0, 0.0, 0.0, 1.0], + [1.0, 0.0, 0.0, 1.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 1.0, 1.0, 0.0, 0.0], + ] + ) + state = HyperhemisphereCartProdDiracDistribution( + particles, + w=array([0.7, 0.1, 0.1, 0.1]), + dim_hemisphere=2, + n_hemispheres=2, + ) + particle_filter.set_state(state) + return particle_filter + + @staticmethod + def _component_distribution(): + return HyperhemisphericalWatsonDistribution( + array([0.0, 0.0, 1.0]), 2.0 + ) + + def test_set_state_expands_component_distribution_across_product(self): + particle_filter = self._filter_with_nonuniform_weights() + + particle_filter.set_state(self._component_distribution()) + + self.assertEqual(particle_filter.filter_state.d.shape, (4, 6)) + self.assertEqual( + particle_filter.filter_state.as_component_array().shape, (4, 2, 3) + ) + np.testing.assert_allclose( + particle_filter.filter_state.w, np.full(4, 0.25) + ) + self.assertTrue( + np.all( + np.asarray(particle_filter.filter_state.as_component_array())[..., -1] + >= 0.0 + ) + ) + + def test_filter_state_distribution_assignment_resets_weights(self): + particle_filter = self._filter_with_nonuniform_weights() + + particle_filter.filter_state = self._component_distribution() + + np.testing.assert_allclose( + particle_filter.filter_state.w, np.full(4, 0.25) + ) + + +if __name__ == "__main__": + unittest.main() From 25205a7ec901a99a6a46d9f74b5243851db96cbc Mon Sep 17 00:00:00 2001 From: Florian Pfaff <6773539+FlorianPfaff@users.noreply.github.com> Date: Sat, 8 Aug 2026 00:46:13 +0800 Subject: [PATCH 3/3] Preserve backend weight dtype on state assignment --- .../filters/hyperhemisphere_cart_prod_particle_filter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyrecest/filters/hyperhemisphere_cart_prod_particle_filter.py b/src/pyrecest/filters/hyperhemisphere_cart_prod_particle_filter.py index b5da5ef86..ed70d7d22 100644 --- a/src/pyrecest/filters/hyperhemisphere_cart_prod_particle_filter.py +++ b/src/pyrecest/filters/hyperhemisphere_cart_prod_particle_filter.py @@ -4,7 +4,7 @@ from beartype import beartype # pylint: disable=no-name-in-module,no-member -from pyrecest.backend import empty, ones +from pyrecest.backend import empty, ones, ones_like from pyrecest.distributions import AbstractHypersphericalDistribution from pyrecest.distributions.cart_prod.hyperhemisphere_cart_prod_dirac_distribution import ( HyperhemisphereCartProdDiracDistribution, @@ -157,7 +157,7 @@ def filter_state(self, new_state): samples[samples[:, -1] < 0] = -samples[samples[:, -1] < 0] self._filter_state.d = samples.reshape(self.filter_state.d.shape) self._filter_state.w = ( - ones(self._filter_state.w.shape[0]) / self._filter_state.w.shape[0] + ones_like(self._filter_state.w) / self._filter_state.w.shape[0] ) else: AbstractParticleFilter.filter_state.fset(self, new_state)