From 9f40bd38ebed02b6c5bbcbf279e8efa828eefe88 Mon Sep 17 00:00:00 2001 From: Florian Pfaff <6773539+FlorianPfaff@users.noreply.github.com> Date: Mon, 31 Aug 2026 00:35:17 +0800 Subject: [PATCH 1/2] Fix hypertoroidal MH defaults and non-finite angle validation --- .../abstract_hypertoroidal_distribution.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/pyrecest/distributions/hypertorus/abstract_hypertoroidal_distribution.py b/src/pyrecest/distributions/hypertorus/abstract_hypertoroidal_distribution.py index 82431c09d..e322bb8f3 100644 --- a/src/pyrecest/distributions/hypertorus/abstract_hypertoroidal_distribution.py +++ b/src/pyrecest/distributions/hypertorus/abstract_hypertoroidal_distribution.py @@ -21,7 +21,7 @@ cos, int32, int64, - isnan, + isfinite, linspace, log, meshgrid, @@ -171,8 +171,10 @@ def angular_error(alpha, beta): Returns: float or numpy array: The angular error(s) in radians. """ - if bool(isnan(alpha).any()) or bool(isnan(beta).any()): - raise ValueError("Angles must not contain NaN values.") + if bool(backend_any(~isfinite(alpha))) or bool(backend_any(~isfinite(beta))): + raise ValueError( + "Angles must contain only finite values; NaN and infinite values are invalid." + ) # Ensure the angles are between 0 and 2*pi alpha = mod(alpha, 2.0 * pi) beta = mod(beta, 2.0 * pi) @@ -312,7 +314,12 @@ def proposal_np(x): proposal = proposal_np if start_point is None: - start_point = self.mean_direction() + try: + start_point = self.mean_direction() + except ValueError: + # Uniform and other symmetric hypertoroidal distributions can have + # no unique mean direction even though MH sampling is well-defined. + start_point = zeros(self.dim) # pylint: disable=duplicate-code s = AbstractManifoldSpecificDistribution.sample_metropolis_hastings( From 35fa3b6f21a588e890f240b14b6ca1cdff6dc17b Mon Sep 17 00:00:00 2001 From: Florian Pfaff <6773539+FlorianPfaff@users.noreply.github.com> Date: Mon, 31 Aug 2026 00:35:33 +0800 Subject: [PATCH 2/2] Add hypertoroidal sampling and angle regressions --- ...toroidal_sampling_and_angle_regressions.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 tests/distributions/test_hypertoroidal_sampling_and_angle_regressions.py diff --git a/tests/distributions/test_hypertoroidal_sampling_and_angle_regressions.py b/tests/distributions/test_hypertoroidal_sampling_and_angle_regressions.py new file mode 100644 index 000000000..23b781fed --- /dev/null +++ b/tests/distributions/test_hypertoroidal_sampling_and_angle_regressions.py @@ -0,0 +1,28 @@ +import pytest +from pyrecest.backend import array +from pyrecest.distributions import AbstractHypertoroidalDistribution +from pyrecest.distributions.hypertorus.hypertoroidal_uniform_distribution import ( + HypertoroidalUniformDistribution, +) + + +def test_uniform_metropolis_hastings_does_not_require_unique_mean(): + dist = HypertoroidalUniformDistribution(2) + + samples = dist.sample_metropolis_hastings(4, burn_in=0, skipping=1) + + assert samples.shape == (4, 2) + + +@pytest.mark.parametrize( + ("alpha", "beta"), + [ + (float("inf"), 0.0), + (float("-inf"), 0.0), + (0.0, float("inf")), + (0.0, float("-inf")), + ], +) +def test_angular_error_rejects_infinite_inputs(alpha, beta): + with pytest.raises(ValueError, match="finite"): + AbstractHypertoroidalDistribution.angular_error(array(alpha), array(beta))