# numpyro_schechter
**Schechter galaxy luminosity distribution for NumPyro**
<p align="center">
<img src="https://raw.githubusercontent.com/alserene/numpyro_schechter/main/docs/assets/logo.png" alt="Schechter distribution logo for numpyro_schechter" width="300"/>
</p>
<p align="center">
<a href="https://pypi.org/project/numpyro-schechter/">
<img src="https://img.shields.io/pypi/pyversions/numpyro-schechter.svg" alt="Python Versions">
</a>
<a href="https://numpyro-schechter.readthedocs.io/en/latest/?badge=latest">
<img src="https://readthedocs.org/projects/numpyro-schechter/badge/?version=latest" alt="Docs Status">
</a>
<a href="https://pypi.org/project/numpyro-schechter/">
<img src="https://img.shields.io/pypi/v/numpyro-schechter.svg" alt="PyPI">
</a>
<a href="https://opensource.org/licenses/MIT">
<img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="MIT License">
</a>
<a href="https://github.com/alserene/numpyro_schechter/actions/workflows/tests.yml">
<img src="https://github.com/alserene/numpyro_schechter/actions/workflows/tests.yml/badge.svg" alt="Tests">
</a>
</p>
---
## Overview
`numpyro_schechter` provides NumPyro-compatible probability distributions for Bayesian inference with Schechter and double Schechter luminosity functions in absolute magnitude space.
Built for astronomers and statisticians, it includes a JAX-compatible, differentiable implementation of the upper incomplete gamma function, enabling stable and efficient modelling in probabilistic programming frameworks.
---
## Parameter Constraints
Due to the custom normalisation logic, some constraints apply:
- `alpha` must be real and non-integer.
- The valid range of `alpha + 1` depends on `alpha_domain_depth`. By default, `alpha_domain_depth=3`, which supports the domain `-3 < alpha + 1 < 3`.
- To model more extreme values of `alpha`, increase the `alpha_domain_depth` parameter (see below).
- The list of valid depths is fixed and can be queried programmatically:
```python
from numpyro_schechter import SchechterMag
SchechterMag.supported_depths()
# -> [3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 30]
```
- If you set `include_poisson_term=True`, the log-likelihood will also include a Poisson term for the total number counts, with expectation `N_exp = norm * volume`. The volume argument defaults to `1.0` but can be set to your survey volume.
---
## Installation
From PyPI:
```bash
pip install numpyro_schechter
```
From GitHub (latest development version):
```bash
pip install git+https://github.com/alserene/numpyro_schechter.git
```
---
## Usage
### Single Schechter Example
Here is a minimal example showing how to use the `SchechterMag` distribution:
```python
import jax.numpy as jnp
import numpyro
import numpyro.distributions as dist
from numpyro_schechter.distribution import SchechterMag
# Simulated observed magnitudes
mag_obs = jnp.linspace(-24, -18, 100)
def model(mag_obs):
# Priors
alpha = numpyro.sample("alpha", dist.Uniform(-3.0, 1.0))
M_star = numpyro.sample("M_star", dist.Uniform(-24.0, -20.0))
logphi = numpyro.sample("logphi", dist.Normal(-3.0, 1.0))
# Custom likelihood using the SchechterMag distribution, fitting only
# the shape. Adding include_poisson_term=True would include total counts.
# See Double Schechter Example for fitting both shape and total count.
schechter_dist = SchechterMag(alpha=alpha, M_star=M_star, logphi=logphi,
mag_obs=mag_obs)
# Manually inject log-likelihood of observed magnitudes.
# Required because SchechterMag/DoubleSchechterMag are custom distributions.
log_likelihood = jnp.sum(schechter_dist.log_prob(mag_obs))
numpyro.factor("likelihood", log_likelihood)
# You can now run inference with NumPyro's MCMC
# e.g., numpyro.infer.MCMC(...).run(rng_key, model, mag_obs=...)
# Note: Sampling is not implemented for SchechterMag or DoubleSchechterMag;
# it is intended for use as a likelihood in inference.
```
### Double Schechter Example
The double Schechter function is the sum of two Schechter components, each with their own parameters
$(\alpha, M^\*, \phi^\*)$, normalised together over the observed magnitude range:
$$
\phi_{\text{double}}(M) =
\frac{\phi_1(M) + \phi_2(M)}{\phi_1^\* \Gamma_1 + \phi_2^\* \Gamma_2}
$$
where $\Gamma_i$ is the upper incomplete gamma function for component $i$.
```python
import jax.numpy as jnp
import numpyro
import numpyro.distributions as dist
from numpyro_schechter import DoubleSchechterMag
def double_schechter_model(mag_obs, volume):
# Slopes
alpha1 = numpyro.sample("alpha1", dist.TruncatedNormal(-0.3, 0.1, low=-1.5, high=0.5))
alpha2 = numpyro.sample("alpha2", dist.TruncatedNormal(-1.3, 0.1, low=-2.5, high=-0.5))
# Normalisations (log10 φ*)
logphi1 = numpyro.sample("logphi1", dist.Normal(-2.5, 0.3))
logphi2 = numpyro.sample("logphi2", dist.Normal(-3.0, 0.3))
# Break magnitudes
M_star1 = numpyro.sample("M_star1", dist.TruncatedNormal(-21.0, 0.5, low=-23.0, high=-19.0))
M_star2 = numpyro.sample("M_star2", dist.TruncatedNormal(-21.5, 0.5, low=-23.5, high=-19.5))
# Likelihood: includes both point term and Poisson count term to fit
# both shape and total count. If include_poisson_term=True, then volume
# is used to scale expected counts and defaults to 1.0.
dbl_schechter_dist = DoubleSchechterMag(alpha1, M_star1, logphi1,
alpha2, M_star2, logphi2,
mag_obs=mag_obs,
include_poisson_term=True,
volume=volume)
# Manually inject log-likelihood of observed magnitudes.
# Required because SchechterMag/DoubleSchechterMag are custom distributions.
log_likelihood = jnp.sum(dbl_schechter_dist.log_prob(mag_obs))
numpyro.factor("likelihood", log_likelihood)
# --- Example MCMC runner (same approach can be used for SchechterMag)
# from numpyro.infer import MCMC, NUTS
# mcmc = MCMC(NUTS(double_schechter_model), num_warmup=1000, num_samples=2000)
# mcmc.run(rng_key, mag_obs=your_magnitude_array, volume=your_survey_volume)
# samples = mcmc.get_samples()
```
For detailed usage and API documentation, please visit the [Documentation](https://numpyro-schechter.readthedocs.io/).
---
## Development
If you want to contribute or develop locally:
```bash
git clone https://github.com/alserene/numpyro_schechter.git
cd numpyro_schechter
poetry install
poetry run pytest
```
---
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file.
---
## Contact
Created by
- Alice — [aserene@swin.edu.au](mailto:aserene@swin.edu.au)
- Aryan - [aryanbansal@swin.edu.au](mailto:aryanbansal@swin.edu.au)
- Edward - [entaylor@swin.edu.au](mailto:entaylor@swin.edu.au)
---
*Happy modelling!*
Raw data
{
"_id": null,
"home_page": null,
"name": "numpyro_schechter",
"maintainer": null,
"docs_url": null,
"requires_python": "<3.14,>=3.10",
"maintainer_email": null,
"keywords": "numpyro, schechter, astronomy, bayesian, distribution",
"author": "Alice Serene",
"author_email": "aserene@swin.edu.au",
"download_url": "https://files.pythonhosted.org/packages/4b/86/7dcba7608255be56fee5a7c825bf68e7e280ebf1ba3bfb13f0163acc3185/numpyro_schechter-1.1.0.tar.gz",
"platform": null,
"description": "# numpyro_schechter\n\n**Schechter galaxy luminosity distribution for NumPyro**\n\n<p align=\"center\">\n <img src=\"https://raw.githubusercontent.com/alserene/numpyro_schechter/main/docs/assets/logo.png\" alt=\"Schechter distribution logo for numpyro_schechter\" width=\"300\"/>\n</p>\n\n<p align=\"center\">\n <a href=\"https://pypi.org/project/numpyro-schechter/\">\n <img src=\"https://img.shields.io/pypi/pyversions/numpyro-schechter.svg\" alt=\"Python Versions\">\n </a>\n <a href=\"https://numpyro-schechter.readthedocs.io/en/latest/?badge=latest\">\n <img src=\"https://readthedocs.org/projects/numpyro-schechter/badge/?version=latest\" alt=\"Docs Status\">\n </a>\n <a href=\"https://pypi.org/project/numpyro-schechter/\">\n <img src=\"https://img.shields.io/pypi/v/numpyro-schechter.svg\" alt=\"PyPI\">\n </a>\n <a href=\"https://opensource.org/licenses/MIT\">\n <img src=\"https://img.shields.io/badge/License-MIT-yellow.svg\" alt=\"MIT License\">\n </a>\n <a href=\"https://github.com/alserene/numpyro_schechter/actions/workflows/tests.yml\">\n <img src=\"https://github.com/alserene/numpyro_schechter/actions/workflows/tests.yml/badge.svg\" alt=\"Tests\">\n </a>\n</p>\n\n---\n\n## Overview\n\n`numpyro_schechter` provides NumPyro-compatible probability distributions for Bayesian inference with Schechter and double Schechter luminosity functions in absolute magnitude space.\n\nBuilt for astronomers and statisticians, it includes a JAX-compatible, differentiable implementation of the upper incomplete gamma function, enabling stable and efficient modelling in probabilistic programming frameworks.\n\n---\n\n## Parameter Constraints\n\nDue to the custom normalisation logic, some constraints apply:\n\n- `alpha` must be real and non-integer.\n- The valid range of `alpha + 1` depends on `alpha_domain_depth`. By default, `alpha_domain_depth=3`, which supports the domain `-3 < alpha + 1 < 3`.\n- To model more extreme values of `alpha`, increase the `alpha_domain_depth` parameter (see below).\n- The list of valid depths is fixed and can be queried programmatically:\n ```python\n from numpyro_schechter import SchechterMag\n SchechterMag.supported_depths()\n # -> [3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 30]\n ```\n- If you set `include_poisson_term=True`, the log-likelihood will also include a Poisson term for the total number counts, with expectation `N_exp = norm * volume`. The volume argument defaults to `1.0` but can be set to your survey volume.\n\n---\n\n## Installation\n\nFrom PyPI:\n\n```bash\npip install numpyro_schechter\n```\n\nFrom GitHub (latest development version):\n\n```bash\npip install git+https://github.com/alserene/numpyro_schechter.git\n```\n\n---\n\n## Usage\n\n### Single Schechter Example\n\nHere is a minimal example showing how to use the `SchechterMag` distribution:\n\n```python\nimport jax.numpy as jnp\nimport numpyro\nimport numpyro.distributions as dist\nfrom numpyro_schechter.distribution import SchechterMag\n\n# Simulated observed magnitudes\nmag_obs = jnp.linspace(-24, -18, 100)\n\ndef model(mag_obs):\n # Priors\n alpha = numpyro.sample(\"alpha\", dist.Uniform(-3.0, 1.0))\n M_star = numpyro.sample(\"M_star\", dist.Uniform(-24.0, -20.0))\n logphi = numpyro.sample(\"logphi\", dist.Normal(-3.0, 1.0))\n\n # Custom likelihood using the SchechterMag distribution, fitting only\n # the shape. Adding include_poisson_term=True would include total counts.\n # See Double Schechter Example for fitting both shape and total count.\n schechter_dist = SchechterMag(alpha=alpha, M_star=M_star, logphi=logphi,\n mag_obs=mag_obs)\n \n # Manually inject log-likelihood of observed magnitudes.\n # Required because SchechterMag/DoubleSchechterMag are custom distributions.\n log_likelihood = jnp.sum(schechter_dist.log_prob(mag_obs))\n numpyro.factor(\"likelihood\", log_likelihood)\n\n# You can now run inference with NumPyro's MCMC\n# e.g., numpyro.infer.MCMC(...).run(rng_key, model, mag_obs=...)\n\n# Note: Sampling is not implemented for SchechterMag or DoubleSchechterMag;\n# it is intended for use as a likelihood in inference.\n```\n\n### Double Schechter Example\n\nThe double Schechter function is the sum of two Schechter components, each with their own parameters \n$(\\alpha, M^\\*, \\phi^\\*)$, normalised together over the observed magnitude range:\n\n$$\n\\phi_{\\text{double}}(M) = \n\\frac{\\phi_1(M) + \\phi_2(M)}{\\phi_1^\\* \\Gamma_1 + \\phi_2^\\* \\Gamma_2}\n$$\n\nwhere $\\Gamma_i$ is the upper incomplete gamma function for component $i$.\n\n```python\nimport jax.numpy as jnp\nimport numpyro\nimport numpyro.distributions as dist\nfrom numpyro_schechter import DoubleSchechterMag\n\ndef double_schechter_model(mag_obs, volume):\n # Slopes\n alpha1 = numpyro.sample(\"alpha1\", dist.TruncatedNormal(-0.3, 0.1, low=-1.5, high=0.5))\n alpha2 = numpyro.sample(\"alpha2\", dist.TruncatedNormal(-1.3, 0.1, low=-2.5, high=-0.5))\n\n # Normalisations (log10 \u03c6*)\n logphi1 = numpyro.sample(\"logphi1\", dist.Normal(-2.5, 0.3))\n logphi2 = numpyro.sample(\"logphi2\", dist.Normal(-3.0, 0.3))\n\n # Break magnitudes\n M_star1 = numpyro.sample(\"M_star1\", dist.TruncatedNormal(-21.0, 0.5, low=-23.0, high=-19.0))\n M_star2 = numpyro.sample(\"M_star2\", dist.TruncatedNormal(-21.5, 0.5, low=-23.5, high=-19.5))\n\n # Likelihood: includes both point term and Poisson count term to fit\n # both shape and total count. If include_poisson_term=True, then volume\n # is used to scale expected counts and defaults to 1.0.\n dbl_schechter_dist = DoubleSchechterMag(alpha1, M_star1, logphi1,\n alpha2, M_star2, logphi2,\n mag_obs=mag_obs,\n include_poisson_term=True,\n volume=volume)\n\n # Manually inject log-likelihood of observed magnitudes.\n # Required because SchechterMag/DoubleSchechterMag are custom distributions.\n log_likelihood = jnp.sum(dbl_schechter_dist.log_prob(mag_obs))\n numpyro.factor(\"likelihood\", log_likelihood)\n\n# --- Example MCMC runner (same approach can be used for SchechterMag)\n# from numpyro.infer import MCMC, NUTS\n# mcmc = MCMC(NUTS(double_schechter_model), num_warmup=1000, num_samples=2000)\n# mcmc.run(rng_key, mag_obs=your_magnitude_array, volume=your_survey_volume)\n# samples = mcmc.get_samples()\n\n```\n\nFor detailed usage and API documentation, please visit the [Documentation](https://numpyro-schechter.readthedocs.io/).\n\n---\n\n## Development\n\nIf you want to contribute or develop locally:\n\n```bash\ngit clone https://github.com/alserene/numpyro_schechter.git\ncd numpyro_schechter\npoetry install\npoetry run pytest\n```\n\n---\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file.\n\n---\n\n## Contact\n\nCreated by\n - Alice \u2014 [aserene@swin.edu.au](mailto:aserene@swin.edu.au)\n - Aryan - [aryanbansal@swin.edu.au](mailto:aryanbansal@swin.edu.au)\n - Edward - [entaylor@swin.edu.au](mailto:entaylor@swin.edu.au)\n\n---\n\n*Happy modelling!*\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Schechter galaxy luminosity distribution for NumPyro.",
"version": "1.1.0",
"project_urls": {
"Homepage": "https://github.com/alserene/numpyro_schechter",
"Issues": "https://github.com/alserene/numpyro_schechter/issues"
},
"split_keywords": [
"numpyro",
" schechter",
" astronomy",
" bayesian",
" distribution"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "2946b3007670792664023e5515dd1661bb7adda6b17be1125232ee135671b5bd",
"md5": "df10096d0328af09eddbbb281275f65d",
"sha256": "5b45601c96254e04db5006622f0b424822796d57e94385669d1a8b6bfa377af5"
},
"downloads": -1,
"filename": "numpyro_schechter-1.1.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "df10096d0328af09eddbbb281275f65d",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<3.14,>=3.10",
"size": 8847,
"upload_time": "2025-08-21T05:12:40",
"upload_time_iso_8601": "2025-08-21T05:12:40.695465Z",
"url": "https://files.pythonhosted.org/packages/29/46/b3007670792664023e5515dd1661bb7adda6b17be1125232ee135671b5bd/numpyro_schechter-1.1.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "4b867dcba7608255be56fee5a7c825bf68e7e280ebf1ba3bfb13f0163acc3185",
"md5": "d08ccd08d817c12e4e67fd937b4b26bd",
"sha256": "32435cc0e999640b40140b8fa177be7900715095b61bea6581ae740f2cb9cbb2"
},
"downloads": -1,
"filename": "numpyro_schechter-1.1.0.tar.gz",
"has_sig": false,
"md5_digest": "d08ccd08d817c12e4e67fd937b4b26bd",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<3.14,>=3.10",
"size": 7581,
"upload_time": "2025-08-21T05:12:41",
"upload_time_iso_8601": "2025-08-21T05:12:41.976322Z",
"url": "https://files.pythonhosted.org/packages/4b/86/7dcba7608255be56fee5a7c825bf68e7e280ebf1ba3bfb13f0163acc3185/numpyro_schechter-1.1.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-21 05:12:41",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "alserene",
"github_project": "numpyro_schechter",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "numpyro_schechter"
}