Skip to content
Merged
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
3 changes: 3 additions & 0 deletions src/pyrecest/filters/hyperspherical_ukf.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,10 +201,13 @@ def predict_nonlinear_arbitrary_noise( # pylint: disable=too-many-locals
"noise_samples and noise_weights must contain the same number "
"of samples."
)
if noise_weights.shape[0] == 0:
raise ValueError("noise_weights must contain at least one sample.")
if not all(isfinite(noise_weights)):
raise ValueError("noise_weights must be finite.")
if not all(noise_weights > 0):
raise ValueError("noise_weights must be strictly positive.")
noise_weights = noise_weights / noise_weights.max()
noise_weights = noise_weights / noise_weights.sum()

mu = reshape(asarray(self._filter_state.mu, dtype=float64), (-1,))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
from pyrecest.filters.hyperspherical_ukf import HypersphericalUKF


@unittest.skipIf(
pyrecest.backend.__backend_name__ in ("pytorch", "jax"),
reason="Arbitrary-noise prediction is not supported on this backend",
)
class HypersphericalUKFArbitraryNoiseNormalizationTest(unittest.TestCase):
@unittest.skipIf(
pyrecest.backend.__backend_name__ in ("pytorch", "jax"),
reason="Arbitrary-noise prediction is not supported on this backend",
)
def test_radial_model_scale_does_not_create_spurious_covariance(self):
ukf = HypersphericalUKF(dim=2, alpha=1.0)
noise_samples = np.array([[0.0, 1.0]])
Expand All @@ -36,3 +36,37 @@ def scaled_same_direction(_x, v):
np.zeros((2, 2)),
atol=1e-12,
)

def test_maximum_finite_weights_do_not_overflow(self):
ukf = HypersphericalUKF(dim=2, alpha=1.0)
noise_samples = np.array([[0.0, 1.0]])
noise_weights = np.full(2, np.finfo(float).max)

def fixed_direction(_x, _v):
return array([1.0, 0.0])

with np.errstate(over="raise", invalid="raise", divide="raise"):
ukf.predict_nonlinear_arbitrary_noise(
fixed_direction, noise_samples, noise_weights
)

npt.assert_allclose(
np.asarray(ukf.filter_state.mu, dtype=float),
np.array([1.0, 0.0]),
atol=1e-12,
)
npt.assert_allclose(
np.asarray(ukf.filter_state.C, dtype=float),
np.zeros((2, 2)),
atol=1e-12,
)

def test_rejects_empty_noise_support(self):
ukf = HypersphericalUKF(dim=2, alpha=1.0)

with self.assertRaisesRegex(ValueError, "at least one sample"):
ukf.predict_nonlinear_arbitrary_noise(
lambda x, _v: x,
np.empty((1, 0)),
np.empty((0,)),
)
Loading