Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Allow zero rate Poisson #1657

Merged
merged 22 commits into from
Oct 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
50a86a0
Allow for zero rate possion
matthewfeickert Oct 21, 2021
c58b17b
Use scipy.special.xlogy in poisson computation for numpy backend
matthewfeickert Oct 21, 2021
eeadcc0
use jax.scipy.special.xlogy for 0-rate allowed
matthewfeickert Oct 21, 2021
99d9d98
[ci all]: Allow limit Poisson(lambda ->0) = 1
matthewfeickert Oct 21, 2021
20efb73
Remove nan_ok as no longer relevant
matthewfeickert Oct 21, 2021
76526e4
Add note in the docs
matthewfeickert Oct 21, 2021
e0433b5
Update torch requirement for API stability
matthewfeickert Oct 21, 2021
bd7f518
Remove extra space
matthewfeickert Oct 21, 2021
e82c456
Update to torch v1.10.0 in lower-bound-requirements check to pass pytest
matthewfeickert Oct 21, 2021
ff9baa7
Seperate out list for visual ease
matthewfeickert Oct 21, 2021
bd1a1aa
Bump tensorflow
matthewfeickert Oct 22, 2021
b30dc6b
bump tfp to 0.11.0
matthewfeickert Oct 22, 2021
80fd292
update note
matthewfeickert Oct 22, 2021
d0d604f
try tensorflow v2.2.3
matthewfeickert Oct 22, 2021
7c0f83f
try tfp 0.10.1
matthewfeickert Oct 22, 2021
1db798f
Set tensorflow v2.3.1, tfp v0.11.0
matthewfeickert Oct 22, 2021
b8b9009
set tfp min to v0.11.0, which requires tf v2.3+
matthewfeickert Oct 22, 2021
10746dd
Test docstrings on the latest release
matthewfeickert Oct 22, 2021
bf597eb
nitpick
matthewfeickert Oct 22, 2021
6fd54cd
Use words
matthewfeickert Oct 22, 2021
5d204a0
Make things RE: limit clear in docstring note
matthewfeickert Oct 22, 2021
652813a
clarify n=0 for limit use
matthewfeickert Oct 22, 2021
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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ jobs:
flags: contrib

- name: Test docstring examples with doctest
if: matrix.python-version == 3.9
run: pytest -r sx src/ README.rst

- name: Report doctest coverage with Codecov
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/lower-bound-requirements.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ jobs:
python -m pip --no-cache-dir --quiet install --requirement lower-bound-requirements.txt
python -m pip --no-cache-dir --quiet install .[test]
python -m pip install --requirement lower-bound-requirements.txt
python -m pip list

- name: List installed Python packages
run: python -m pip list

