Skip to content
Open
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
36 changes: 30 additions & 6 deletions pymc/distributions/transforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import warnings

from functools import singledispatch

import numpy as np
Expand Down Expand Up @@ -45,7 +47,7 @@
"logodds",
"ordered",
"simplex",
"sum_to_1",
"sum_to_1", # noqa: F822 (deprecated; served lazily via module __getattr__)
]


Expand Down Expand Up @@ -123,11 +125,24 @@ class SumTo1(Transform):
Transforms K - 1 dimensional simplex space (K values in [0, 1] that sum to 1) to a K - 1 vector of values in [0, 1].

This transformation operates on the last dimension of the input tensor.

.. warning::
SumTo1 is deprecated and will be removed in a future release.
Use :data:`~pymc.distributions.transforms.simplex` instead.
"""

name = "sumto1"
ndim_supp = 1

def __init__(self):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's enough to deprecate access to the sum_to_1 and leave the class noiseless. Less code in this PR and same result, as I don't expect people to be creating their own instances

warnings.warn(
"SumTo1 is deprecated and will be removed in a future release. "
"Use the simplex transform instead.",
FutureWarning,
stacklevel=2,
)
super().__init__()

def backward(self, value, *inputs):
remaining = 1 - pt.sum(value[..., :], axis=-1, keepdims=True)
return pt.concatenate([value[..., :], remaining], axis=-1)
Expand Down Expand Up @@ -709,12 +724,21 @@ def log_jac_det(self, value, *rv_inputs):
Instantiation of :class:`pymc.logprob.transforms.LogTransform`
for use in the ``transform`` argument of a random variable."""

sum_to_1 = SumTo1()
sum_to_1.__doc__ = """
Instantiation of :class:`pymc.distributions.transforms.SumTo1`
for use in the ``transform`` argument of a random variable."""

circular = CircularTransform()
circular.__doc__ = """
Instantiation of :class:`pymc.logprob.transforms.CircularTransform`
for use in the ``transform`` argument of a random variable."""


def __getattr__(name):
if name == "sum_to_1":
# `sum_to_1` is served lazily so that simply importing PyMC does not
# raise the FutureWarning emitted by `SumTo1.__init__`. Accessing the
# attribute still warns, as the transform is deprecated.
sum_to_1 = SumTo1()
sum_to_1.__doc__ = """
Instantiation of :class:`pymc.distributions.transforms.SumTo1`
for use in the ``transform`` argument of a random variable."""
return sum_to_1

raise AttributeError(f"module {__name__} has no attribute {name}")
29 changes: 24 additions & 5 deletions tests/distributions/test_transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
# limitations under the License.


import warnings

import numpy as np
import pytensor.tensor as pt
import pytest
Expand Down Expand Up @@ -44,6 +46,12 @@
# stable. The minimal addable slack for float32 is higher thus we need to be less strict
tol = 1e-7 if config.floatX == "float64" else 1e-5

# `sum_to_1` is deprecated (see #7009). Functional tests use a pre-built instance so
# that only the dedicated deprecation test emits the FutureWarning.
with warnings.catch_warnings():
warnings.simplefilter("ignore", FutureWarning)
sum_to_1 = tr.SumTo1()


def check_transform(transform, domain, constructor=pt.scalar, test=0, rv_var=None):
x = constructor("x")
Expand Down Expand Up @@ -145,18 +153,29 @@ def test_simplex_accuracy():


def test_sum_to_1():
check_vector_transform(tr.sum_to_1, Simplex(2))
check_vector_transform(tr.sum_to_1, Simplex(4))
check_vector_transform(sum_to_1, Simplex(2))
check_vector_transform(sum_to_1, Simplex(4))

check_jacobian_det(
tr.sum_to_1,
sum_to_1,
Vector(Unit, 2),
pt.vector,
floatX(np.array([0, 0])),
lambda x: x[:-1],
)


def test_sum_to_1_deprecated():
with pytest.warns(FutureWarning, match="SumTo1 is deprecated"):
tr.SumTo1()

with pytest.warns(FutureWarning, match="SumTo1 is deprecated"):
instance = tr.sum_to_1

# The transform can still be used while deprecated
check_vector_transform(instance, Simplex(2))


def test_log():
check_transform(tr.log, Rplusbig)

Expand Down Expand Up @@ -571,7 +590,7 @@ def test_vonmises_ordered(self, mu, kappa, size):
floatX(np.zeros(3)),
floatX(np.ones(3)),
(4, 3),
tr.Chain([tr.sum_to_1, tr.logodds]),
tr.Chain([sum_to_1, tr.logodds]),
),
],
)
Expand All @@ -593,7 +612,7 @@ def test_uniform_other(self, lower, upper, size, transform):
(floatX(np.zeros(3)), floatX(np.diag(np.ones(3))), (4,), (4, 3)),
],
)
@pytest.mark.parametrize("transform", (tr.ordered, tr.sum_to_1))
@pytest.mark.parametrize("transform", (tr.ordered, sum_to_1))
def test_mvnormal_transform(self, mu, cov, size, shape, transform):
initval = np.sort(np.random.randn(*shape))
model = self.build_model(
Expand Down