From 21e2b7bc6c39e7399ed7f17c2faf49f34ed85e60 Mon Sep 17 00:00:00 2001 From: Florian Pfaff <6773539+FlorianPfaff@users.noreply.github.com> Date: Wed, 19 Aug 2026 18:03:09 +0800 Subject: [PATCH 1/6] Preserve custom linear subclasses in conversion --- .../nonperiodic/custom_linear_distribution.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/pyrecest/distributions/nonperiodic/custom_linear_distribution.py b/src/pyrecest/distributions/nonperiodic/custom_linear_distribution.py index aac90a8919..be8e888f36 100644 --- a/src/pyrecest/distributions/nonperiodic/custom_linear_distribution.py +++ b/src/pyrecest/distributions/nonperiodic/custom_linear_distribution.py @@ -83,8 +83,8 @@ def pdf(self, xs): p = reshape(p, xs.shape[:-1]) return p - @staticmethod - def from_distribution(distribution): + @classmethod + def from_distribution(cls, distribution): """ Creates a CustomLinearDistribution from some other distribution @@ -96,8 +96,7 @@ def from_distribution(distribution): chd (CustomLinearDistribution) CustomLinearDistribution with identical pdf """ - chd = CustomLinearDistribution(distribution.pdf, distribution.dim) - return chd + return cls(distribution.pdf, distribution.dim) def integrate(self, left=None, right=None): return AbstractLinearDistribution.integrate(self, left, right) From cc9bc96fa2399cd78633b2e966dd25405865b3c2 Mon Sep 17 00:00:00 2001 From: Florian Pfaff <6773539+FlorianPfaff@users.noreply.github.com> Date: Wed, 19 Aug 2026 18:03:20 +0800 Subject: [PATCH 2/6] Preserve hypercylindrical subclasses in conversion --- .../cart_prod/custom_hypercylindrical_distribution.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/pyrecest/distributions/cart_prod/custom_hypercylindrical_distribution.py b/src/pyrecest/distributions/cart_prod/custom_hypercylindrical_distribution.py index ac75c0860e..8df5ed0d2e 100644 --- a/src/pyrecest/distributions/cart_prod/custom_hypercylindrical_distribution.py +++ b/src/pyrecest/distributions/cart_prod/custom_hypercylindrical_distribution.py @@ -28,8 +28,8 @@ def __init__(self, f, bound_dim, lin_dim): self, f, bound_dim, lin_dim ) - @staticmethod - def from_distribution(distribution): + @classmethod + def from_distribution(cls, distribution): """ Create a CustomHypercylindricalDistribution from another AbstractHypercylindricalDistribution. @@ -41,10 +41,7 @@ def from_distribution(distribution): chhd (CustomHypercylindricalDistribution) The created CustomHypercylindricalDistribution """ - chhd = CustomHypercylindricalDistribution( - distribution.pdf, distribution.bound_dim, distribution.lin_dim - ) - return chhd + return cls(distribution.pdf, distribution.bound_dim, distribution.lin_dim) def integrate(self, integration_boundaries=None): # Call the integrate method from the superclass From 6f05663b91a64941b86b2d1b3d0fca7bac6aaca3 Mon Sep 17 00:00:00 2001 From: Florian Pfaff <6773539+FlorianPfaff@users.noreply.github.com> Date: Wed, 19 Aug 2026 18:03:30 +0800 Subject: [PATCH 3/6] Preserve hyperspherical custom subclasses --- .../custom_hyperspherical_distribution.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/pyrecest/distributions/hypersphere_subset/custom_hyperspherical_distribution.py b/src/pyrecest/distributions/hypersphere_subset/custom_hyperspherical_distribution.py index 7bb8c26608..5fc8162ac8 100644 --- a/src/pyrecest/distributions/hypersphere_subset/custom_hyperspherical_distribution.py +++ b/src/pyrecest/distributions/hypersphere_subset/custom_hyperspherical_distribution.py @@ -9,13 +9,12 @@ def __init__(self, f, dim, scale_by=1): AbstractCustomDistribution.__init__(self, f, scale_by) AbstractHypersphericalDistribution.__init__(self, dim) - @staticmethod - def from_distribution(distribution): + @classmethod + def from_distribution(cls, distribution): if not isinstance(distribution, AbstractHypersphericalDistribution): raise ValueError("Input variable distribution is of the wrong class.") - chd = CustomHypersphericalDistribution(distribution.pdf, distribution.dim) - return chd + return cls(distribution.pdf, distribution.dim) def integrate(self, integration_boundaries=None): return AbstractHypersphericalDistribution.integrate( From 98e58c88722945a3f7d1cd91dd4d1f724024ea8b Mon Sep 17 00:00:00 2001 From: Florian Pfaff <6773539+FlorianPfaff@users.noreply.github.com> Date: Wed, 19 Aug 2026 18:03:41 +0800 Subject: [PATCH 4/6] Preserve hyperhemispherical custom subclasses --- .../custom_hyperhemispherical_distribution.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/pyrecest/distributions/hypersphere_subset/custom_hyperhemispherical_distribution.py b/src/pyrecest/distributions/hypersphere_subset/custom_hyperhemispherical_distribution.py index 3e1fdcf425..b73d1d9729 100644 --- a/src/pyrecest/distributions/hypersphere_subset/custom_hyperhemispherical_distribution.py +++ b/src/pyrecest/distributions/hypersphere_subset/custom_hyperhemispherical_distribution.py @@ -52,8 +52,8 @@ def integrate(self, integration_boundaries=None): self, integration_boundaries ) - @staticmethod - def from_distribution(distribution: "AbstractHypersphericalDistribution"): + @classmethod + def from_distribution(cls, distribution: "AbstractHypersphericalDistribution"): """ Create a CustomHyperhemisphericalDistribution from another distribution. @@ -62,21 +62,15 @@ def from_distribution(distribution: "AbstractHypersphericalDistribution"): :raises ValueError: if the type of dist is not supported. """ if isinstance(distribution, AbstractHyperhemisphericalDistribution): - return CustomHyperhemisphericalDistribution( - distribution.pdf, distribution.dim - ) + return cls(distribution.pdf, distribution.dim) if isinstance(distribution, BinghamDistribution): - chhd = CustomHyperhemisphericalDistribution( - distribution.pdf, distribution.dim - ) + chhd = cls(distribution.pdf, distribution.dim) chhd.scale_by = 2 return chhd if isinstance(distribution, AbstractHypersphericalDistribution): - chhd = CustomHyperhemisphericalDistribution( - distribution.pdf, distribution.dim - ) + chhd = cls(distribution.pdf, distribution.dim) norm_const_inv = chhd.integrate() chhd.scale_by = 1 / norm_const_inv return chhd From e4100a8702999e485f4023e3ac85ee94a62092c1 Mon Sep 17 00:00:00 2001 From: Florian Pfaff <6773539+FlorianPfaff@users.noreply.github.com> Date: Wed, 19 Aug 2026 18:03:53 +0800 Subject: [PATCH 5/6] Preserve hemispherical custom subclasses --- .../custom_hemispherical_distribution.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pyrecest/distributions/hypersphere_subset/custom_hemispherical_distribution.py b/src/pyrecest/distributions/hypersphere_subset/custom_hemispherical_distribution.py index fb691191e0..1c950edad4 100644 --- a/src/pyrecest/distributions/hypersphere_subset/custom_hemispherical_distribution.py +++ b/src/pyrecest/distributions/hypersphere_subset/custom_hemispherical_distribution.py @@ -17,15 +17,15 @@ def __init__(self, f: Callable): AbstractHemisphericalDistribution.__init__(self) CustomHyperhemisphericalDistribution.__init__(self, f, 2) - @staticmethod - def from_distribution(distribution: "AbstractHypersphericalDistribution"): + @classmethod + def from_distribution(cls, distribution: "AbstractHypersphericalDistribution"): if distribution.dim != 2: raise ValueError("Dimension of the distribution should be 2.") if isinstance(distribution, AbstractHyperhemisphericalDistribution): - return CustomHemisphericalDistribution(distribution.pdf) + return cls(distribution.pdf) if isinstance(distribution, BinghamDistribution): - chsd = CustomHemisphericalDistribution(distribution.pdf) + chsd = cls(distribution.pdf) chsd.scale_by = 2 return chsd if isinstance(distribution, AbstractHypersphericalDistribution): @@ -38,7 +38,7 @@ def from_distribution(distribution: "AbstractHypersphericalDistribution"): distribution.pdf, distribution.dim ) norm_const_inv = chhd_unnorm.integrate() - chsd = CustomHemisphericalDistribution(distribution.pdf) + chsd = cls(distribution.pdf) chsd.scale_by = 1 / norm_const_inv return chsd From bb8150394e3ebffd1a6aea6585b0d789addc40d6 Mon Sep 17 00:00:00 2001 From: Florian Pfaff <6773539+FlorianPfaff@users.noreply.github.com> Date: Wed, 19 Aug 2026 18:04:05 +0800 Subject: [PATCH 6/6] Add custom factory subclass regression coverage --- ...st_custom_factory_subclass_preservation.py | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 tests/distributions/test_custom_factory_subclass_preservation.py diff --git a/tests/distributions/test_custom_factory_subclass_preservation.py b/tests/distributions/test_custom_factory_subclass_preservation.py new file mode 100644 index 0000000000..f42981a9d7 --- /dev/null +++ b/tests/distributions/test_custom_factory_subclass_preservation.py @@ -0,0 +1,85 @@ +import unittest + +from pyrecest.distributions.cart_prod.custom_hypercylindrical_distribution import ( + CustomHypercylindricalDistribution, +) +from pyrecest.distributions.conversion import convert_distribution +from pyrecest.distributions.hypersphere_subset.custom_hemispherical_distribution import ( + CustomHemisphericalDistribution, +) +from pyrecest.distributions.hypersphere_subset.custom_hyperhemispherical_distribution import ( + CustomHyperhemisphericalDistribution, +) +from pyrecest.distributions.hypersphere_subset.custom_hyperspherical_distribution import ( + CustomHypersphericalDistribution, +) +from pyrecest.distributions.nonperiodic.custom_linear_distribution import ( + CustomLinearDistribution, +) + + +def _constant_pdf(xs): + return xs[..., 0] * 0.0 + 1.0 + + +class _CustomLinearSubclass(CustomLinearDistribution): + pass + + +class _CustomHypercylindricalSubclass(CustomHypercylindricalDistribution): + pass + + +class _CustomHypersphericalSubclass(CustomHypersphericalDistribution): + pass + + +class _CustomHyperhemisphericalSubclass(CustomHyperhemisphericalDistribution): + pass + + +class _CustomHemisphericalSubclass(CustomHemisphericalDistribution): + pass + + +class CustomFactorySubclassPreservationTest(unittest.TestCase): + def test_custom_linear_conversion_preserves_requested_subclass(self): + source = CustomLinearDistribution(_constant_pdf, dim=1) + + converted = convert_distribution(source, _CustomLinearSubclass) + + self.assertIsInstance(converted, _CustomLinearSubclass) + + def test_custom_hypercylindrical_conversion_preserves_requested_subclass(self): + source = CustomHypercylindricalDistribution( + _constant_pdf, bound_dim=1, lin_dim=1 + ) + + converted = convert_distribution(source, _CustomHypercylindricalSubclass) + + self.assertIsInstance(converted, _CustomHypercylindricalSubclass) + + def test_custom_hyperspherical_conversion_preserves_requested_subclass(self): + source = CustomHypersphericalDistribution(_constant_pdf, dim=2) + + converted = convert_distribution(source, _CustomHypersphericalSubclass) + + self.assertIsInstance(converted, _CustomHypersphericalSubclass) + + def test_custom_hyperhemispherical_conversion_preserves_requested_subclass(self): + source = CustomHyperhemisphericalDistribution(_constant_pdf, dim=2) + + converted = convert_distribution(source, _CustomHyperhemisphericalSubclass) + + self.assertIsInstance(converted, _CustomHyperhemisphericalSubclass) + + def test_custom_hemispherical_conversion_preserves_requested_subclass(self): + source = CustomHemisphericalDistribution(_constant_pdf) + + converted = convert_distribution(source, _CustomHemisphericalSubclass) + + self.assertIsInstance(converted, _CustomHemisphericalSubclass) + + +if __name__ == "__main__": + unittest.main()