- name: Test with pytest
run: |
Expand Down
6 changes: 3 additions & 3 deletions lower-bound-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ uproot==4.1.1
# minuit
iminuit==2.4.0
# tensorflow
tensorflow==2.2.1 # c.f. PR #1001
tensorflow-probability==0.10.1
tensorflow==2.3.1 # tensorflow-probability v0.11.0 requires tensorflow>=2.3
tensorflow-probability==0.11.0 # c.f. PR #1657
matthewfeickert marked this conversation as resolved.
Show resolved Hide resolved
# torch
torch==1.8.0
torch==1.10.0
# jax
# Use Google Cloud Storage buckets for long term wheel support
# c.f. https://github.com/google/jax/discussions/7608#discussioncomment-1269342
Expand Down
6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
extras_require = {
'shellcomplete': ['click_completion'],
'tensorflow': [
'tensorflow~=2.2,>=2.2.1,!=2.3.0', # c.f. https://github.com/tensorflow/tensorflow/pull/40789
'tensorflow-probability~=0.10,>=0.10.1',
'tensorflow~=2.3,!=2.3.0', # c.f. https://github.com/tensorflow/tensorflow/pull/40789
'tensorflow-probability~=0.11',
],
'torch': ['torch~=1.8'],
'torch': ['torch~=1.10'],
'jax': ['jax~=0.2.8', 'jaxlib~=0.1.58,!=0.1.68'], # c.f. Issue 1501
'xmlio': ['uproot>=4.1.1'],
'minuit': ['iminuit>=2.4'],
Expand Down
20 changes: 17 additions & 3 deletions src/pyhf/tensor/jax_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
config.update('jax_enable_x64', True)

import jax.numpy as jnp
from jax.scipy.special import gammaln
from jax.scipy.special import gammaln, xlogy
from jax.scipy import special
from jax.scipy.stats import norm
import numpy as np
Expand Down Expand Up @@ -368,14 +368,28 @@ def einsum(self, subscripts, *operands):
def poisson_logpdf(self, n, lam):
n = jnp.asarray(n)
lam = jnp.asarray(lam)
return n * jnp.log(lam) - lam - gammaln(n + 1.0)
return xlogy(n, lam) - lam - gammaln(n + 1.0)

def poisson(self, n, lam):
r"""
The continuous approximation, using :math:`n! = \Gamma\left(n+1\right)`,
to the probability mass function of the Poisson distribution evaluated
at :code:`n` given the parameter :code:`lam`.

.. note::

Though the p.m.f of the Poisson distribution is not defined for
:math:`\lambda = 0`, the limit as :math:`\lambda \to 0` is still
defined, which gives a degenerate p.m.f. of

.. math::

\lim_{\lambda \to 0} \,\mathrm{Pois}(n | \lambda) =
\left\{\begin{array}{ll}
1, & n = 0,\\
0, & n > 0
\end{array}\right.

Example:

>>> import pyhf
Expand All @@ -398,7 +412,7 @@ def poisson(self, n, lam):
"""
n = jnp.asarray(n)
lam = jnp.asarray(lam)
return jnp.exp(n * jnp.log(lam) - lam - gammaln(n + 1.0))
return jnp.exp(xlogy(n, lam) - lam - gammaln(n + 1.0))

def normal_logpdf(self, x, mu, sigma):
# this is much faster than
Expand Down
20 changes: 17 additions & 3 deletions src/pyhf/tensor/numpy_backend.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""NumPy Tensor Library Module."""
import numpy as np
import logging
from scipy.special import gammaln
from scipy.special import gammaln, xlogy
from scipy import special
from scipy.stats import norm, poisson

Expand Down Expand Up @@ -349,14 +349,28 @@ def einsum(self, subscripts, *operands):
return np.einsum(subscripts, *operands)

def poisson_logpdf(self, n, lam):
return n * np.log(lam) - lam - gammaln(n + 1.0)
return xlogy(n, lam) - lam - gammaln(n + 1.0)

def poisson(self, n, lam):
r"""
The continuous approximation, using :math:`n! = \Gamma\left(n+1\right)`,
to the probability mass function of the Poisson distribution evaluated
at :code:`n` given the parameter :code:`lam`.

.. note::

Though the p.m.f of the Poisson distribution is not defined for
:math:`\lambda = 0`, the limit as :math:`\lambda \to 0` is still
defined, which gives a degenerate p.m.f. of

.. math::

\lim_{\lambda \to 0} \,\mathrm{Pois}(n | \lambda) =
\left\{\begin{array}{ll}
1, & n = 0,\\
0, & n > 0
\end{array}\right.

Example:

>>> import pyhf
Expand All @@ -379,7 +393,7 @@ def poisson(self, n, lam):
"""
n = np.asarray(n)
lam = np.asarray(lam)
return np.exp(n * np.log(lam) - lam - gammaln(n + 1.0))
return np.exp(xlogy(n, lam) - lam - gammaln(n + 1.0))

def normal_logpdf(self, x, mu, sigma):
# this is much faster than
Expand Down
14 changes: 14 additions & 0 deletions src/pyhf/tensor/pytorch_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,20 @@ def poisson(self, n, lam):
to the probability mass function of the Poisson distribution evaluated
at :code:`n` given the parameter :code:`lam`.

.. note::

Though the p.m.f of the Poisson distribution is not defined for
:math:`\lambda = 0`, the limit as :math:`\lambda \to 0` is still
defined, which gives a degenerate p.m.f. of

.. math::

\lim_{\lambda \to 0} \,\mathrm{Pois}(n | \lambda) =
\left\{\begin{array}{ll}
1, & n = 0,\\
0, & n > 0
\end{array}\right.

Example:

>>> import pyhf
Expand Down
37 changes: 16 additions & 21 deletions src/pyhf/tensor/tensorflow_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import logging
import tensorflow as tf
import tensorflow_probability as tfp
from numpy import nan

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -427,22 +426,28 @@ def poisson_logpdf(self, n, lam):
TensorFlow Tensor: Value of the continuous approximation to log(Poisson(n|lam))
"""
lam = self.astensor(lam)
# Guard against Poisson(n=0 | lam=0)
# c.f. https://github.com/scikit-hep/pyhf/issues/293
valid_obs_given_rate = tf.logical_or(
tf.math.not_equal(lam, n), tf.math.not_equal(n, 0)
)

return tf.where(
valid_obs_given_rate, tfp.distributions.Poisson(lam).log_prob(n), nan
)
return tfp.distributions.Poisson(lam).log_prob(n)

def poisson(self, n, lam):
r"""
The continuous approximation, using :math:`n! = \Gamma\left(n+1\right)`,
to the probability mass function of the Poisson distribution evaluated
at :code:`n` given the parameter :code:`lam`.

.. note::

Though the p.m.f of the Poisson distribution is not defined for
:math:`\lambda = 0`, the limit as :math:`\lambda \to 0` is still
defined, which gives a degenerate p.m.f. of

.. math::

\lim_{\lambda \to 0} \,\mathrm{Pois}(n | \lambda) =
\left\{\begin{array}{ll}
1, & n = 0,\\
0, & n > 0
\end{array}\right.

Example:
>>> import pyhf
>>> pyhf.set_backend("tensorflow")
Expand All @@ -465,17 +470,7 @@ def poisson(self, n, lam):
TensorFlow Tensor: Value of the continuous approximation to Poisson(n|lam)
"""
lam = self.astensor(lam)
# Guard against Poisson(n=0 | lam=0)
# c.f. https://github.com/scikit-hep/pyhf/issues/293
valid_obs_given_rate = tf.logical_or(
tf.math.not_equal(lam, n), tf.math.not_equal(n, 0)
)

return tf.where(
valid_obs_given_rate,
tf.exp(tfp.distributions.Poisson(lam).log_prob(n)),
nan,
)
return tf.exp(tfp.distributions.Poisson(lam).log_prob(n))

def normal_logpdf(self, x, mu, sigma):
r"""
Expand Down
18 changes: 6 additions & 12 deletions tests/test_tensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,17 +287,14 @@ def test_pdf_calculations(backend):
],
nan_ok=True,
)
# poisson(lambda=0) is not defined, should return NaN
# Allow poisson(lambda=0) under limit Poisson(n = 0 | lambda -> 0) = 1
assert tb.tolist(
tb.poisson(tb.astensor([0, 0, 1, 1]), tb.astensor([0, 1, 0, 1]))
) == pytest.approx(
[np.nan, 0.3678794503211975, 0.0, 0.3678794503211975], nan_ok=True
)
) == pytest.approx([1.0, 0.3678794503211975, 0.0, 0.3678794503211975])
assert tb.tolist(
tb.poisson_logpdf(tb.astensor([0, 0, 1, 1]), tb.astensor([0, 1, 0, 1]))
) == pytest.approx(
np.log([np.nan, 0.3678794503211975, 0.0, 0.3678794503211975]).tolist(),
nan_ok=True,
np.log([1.0, 0.3678794503211975, 0.0, 0.3678794503211975]).tolist()
)

# Ensure continuous approximation is valid
Expand Down Expand Up @@ -333,17 +330,14 @@ def test_pdf_calculations_pytorch(backend):
],
)

# poisson(lambda=0) is not defined, should return NaN
# Allow poisson(lambda=0) under limit Poisson(n = 0 | lambda -> 0) = 1
assert tb.tolist(
tb.poisson(tb.astensor([0, 0, 1, 1]), tb.astensor([0, 1, 0, 1]))
) == pytest.approx(
[np.nan, 0.3678794503211975, 0.0, 0.3678794503211975], nan_ok=True
)
) == pytest.approx([1.0, 0.3678794503211975, 0.0, 0.3678794503211975])
assert tb.tolist(
tb.poisson_logpdf(tb.astensor([0, 0, 1, 1]), tb.astensor([0, 1, 0, 1]))
) == pytest.approx(
np.log([np.nan, 0.3678794503211975, 0.0, 0.3678794503211975]).tolist(),
nan_ok=True,
np.log([1.0, 0.3678794503211975, 0.0, 0.3678794503211975]).tolist()
)

# Ensure continuous approximation is valid
Expand Down