Skip to content

Commit ecbcf38

Browse files
authored
Rename moment to finite_logp_point (#7166)
1 parent d6535e8 commit ecbcf38

22 files changed

+534
-463
lines changed

docs/source/contributing/implementing_distribution.md

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ This guide provides an overview on how to implement a distribution for PyMC.
55
It is designed for developers who wish to add a new distribution to the library.
66
Users will not be aware of all this complexity and should instead make use of helper methods such as `~pymc.CustomDist`.
77

8-
PyMC {class}`~pymc.Distribution` builds on top of PyTensor's {class}`~pytensor.tensor.random.op.RandomVariable`, and implements `logp`, `logcdf`, `icdf` and `moment` methods as well as other initialization and validation helpers.
8+
PyMC {class}`~pymc.Distribution` builds on top of PyTensor's {class}`~pytensor.tensor.random.op.RandomVariable`, and implements `logp`, `logcdf`, `icdf` and `support_point` methods as well as other initialization and validation helpers.
99
Most notably `shape/dims/observed` kwargs, alternative parametrizations, and default `transform`.
1010

1111
Here is a summary check-list of the steps needed to implement a new distribution.
@@ -14,7 +14,7 @@ Each section will be expanded below:
1414
1. Creating a new `RandomVariable` `Op`
1515
1. Implementing the corresponding `Distribution` class
1616
1. Adding tests for the new `RandomVariable`
17-
1. Adding tests for `logp` / `logcdf` / `icdf` and `moment` methods
17+
1. Adding tests for `logp` / `logcdf` / `icdf` and `support_point` methods
1818
1. Documenting the new `Distribution`.
1919

2020
This guide does not attempt to explain the rationale behind the `Distributions` current implementation, and details are provided only insofar as they help to implement new "standard" distributions.
@@ -120,7 +120,7 @@ After implementing the new `RandomVariable` `Op`, it's time to make use of it in
120120
PyMC works in a very {term}`functional <Functional Programming>` way, and the `distribution` classes are there mostly to add PyMC API features and keep related methods organized together.
121121
In practice, they take care of:
122122

123-
1. Linking ({term}`Dispatching`) an `rv_op` class with the corresponding `moment`, `logp`, `logcdf` and `icdf` methods.
123+
1. Linking ({term}`Dispatching`) an `rv_op` class with the corresponding `support_point`, `logp`, `logcdf` and `icdf` methods.
124124
1. Defining a standard transformation (for continuous distributions) that converts a bounded variable domain (e.g., positive line) to an unbounded domain (i.e., the real line), which many samplers prefer.
125125
1. Validating the parametrization of a distribution and converting non-symbolic inputs (i.e., numeric literals or NumPy arrays) to symbolic variables.
126126
1. Converting multiple alternative parametrizations to the standard parametrization that the `RandomVariable` is defined in terms of.
@@ -156,14 +156,14 @@ class Blah(PositiveContinuous):
156156
# the rv_op needs in order to be instantiated
157157
return super().dist([param1, param2], **kwargs)
158158

159-
# moment returns a symbolic expression for the stable moment from which to start sampling
159+
# support_point returns a symbolic expression for the stable point from which to start sampling
160160
# the variable, given the implicit `rv`, `size` and `param1` ... `paramN`.
161161
# This is typically a "representative" point such as the the mean or mode.
162-
def moment(rv, size, param1, param2):
163-
moment, _ = pt.broadcast_arrays(param1, param2)
162+
def support_point(rv, size, param1, param2):
163+
support_point, _ = pt.broadcast_arrays(param1, param2)
164164
if not rv_size_is_none(size):
165-
moment = pt.full(size, moment)
166-
return moment
165+
support_point = pt.full(size, support_point)
166+
return support_point
167167

168168
# Logp returns a symbolic expression for the elementwise log-pdf or log-pmf evaluation
169169
# of the variable given the `value` of the variable and the parameters `param1` ... `paramN`.
@@ -200,18 +200,18 @@ class Blah(PositiveContinuous):
200200
Some notes:
201201

202202
1. A distribution should at the very least inherit from {class}`~pymc.Discrete` or {class}`~pymc.Continuous`. For the latter, more specific subclasses exist: `PositiveContinuous`, `UnitContinuous`, `BoundedContinuous`, `CircularContinuous`, `SimplexContinuous`, which specify default transformations for the variables. If you need to specify a one-time custom transform you can also create a `_default_transform` dispatch function as is done for the {class}`~pymc.distributions.multivariate.LKJCholeskyCov`.
203-
1. If a distribution does not have a corresponding `rng_fn` implementation, a `RandomVariable` should still be created to raise a `NotImplementedError`. This is, for example, the case in {class}`~pymc.distributions.continuous.Flat`. In this case it will be necessary to provide a `moment` method, because without a `rng_fn`, PyMC can't fall back to a random draw to use as an initial point for MCMC.
204-
1. As mentioned above, PyMC works in a very {term}`functional <Functional Programming>` way, and all the information that is needed in the `logp`, `logcdf`, `icdf` and `moment` methods is expected to be "carried" via the `RandomVariable` inputs. You may pass numerical arguments that are not strictly needed for the `rng_fn` method but are used in the those methods. Just keep in mind whether this affects the correct shape inference behavior of the `RandomVariable`.
203+
1. If a distribution does not have a corresponding `rng_fn` implementation, a `RandomVariable` should still be created to raise a `NotImplementedError`. This is, for example, the case in {class}`~pymc.distributions.continuous.Flat`. In this case it will be necessary to provide a `support_point` method, because without a `rng_fn`, PyMC can't fall back to a random draw to use as an initial point for MCMC.
204+
1. As mentioned above, PyMC works in a very {term}`functional <Functional Programming>` way, and all the information that is needed in the `logp`, `logcdf`, `icdf` and `support_point` methods is expected to be "carried" via the `RandomVariable` inputs. You may pass numerical arguments that are not strictly needed for the `rng_fn` method but are used in the those methods. Just keep in mind whether this affects the correct shape inference behavior of the `RandomVariable`.
205205
1. The `logcdf`, and `icdf` methods is not a requirement, but it's a nice plus!
206-
1. Currently, only one moment is supported in the `moment` method, and probably the "higher-order" one is the most useful (that is `mean` > `median` > `mode`)... You might need to truncate the moment if you are dealing with a discrete distribution. `moment` should return a valid point for the random variable (i.e., it always has non-zero probability when evaluated at that point)
207-
1. When creating the `moment` method, be careful with `size != None` and broadcast properly also based on parameters that are not necessarily used to calculate the moment. For example, the `sigma` in `pm.Normal.dist(mu=0, sigma=np.arange(1, 6))` is irrelevant for the moment, but may nevertheless inform about the shape. In this case, the `moment` should return `[mu, mu, mu, mu, mu]`.
206+
1. Currently, only one moment is supported in the `support_point` method, and probably the "higher-order" one is the most useful (that is `mean` > `median` > `mode`)... You might need to truncate the moment if you are dealing with a discrete distribution. `support_point` should return a valid point for the random variable (i.e., it always has non-zero probability when evaluated at that point)
207+
1. When creating the `support_point` method, be careful with `size != None` and broadcast properly also based on parameters that are not necessarily used to calculate the moment. For example, the `sigma` in `pm.Normal.dist(mu=0, sigma=np.arange(1, 6))` is irrelevant for the moment, but may nevertheless inform about the shape. In this case, the `support_point` should return `[mu, mu, mu, mu, mu]`.
208208

209209
For a quick check that things are working you can try the following:
210210

211211
```python
212212

213213
import pymc as pm
214-
from pymc.distributions.distribution import moment
214+
from pymc.distributions.distribution import support_point
215215

216216
# pm.blah = pm.Normal in this example
217217
blah = pm.blah.dist(mu=0, sigma=1)
@@ -220,8 +220,8 @@ blah = pm.blah.dist(mu=0, sigma=1)
220220
pm.draw(blah, random_seed=1)
221221
# array(-1.01397228)
222222

223-
# Test the moment method
224-
moment(blah).eval()
223+
# Test the support_point method
224+
support_point(blah).eval()
225225
# array(0.)
226226

227227
# Test the logp method
@@ -371,9 +371,9 @@ def test_blah_logcdf(self):
371371

372372
```
373373

374-
## 5. Adding tests for the `moment` method
374+
## 5. Adding tests for the `support_point` method
375375

376-
Tests for the `moment` make use of the function `assert_moment_is_expected`
376+
Tests for the `support_point` make use of the function `assert_support_point_is_expected`
377377
which checks if:
378378
1. Moments return the `expected` values
379379
1. Moments have the expected size and shape
@@ -383,7 +383,7 @@ which checks if:
383383

384384
import pytest
385385
from pymc.distributions import Blah
386-
from pymc.testing import assert_moment_is_expected
386+
from pymc.testing import assert_support_point_is_expected
387387

388388

389389
@pytest.mark.parametrize(
@@ -395,10 +395,10 @@ from pymc.testing import assert_moment_is_expected
395395
(np.arange(5), np.arange(1, 6), (2, 5), np.full((2, 5), np.arange(5))),
396396
],
397397
)
398-
def test_blah_moment(param1, param2, size, expected):
398+
def test_blah_support_point(param1, param2, size, expected):
399399
with Model() as model:
400400
Blah("x", param1=param1, param2=param2, size=size)
401-
assert_moment_is_expected(model, expected)
401+
assert_support_point_is_expected(model, expected)
402402

403403
```
404404

pymc/distributions/censored.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from pymc.distributions.distribution import (
2121
Distribution,
2222
SymbolicRandomVariable,
23-
_moment,
23+
_support_point,
2424
)
2525
from pymc.distributions.shape_utils import _change_dist_size, change_dist_size
2626
from pymc.util import check_dist_not_registered
@@ -127,9 +127,9 @@ def change_censored_size(cls, dist, new_size, expand=False):
127127
return Censored.rv_op(uncensored_dist, lower, upper, size=new_size)
128128

129129

130-
@_moment.register(CensoredRV)
131-
def moment_censored(op, rv, dist, lower, upper):
132-
moment = pt.switch(
130+
@_support_point.register(CensoredRV)
131+
def support_point_censored(op, rv, dist, lower, upper):
132+
support_point = pt.switch(
133133
pt.eq(lower, -np.inf),
134134
pt.switch(
135135
pt.isinf(upper),
@@ -146,5 +146,5 @@ def moment_censored(op, rv, dist, lower, upper):
146146
(lower + upper) / 2,
147147
),
148148
)
149-
moment = pt.full_like(dist, moment)
150-
return moment
149+
support_point = pt.full_like(dist, support_point)
150+
return support_point

0 commit comments

Comments
 (0)