From 0190cd532e2e3c82cb7533325b8b785fa988acb4 Mon Sep 17 00:00:00 2001 From: "niko.sarcevic" Date: Sat, 13 Jun 2026 23:39:49 -0400 Subject: [PATCH 1/2] updated pyproject s#so doc build and doctest is contained --- pyproject.toml | 42 +++++++++++++++--------------------------- 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 966659f8..74af1e1a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -120,20 +120,18 @@ commands = [ ] [tool.tox.env.docs] -description = "Build docs with Sphinx and doctest" +description = "Build docs with Sphinx" extras = ["docs", "viz"] commands = [ [ - "sphinx-apidoc", - "-d", "2", - "-o", "{tox_root}/docs/api", - "{tox_root}/src/lfkit", - "--separate", - "--no-toc", - "-f", + "rm", + "-rf", + "{tox_root}/docs/_build", + "{tox_root}/docs/api/generated", ], [ "sphinx-build", + "-E", "-c", "{tox_root}/docs", "-b", "{posargs:html}", "{tox_root}/docs", @@ -144,27 +142,18 @@ commands = [ [tool.tox.env.do] description = "Run doctests + html build and open local docs" -recreate = true extras = ["docs", "viz"] allowlist_externals = ["open", "rm"] commands = [ [ "rm", "-rf", - "{tox_root}/docs/_build/doctest", - "{tox_root}/docs/_build/html", - ], - [ - "sphinx-apidoc", - "-d", "2", - "-o", "{tox_root}/docs/api", - "{tox_root}/src/lfkit", - "--separate", - "--no-toc", - "-f", + "{tox_root}/docs/_build", + "{tox_root}/docs/api/generated", ], [ "sphinx-build", + "-E", "-b", "doctest", "{tox_root}/docs", "{tox_root}/docs/_build/doctest", @@ -172,6 +161,7 @@ commands = [ ], [ "sphinx-build", + "-E", "-b", "html", "{tox_root}/docs", "{tox_root}/docs/_build/html", @@ -183,15 +173,13 @@ commands = [ [tool.tox.env.docs-releases] description = "Build the documentation for lfkit releases using Sphinx" extras = ["docs", "viz"] -allowlist_externals = ["cp"] +allowlist_externals = ["cp", "rm"] commands = [ [ - "sphinx-apidoc", - "-d", "2", - "-o", "{tox_root}/docs/api", - "{tox_root}/src/lfkit", - "--separate", - "-f", + "rm", + "-rf", + "{tox_root}/docs/_build", + "{tox_root}/docs/api/generated", ], [ "sphinx-multiversion", From 6f65bb9437175aaa3e6d0fb5db776a0df80fef45 Mon Sep 17 00:00:00 2001 From: "niko.sarcevic" Date: Sat, 13 Jun 2026 23:40:11 -0400 Subject: [PATCH 2/2] updated docstrings in src --- docs/about/photometry_overview.rst | 2 +- ...it.api.conditional_luminosity_function.rst | 0 .../{ => generated}/lfkit.api.corrections.rst | 0 .../lfkit.api.luminosity_function.rst | 0 docs/api/{ => generated}/lfkit.api.rst | 0 .../lfkit.corrections.color_anchors.rst | 0 .../lfkit.corrections.filters.rst | 0 .../lfkit.corrections.kcorrect_backend.rst | 0 .../lfkit.corrections.kcorrect_from_color.rst | 0 .../lfkit.corrections.kcorrect_grids.rst | 0 .../lfkit.corrections.poggianti1997.rst | 0 .../lfkit.corrections.responses.rst | 0 .../api/{ => generated}/lfkit.corrections.rst | 0 .../{ => generated}/lfkit.cosmo.cosmology.rst | 0 docs/api/{ => generated}/lfkit.cosmo.rst | 0 ...fkit.luminosity_functions.completeness.rst | 0 ...nosity_functions.conditional_integrals.rst | 0 ...uminosity_functions.conditional_models.rst | 0 .../lfkit.luminosity_functions.integrals.rst | 0 ....luminosity_functions.models.composite.rst | 0 ...t.luminosity_functions.models.gaussian.rst | 0 ....luminosity_functions.models.modifiers.rst | 0 ....luminosity_functions.models.power_law.rst | 0 .../lfkit.luminosity_functions.models.rst | 0 ....luminosity_functions.models.schechter.rst | 0 ....luminosity_functions.parameter_models.rst | 0 ....luminosity_functions.redshift_density.rst | 0 .../lfkit.luminosity_functions.registry.rst | 0 .../lfkit.luminosity_functions.rst | 0 .../lfkit.photometry.luminosities.rst | 0 .../lfkit.photometry.magnitudes.rst | 0 docs/api/{ => generated}/lfkit.photometry.rst | 0 docs/api/{ => generated}/lfkit.rst | 0 .../lfkit.utils.download_poggianti97_data.rst | 0 .../lfkit.utils.evaluators.rst | 0 .../lfkit.utils.integrators.rst | 0 .../lfkit.utils.interpolation.rst | 0 docs/api/{ => generated}/lfkit.utils.io.rst | 0 docs/api/{ => generated}/lfkit.utils.rst | 0 .../api/{ => generated}/lfkit.utils.types.rst | 0 .../api/{ => generated}/lfkit.utils.units.rst | 0 .../lfkit.utils.validators.rst | 0 docs/api/index.rst | 12 +- .../conditional_luminosity_function.rst | 6 +- docs/examples/lf_models/composite_models.rst | 4 +- docs/examples/lf_models/power_law_models.rst | 8 +- docs/examples/lf_models/schechter_models.rst | 2 +- src/lfkit/api/_namespaces.py | 61 ++++++-- .../api/conditional_luminosity_function.py | 86 ++++++----- src/lfkit/api/luminosity_function.py | 125 +++++++++------- src/lfkit/luminosity_functions/_discovery.py | 8 +- .../conditional_integrals.py | 16 +- .../conditional_models.py | 40 ++++- src/lfkit/luminosity_functions/integrals.py | 87 ++++++++++- .../luminosity_functions/models/composite.py | 86 ++++++++++- .../luminosity_functions/models/gaussian.py | 82 ++++++++++- .../luminosity_functions/models/modifiers.py | 22 ++- .../luminosity_functions/models/power_law.py | 137 +++++++++++++++++- .../luminosity_functions/models/schechter.py | 58 ++------ .../luminosity_functions/parameter_models.py | 134 ++++++++++------- .../luminosity_functions/redshift_density.py | 48 ++++-- src/lfkit/luminosity_functions/registry.py | 136 ++++++++++++++--- tests/test_api_luminosity_function.py | 2 +- 63 files changed, 883 insertions(+), 279 deletions(-) rename docs/api/{ => generated}/lfkit.api.conditional_luminosity_function.rst (100%) rename docs/api/{ => generated}/lfkit.api.corrections.rst (100%) rename docs/api/{ => generated}/lfkit.api.luminosity_function.rst (100%) rename docs/api/{ => generated}/lfkit.api.rst (100%) rename docs/api/{ => generated}/lfkit.corrections.color_anchors.rst (100%) rename docs/api/{ => generated}/lfkit.corrections.filters.rst (100%) rename docs/api/{ => generated}/lfkit.corrections.kcorrect_backend.rst (100%) rename docs/api/{ => generated}/lfkit.corrections.kcorrect_from_color.rst (100%) rename docs/api/{ => generated}/lfkit.corrections.kcorrect_grids.rst (100%) rename docs/api/{ => generated}/lfkit.corrections.poggianti1997.rst (100%) rename docs/api/{ => generated}/lfkit.corrections.responses.rst (100%) rename docs/api/{ => generated}/lfkit.corrections.rst (100%) rename docs/api/{ => generated}/lfkit.cosmo.cosmology.rst (100%) rename docs/api/{ => generated}/lfkit.cosmo.rst (100%) rename docs/api/{ => generated}/lfkit.luminosity_functions.completeness.rst (100%) rename docs/api/{ => generated}/lfkit.luminosity_functions.conditional_integrals.rst (100%) rename docs/api/{ => generated}/lfkit.luminosity_functions.conditional_models.rst (100%) rename docs/api/{ => generated}/lfkit.luminosity_functions.integrals.rst (100%) rename docs/api/{ => generated}/lfkit.luminosity_functions.models.composite.rst (100%) rename docs/api/{ => generated}/lfkit.luminosity_functions.models.gaussian.rst (100%) rename docs/api/{ => generated}/lfkit.luminosity_functions.models.modifiers.rst (100%) rename docs/api/{ => generated}/lfkit.luminosity_functions.models.power_law.rst (100%) rename docs/api/{ => generated}/lfkit.luminosity_functions.models.rst (100%) rename docs/api/{ => generated}/lfkit.luminosity_functions.models.schechter.rst (100%) rename docs/api/{ => generated}/lfkit.luminosity_functions.parameter_models.rst (100%) rename docs/api/{ => generated}/lfkit.luminosity_functions.redshift_density.rst (100%) rename docs/api/{ => generated}/lfkit.luminosity_functions.registry.rst (100%) rename docs/api/{ => generated}/lfkit.luminosity_functions.rst (100%) rename docs/api/{ => generated}/lfkit.photometry.luminosities.rst (100%) rename docs/api/{ => generated}/lfkit.photometry.magnitudes.rst (100%) rename docs/api/{ => generated}/lfkit.photometry.rst (100%) rename docs/api/{ => generated}/lfkit.rst (100%) rename docs/api/{ => generated}/lfkit.utils.download_poggianti97_data.rst (100%) rename docs/api/{ => generated}/lfkit.utils.evaluators.rst (100%) rename docs/api/{ => generated}/lfkit.utils.integrators.rst (100%) rename docs/api/{ => generated}/lfkit.utils.interpolation.rst (100%) rename docs/api/{ => generated}/lfkit.utils.io.rst (100%) rename docs/api/{ => generated}/lfkit.utils.rst (100%) rename docs/api/{ => generated}/lfkit.utils.types.rst (100%) rename docs/api/{ => generated}/lfkit.utils.units.rst (100%) rename docs/api/{ => generated}/lfkit.utils.validators.rst (100%) diff --git a/docs/about/photometry_overview.rst b/docs/about/photometry_overview.rst index 91557a07..89fad9ef 100644 --- a/docs/about/photometry_overview.rst +++ b/docs/about/photometry_overview.rst @@ -202,7 +202,7 @@ Here: - :math:`\alpha` is the faint-end slope, - :math:`x` is the luminosity ratio :math:`L/L_\star` written in magnitude form. -The parameter :math:`M_\star` marks the transition between the power-law part of +The parameter :math:`M_\star` marks the transition between the power law part of the luminosity function and the exponential bright-end cutoff. The parameter :math:`\alpha` controls how rapidly the abundance rises toward fainter magnitudes. More negative values of :math:`\alpha` produce a steeper faint end. diff --git a/docs/api/lfkit.api.conditional_luminosity_function.rst b/docs/api/generated/lfkit.api.conditional_luminosity_function.rst similarity index 100% rename from docs/api/lfkit.api.conditional_luminosity_function.rst rename to docs/api/generated/lfkit.api.conditional_luminosity_function.rst diff --git a/docs/api/lfkit.api.corrections.rst b/docs/api/generated/lfkit.api.corrections.rst similarity index 100% rename from docs/api/lfkit.api.corrections.rst rename to docs/api/generated/lfkit.api.corrections.rst diff --git a/docs/api/lfkit.api.luminosity_function.rst b/docs/api/generated/lfkit.api.luminosity_function.rst similarity index 100% rename from docs/api/lfkit.api.luminosity_function.rst rename to docs/api/generated/lfkit.api.luminosity_function.rst diff --git a/docs/api/lfkit.api.rst b/docs/api/generated/lfkit.api.rst similarity index 100% rename from docs/api/lfkit.api.rst rename to docs/api/generated/lfkit.api.rst diff --git a/docs/api/lfkit.corrections.color_anchors.rst b/docs/api/generated/lfkit.corrections.color_anchors.rst similarity index 100% rename from docs/api/lfkit.corrections.color_anchors.rst rename to docs/api/generated/lfkit.corrections.color_anchors.rst diff --git a/docs/api/lfkit.corrections.filters.rst b/docs/api/generated/lfkit.corrections.filters.rst similarity index 100% rename from docs/api/lfkit.corrections.filters.rst rename to docs/api/generated/lfkit.corrections.filters.rst diff --git a/docs/api/lfkit.corrections.kcorrect_backend.rst b/docs/api/generated/lfkit.corrections.kcorrect_backend.rst similarity index 100% rename from docs/api/lfkit.corrections.kcorrect_backend.rst rename to docs/api/generated/lfkit.corrections.kcorrect_backend.rst diff --git a/docs/api/lfkit.corrections.kcorrect_from_color.rst b/docs/api/generated/lfkit.corrections.kcorrect_from_color.rst similarity index 100% rename from docs/api/lfkit.corrections.kcorrect_from_color.rst rename to docs/api/generated/lfkit.corrections.kcorrect_from_color.rst diff --git a/docs/api/lfkit.corrections.kcorrect_grids.rst b/docs/api/generated/lfkit.corrections.kcorrect_grids.rst similarity index 100% rename from docs/api/lfkit.corrections.kcorrect_grids.rst rename to docs/api/generated/lfkit.corrections.kcorrect_grids.rst diff --git a/docs/api/lfkit.corrections.poggianti1997.rst b/docs/api/generated/lfkit.corrections.poggianti1997.rst similarity index 100% rename from docs/api/lfkit.corrections.poggianti1997.rst rename to docs/api/generated/lfkit.corrections.poggianti1997.rst diff --git a/docs/api/lfkit.corrections.responses.rst b/docs/api/generated/lfkit.corrections.responses.rst similarity index 100% rename from docs/api/lfkit.corrections.responses.rst rename to docs/api/generated/lfkit.corrections.responses.rst diff --git a/docs/api/lfkit.corrections.rst b/docs/api/generated/lfkit.corrections.rst similarity index 100% rename from docs/api/lfkit.corrections.rst rename to docs/api/generated/lfkit.corrections.rst diff --git a/docs/api/lfkit.cosmo.cosmology.rst b/docs/api/generated/lfkit.cosmo.cosmology.rst similarity index 100% rename from docs/api/lfkit.cosmo.cosmology.rst rename to docs/api/generated/lfkit.cosmo.cosmology.rst diff --git a/docs/api/lfkit.cosmo.rst b/docs/api/generated/lfkit.cosmo.rst similarity index 100% rename from docs/api/lfkit.cosmo.rst rename to docs/api/generated/lfkit.cosmo.rst diff --git a/docs/api/lfkit.luminosity_functions.completeness.rst b/docs/api/generated/lfkit.luminosity_functions.completeness.rst similarity index 100% rename from docs/api/lfkit.luminosity_functions.completeness.rst rename to docs/api/generated/lfkit.luminosity_functions.completeness.rst diff --git a/docs/api/lfkit.luminosity_functions.conditional_integrals.rst b/docs/api/generated/lfkit.luminosity_functions.conditional_integrals.rst similarity index 100% rename from docs/api/lfkit.luminosity_functions.conditional_integrals.rst rename to docs/api/generated/lfkit.luminosity_functions.conditional_integrals.rst diff --git a/docs/api/lfkit.luminosity_functions.conditional_models.rst b/docs/api/generated/lfkit.luminosity_functions.conditional_models.rst similarity index 100% rename from docs/api/lfkit.luminosity_functions.conditional_models.rst rename to docs/api/generated/lfkit.luminosity_functions.conditional_models.rst diff --git a/docs/api/lfkit.luminosity_functions.integrals.rst b/docs/api/generated/lfkit.luminosity_functions.integrals.rst similarity index 100% rename from docs/api/lfkit.luminosity_functions.integrals.rst rename to docs/api/generated/lfkit.luminosity_functions.integrals.rst diff --git a/docs/api/lfkit.luminosity_functions.models.composite.rst b/docs/api/generated/lfkit.luminosity_functions.models.composite.rst similarity index 100% rename from docs/api/lfkit.luminosity_functions.models.composite.rst rename to docs/api/generated/lfkit.luminosity_functions.models.composite.rst diff --git a/docs/api/lfkit.luminosity_functions.models.gaussian.rst b/docs/api/generated/lfkit.luminosity_functions.models.gaussian.rst similarity index 100% rename from docs/api/lfkit.luminosity_functions.models.gaussian.rst rename to docs/api/generated/lfkit.luminosity_functions.models.gaussian.rst diff --git a/docs/api/lfkit.luminosity_functions.models.modifiers.rst b/docs/api/generated/lfkit.luminosity_functions.models.modifiers.rst similarity index 100% rename from docs/api/lfkit.luminosity_functions.models.modifiers.rst rename to docs/api/generated/lfkit.luminosity_functions.models.modifiers.rst diff --git a/docs/api/lfkit.luminosity_functions.models.power_law.rst b/docs/api/generated/lfkit.luminosity_functions.models.power_law.rst similarity index 100% rename from docs/api/lfkit.luminosity_functions.models.power_law.rst rename to docs/api/generated/lfkit.luminosity_functions.models.power_law.rst diff --git a/docs/api/lfkit.luminosity_functions.models.rst b/docs/api/generated/lfkit.luminosity_functions.models.rst similarity index 100% rename from docs/api/lfkit.luminosity_functions.models.rst rename to docs/api/generated/lfkit.luminosity_functions.models.rst diff --git a/docs/api/lfkit.luminosity_functions.models.schechter.rst b/docs/api/generated/lfkit.luminosity_functions.models.schechter.rst similarity index 100% rename from docs/api/lfkit.luminosity_functions.models.schechter.rst rename to docs/api/generated/lfkit.luminosity_functions.models.schechter.rst diff --git a/docs/api/lfkit.luminosity_functions.parameter_models.rst b/docs/api/generated/lfkit.luminosity_functions.parameter_models.rst similarity index 100% rename from docs/api/lfkit.luminosity_functions.parameter_models.rst rename to docs/api/generated/lfkit.luminosity_functions.parameter_models.rst diff --git a/docs/api/lfkit.luminosity_functions.redshift_density.rst b/docs/api/generated/lfkit.luminosity_functions.redshift_density.rst similarity index 100% rename from docs/api/lfkit.luminosity_functions.redshift_density.rst rename to docs/api/generated/lfkit.luminosity_functions.redshift_density.rst diff --git a/docs/api/lfkit.luminosity_functions.registry.rst b/docs/api/generated/lfkit.luminosity_functions.registry.rst similarity index 100% rename from docs/api/lfkit.luminosity_functions.registry.rst rename to docs/api/generated/lfkit.luminosity_functions.registry.rst diff --git a/docs/api/lfkit.luminosity_functions.rst b/docs/api/generated/lfkit.luminosity_functions.rst similarity index 100% rename from docs/api/lfkit.luminosity_functions.rst rename to docs/api/generated/lfkit.luminosity_functions.rst diff --git a/docs/api/lfkit.photometry.luminosities.rst b/docs/api/generated/lfkit.photometry.luminosities.rst similarity index 100% rename from docs/api/lfkit.photometry.luminosities.rst rename to docs/api/generated/lfkit.photometry.luminosities.rst diff --git a/docs/api/lfkit.photometry.magnitudes.rst b/docs/api/generated/lfkit.photometry.magnitudes.rst similarity index 100% rename from docs/api/lfkit.photometry.magnitudes.rst rename to docs/api/generated/lfkit.photometry.magnitudes.rst diff --git a/docs/api/lfkit.photometry.rst b/docs/api/generated/lfkit.photometry.rst similarity index 100% rename from docs/api/lfkit.photometry.rst rename to docs/api/generated/lfkit.photometry.rst diff --git a/docs/api/lfkit.rst b/docs/api/generated/lfkit.rst similarity index 100% rename from docs/api/lfkit.rst rename to docs/api/generated/lfkit.rst diff --git a/docs/api/lfkit.utils.download_poggianti97_data.rst b/docs/api/generated/lfkit.utils.download_poggianti97_data.rst similarity index 100% rename from docs/api/lfkit.utils.download_poggianti97_data.rst rename to docs/api/generated/lfkit.utils.download_poggianti97_data.rst diff --git a/docs/api/lfkit.utils.evaluators.rst b/docs/api/generated/lfkit.utils.evaluators.rst similarity index 100% rename from docs/api/lfkit.utils.evaluators.rst rename to docs/api/generated/lfkit.utils.evaluators.rst diff --git a/docs/api/lfkit.utils.integrators.rst b/docs/api/generated/lfkit.utils.integrators.rst similarity index 100% rename from docs/api/lfkit.utils.integrators.rst rename to docs/api/generated/lfkit.utils.integrators.rst diff --git a/docs/api/lfkit.utils.interpolation.rst b/docs/api/generated/lfkit.utils.interpolation.rst similarity index 100% rename from docs/api/lfkit.utils.interpolation.rst rename to docs/api/generated/lfkit.utils.interpolation.rst diff --git a/docs/api/lfkit.utils.io.rst b/docs/api/generated/lfkit.utils.io.rst similarity index 100% rename from docs/api/lfkit.utils.io.rst rename to docs/api/generated/lfkit.utils.io.rst diff --git a/docs/api/lfkit.utils.rst b/docs/api/generated/lfkit.utils.rst similarity index 100% rename from docs/api/lfkit.utils.rst rename to docs/api/generated/lfkit.utils.rst diff --git a/docs/api/lfkit.utils.types.rst b/docs/api/generated/lfkit.utils.types.rst similarity index 100% rename from docs/api/lfkit.utils.types.rst rename to docs/api/generated/lfkit.utils.types.rst diff --git a/docs/api/lfkit.utils.units.rst b/docs/api/generated/lfkit.utils.units.rst similarity index 100% rename from docs/api/lfkit.utils.units.rst rename to docs/api/generated/lfkit.utils.units.rst diff --git a/docs/api/lfkit.utils.validators.rst b/docs/api/generated/lfkit.utils.validators.rst similarity index 100% rename from docs/api/lfkit.utils.validators.rst rename to docs/api/generated/lfkit.utils.validators.rst diff --git a/docs/api/index.rst b/docs/api/index.rst index fd4af68f..bd31fbc1 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -2,15 +2,7 @@ API reference ============= .. autosummary:: - :toctree: + :toctree: generated :recursive: - lfkit - lfkit.api - lfkit.api.luminosity_function - lfkit.api.conditional_luminosity_function - lfkit.api.corrections - lfkit.luminosity_functions - lfkit.photometry - lfkit.corrections - lfkit.cosmo \ No newline at end of file + lfkit \ No newline at end of file diff --git a/docs/examples/conditional_luminosity_function.rst b/docs/examples/conditional_luminosity_function.rst index 0a116ca1..6f1e5181 100644 --- a/docs/examples/conditional_luminosity_function.rst +++ b/docs/examples/conditional_luminosity_function.rst @@ -25,7 +25,7 @@ mass, or another quantity. LFKit exposes conditional luminosity functions through :class:`lfkit.ConditionalLuminosityFunction`. Each constructor returns a -luminosity-function object that can be evaluated with +luminosity function object that can be evaluated with ``lf.phi`` and integrated through the usual ``lf.integrals`` namespace. The examples below use redshift as the conditioning variable because this is @@ -127,7 +127,7 @@ population evolves. Conditional Schechter surface ----------------------------- -The same conditional Schechter model can be evaluated over the full +The same conditional luminosity function model can be evaluated over the full magnitude-redshift plane rather than at a few selected redshifts. The filled colour scale shows :math:`\log_{10}\Phi(M \mid z)`. The contours @@ -423,7 +423,7 @@ It is useful to compare the component shapes at fixed condition before combining them into more complicated models. This separates differences in functional form from differences caused by redshift or halo-mass evolution. -The standard Schechter form has a power-law faint end and an exponential +The standard Schechter form has a power law faint end and an exponential bright-end cutoff. The modified Schechter component has a broader bright-end shape. The lognormal component is localized around a characteristic luminosity. Together, these examples show the kinds of galaxy populations each component is diff --git a/docs/examples/lf_models/composite_models.rst b/docs/examples/lf_models/composite_models.rst index 1d450a4e..e6a781de 100644 --- a/docs/examples/lf_models/composite_models.rst +++ b/docs/examples/lf_models/composite_models.rst @@ -5,7 +5,7 @@ |lfkitlogo| Composite models ============================= -Composite models combine multiple luminosity-function components. They are +Composite models combine multiple luminosity function components. They are useful when a population is better described as a mixture rather than by one single analytic shape. @@ -83,7 +83,7 @@ function with a suppressed bright end. Luminosity cutoff modifier -------------------------- -A luminosity cutoff can be applied to an existing luminosity-function object. +A luminosity cutoff can be applied to an existing luminosity function object. This keeps the base model unchanged and returns a new object whose :meth:`lfkit.LuminosityFunction.phi` values are multiplied by a bright-end cutoff. diff --git a/docs/examples/lf_models/power_law_models.rst b/docs/examples/lf_models/power_law_models.rst index eb14b8cf..ebaf8e8e 100644 --- a/docs/examples/lf_models/power_law_models.rst +++ b/docs/examples/lf_models/power_law_models.rst @@ -13,7 +13,7 @@ a Schechter-like exponential cutoff. Single power law ---------------- -The single power-law model has one slope. In magnitude space, this gives a +The single power law model has one slope. In magnitude space, this gives a straight-line trend when shown on a logarithmic :math:`\Phi(M)` axis. .. plot:: @@ -58,7 +58,7 @@ straight-line trend when shown on a logarithmic :math:`\Phi(M)` axis. r"$\Phi(M)$ [$\mathrm{Mpc}^{-3}\,\mathrm{mag}^{-1}$]", fontsize=LABEL_SIZE, ) - ax.set_title("Single power-law luminosity functions", fontsize=TITLE_SIZE) + ax.set_title("Single power law luminosity functions", fontsize=TITLE_SIZE) ax.tick_params(axis="both", labelsize=TICK_SIZE) ax.legend(frameon=True, fontsize=LEGEND_SIZE, loc="best") plt.tight_layout() @@ -148,7 +148,7 @@ and faint sides need different behaviour without using a Schechter cutoff. Log-normalized power law ------------------------ -The log-normalized power-law model is the same power-law shape, but the +The log-normalized power law model is the same power law shape, but the normalization is supplied as ``log_phi_star``. This is convenient for fitting or sampling applications where the normalization is naturally varied in logarithmic space. @@ -199,7 +199,7 @@ logarithmic space. r"$\Phi(M)$ [$\mathrm{Mpc}^{-3}\,\mathrm{mag}^{-1}$]", fontsize=LABEL_SIZE, ) - ax.set_title("Log-normalized power-law models", fontsize=TITLE_SIZE) + ax.set_title("Log-normalized power law models", fontsize=TITLE_SIZE) ax.tick_params(axis="both", labelsize=TICK_SIZE) ax.legend(frameon=True, fontsize=LEGEND_SIZE, loc="best") plt.tight_layout() diff --git a/docs/examples/lf_models/schechter_models.rst b/docs/examples/lf_models/schechter_models.rst index bedf572d..16629abf 100644 --- a/docs/examples/lf_models/schechter_models.rst +++ b/docs/examples/lf_models/schechter_models.rst @@ -5,7 +5,7 @@ |lfkitlogo| Schechter family models =================================== -The Schechter family contains luminosity functions with a power-law faint end +The Schechter family contains luminosity functions with a power law faint end and a bright-end cutoff. These models are commonly used when the abundance of faint galaxies rises approximately as a power law, while the most luminous galaxies are exponentially rare. diff --git a/src/lfkit/api/_namespaces.py b/src/lfkit/api/_namespaces.py index cb6947e8..cb3b340b 100644 --- a/src/lfkit/api/_namespaces.py +++ b/src/lfkit/api/_namespaces.py @@ -1,4 +1,4 @@ -"""User-facing luminosity-function API namespaces.""" +"""User-facing luminosity function API namespaces.""" from __future__ import annotations @@ -14,28 +14,43 @@ class LFIntegralsAPI: - """Grouped API for luminosity function integrals.""" + """Grouped API for luminosity function integrals. + + Args: + lf: Luminosity function object whose callable form is used by bound + integral methods. + """ def __init__(self, lf) -> None: self.lf = lf class LFCompletenessAPI: - """Grouped API for catalog-completeness calculations.""" + """Grouped API for catalog completeness calculations. + + Args: + lf: Luminosity function object whose callable form is used by bound + completeness methods. + """ def __init__(self, lf) -> None: self.lf = lf class LFRedshiftDensityAPI: - """Grouped API for LF-weighted redshift-density calculations.""" + """Grouped API for luminosity function weighted redshift density calculations. + + Args: + lf: Luminosity function object whose callable form is used by bound + redshift density methods. + """ def __init__(self, lf) -> None: self.lf = lf class LFMagnitudesAPI: - """Grouped API for apparent- and absolute-magnitude conversions.""" + """Grouped API for apparent magnitude and absolute magnitude conversions.""" class LFLuminositiesAPI: @@ -76,7 +91,20 @@ def expose_lf_function( lf_arg_position: int | None = None, lf_arg_name: str | None = None, ) -> Callable[..., Any]: - """Expose a low-level LF function as a bound API method.""" + """Expose a low level luminosity function helper as a bound API method. + + Args: + function: Low level function to expose as a method. + lf_arg_position: Positional index where the luminosity function callable + should be inserted. If ``None``, no positional luminosity function + argument is inserted. + lf_arg_name: Keyword name used to pass the luminosity function callable. + If provided, this takes priority over ``lf_arg_position``. + + Returns: + Bound method that injects ``self.lf._as_callable()`` before calling + ``function``. + """ @wraps(function) def method(self, *args, **kwargs): @@ -97,7 +125,14 @@ def method(self, *args, **kwargs): def _public_functions(module: object) -> dict[str, Callable[..., Any]]: - """Return callable public functions declared by a module.""" + """Return callable public functions declared by a module. + + Args: + module: Module object with an optional ``__all__`` declaration. + + Returns: + Dictionary mapping public function names to callable objects. + """ return { name: getattr(module, name) for name in getattr(module, "__all__", []) @@ -106,13 +141,21 @@ def _public_functions(module: object) -> dict[str, Callable[..., Any]]: def _method_name(module: object, function_name: str) -> str: - """Return API method name for a low-level function.""" + """Return the API method name for a low level function. + + Args: + module: Module object that may define ``__api_aliases__``. + function_name: Name of the low level function. + + Returns: + Public API method name, using ``__api_aliases__`` when available. + """ aliases = getattr(module, "__api_aliases__", {}) return aliases.get(function_name, function_name) def _attach_api_methods() -> None: - """Attach low-level functions to their API namespace classes.""" + """Attach low level functions to their API namespace classes.""" for api_cls, spec in _API_NAMESPACES.items(): module = spec["module"] bound_to_lf = spec.get("bound_to_lf", False) diff --git a/src/lfkit/api/conditional_luminosity_function.py b/src/lfkit/api/conditional_luminosity_function.py index f6478bc9..8a0a9776 100644 --- a/src/lfkit/api/conditional_luminosity_function.py +++ b/src/lfkit/api/conditional_luminosity_function.py @@ -21,21 +21,13 @@ class ConditionalLuminosityFunction(LuminosityFunction): """User-facing wrapper for conditional luminosity function models. - A conditional luminosity function evaluates ``Phi(M | x)``, where - ``M`` is absolute magnitude and ``x`` is an external conditioning - variable such as redshift, halo mass, or another model-specific quantity. + A conditional luminosity function evaluates ``Phi(M | x)``, where ``M`` is + absolute magnitude and ``x`` is an external conditioning variable such as + redshift, halo mass, environment, richness, stellar mass, or another + model-specific quantity. Instances can be created either with the generic constructor or with automatically generated model constructors. - - Examples: - >>> clf = ConditionalLuminosityFunction( - ... model="schechter", - ... parameters={"phi_star": 1e-3, "m_star": -20.5, "alpha": -1.1}, - ... ) - >>> phi = clf.phi(absolute_mag=-20.0, condition=0.5) - - >>> ConditionalLuminosityFunction.available_models() """ def phi( @@ -47,16 +39,16 @@ def phi( Args: absolute_mag: Absolute magnitude value or array. - condition: Conditioning variable value or array. The meaning of - this variable depends on the selected conditional LF model. + condition: Conditioning variable value or array. The meaning of this + variable depends on the selected conditional luminosity function model. Returns: Conditional luminosity function evaluated at ``absolute_mag`` and ``condition``. Raises: - ValueError: If ``condition`` is not provided or the model is not - registered as a conditional luminosity function. + ValueError: If ``condition`` is not provided or the model is not registered + as a conditional luminosity function. """ model_spec = get_conditional_lf_model(self.model) @@ -74,7 +66,11 @@ def phi( @staticmethod def available_models() -> list[str]: - """Return conditional luminosity function model names.""" + """Return registered conditional luminosity function model names. + + Returns: + Sorted list of registered conditional luminosity function model names. + """ return sorted(CONDITIONAL_LF_MODELS) @@ -83,7 +79,15 @@ def _make_conditional_constructor( model_name: str, function: Any, ): - """Create a classmethod constructor from a registered conditional LF model.""" + """Create a classmethod constructor from a registered conditional model. + + Args: + model_name: Name of the registered conditional luminosity function model. + function: Registered low level conditional luminosity function callable. + + Returns: + Classmethod constructor for ``ConditionalLuminosityFunction``. + """ signature = inspect.signature(function) @classmethod @@ -106,22 +110,23 @@ def constructor( constructor.__name__ = model_name constructor.__qualname__ = f"ConditionalLuminosityFunction.{model_name}" constructor.__doc__ = f"""Create a ``{model_name}`` conditional luminosity function. - -The keyword arguments are inferred from the registered low-level model -function. Required model parameters must be supplied by the user. Optional -model parameters use their low-level defaults unless explicitly provided. - -Args: - meta: Optional metadata stored on the luminosity function object. - **parameters: Parameters passed to the registered conditional LF model. - -Returns: - ConditionalLuminosityFunction: Configured conditional luminosity function. - -Examples: - >>> clf = ConditionalLuminosityFunction.{model_name}(...) - >>> phi = clf.phi(absolute_mag=-20.0, condition=0.5) -""" + + The keyword arguments are inferred from the registered low level model + function. Required model parameters must be supplied by the user. Optional + model parameters use their low level defaults unless explicitly provided. + + Args: + meta: Optional metadata stored on the luminosity function object. + **parameters: Parameters passed to the registered conditional luminosity + function model. + + Returns: + Configured conditional luminosity function. + + Examples: + >>> clf = ConditionalLuminosityFunction.{model_name}(...) + >>> phi = clf.phi(absolute_mag=-20.0, condition=0.5) + """ return constructor @@ -131,22 +136,23 @@ def _parameters_from_signature( signature: inspect.Signature, provided: Mapping[str, Any], ) -> dict[str, Any]: - """Build stored parameters using function defaults plus user values. + """Build stored parameters from a function signature and user values. - Independent variables such as ``absolute_mag`` and ``condition`` are not - stored as model parameters. They are supplied later when calling + Independent variables such as ``absolute_mag`` and ``condition`` are not stored + as model parameters. They are supplied later when calling :meth:`ConditionalLuminosityFunction.phi`. Args: - signature: Signature of the registered low-level conditional LF model. + signature: Signature of the registered low level conditional luminosity + function model. provided: User-supplied constructor keyword arguments. Returns: - Dictionary of model parameters to store on the API object. + Dictionary of model parameters stored on the API object. Raises: TypeError: If the user provides a keyword that is not accepted by the - registered low-level model function. + registered low level model function. """ payload: dict[str, Any] = {} diff --git a/src/lfkit/api/luminosity_function.py b/src/lfkit/api/luminosity_function.py index e7042022..31d6442d 100644 --- a/src/lfkit/api/luminosity_function.py +++ b/src/lfkit/api/luminosity_function.py @@ -46,29 +46,14 @@ class LuminosityFunction: """User-facing wrapper for luminosity function evaluation. - A luminosity function describes the number density of galaxies as a - function of absolute magnitude. This class stores a registered LF model and - its parameters, then exposes a consistent user-facing interface for model - evaluation, apparent-magnitude evaluation, integrals, completeness - calculations, redshift-density calculations, and magnitude/luminosity - conversions. + A luminosity function describes the number density of galaxies as a function of + absolute magnitude. This class stores a registered model and its parameters, + then exposes a consistent interface for model evaluation, apparent magnitude + evaluation, integrals, completeness calculations, redshift density + calculations, and magnitude or luminosity conversions. Instances can be created either with the generic constructor or with automatically generated model constructors. - - Examples: - >>> lf = LuminosityFunction( - ... model="schechter", - ... parameters={"phi_star": 1e-3, "m_star": -20.5, "alpha": -1.1}, - ... ) - >>> phi = lf.phi(absolute_mag=-20.0) - - >>> lf = LuminosityFunction.schechter( - ... phi_star=1e-3, - ... m_star=-20.5, - ... alpha=-1.1, - ... ) - >>> phi = lf.phi(absolute_mag=[-21.0, -20.0, -19.0]) """ def __init__( @@ -82,7 +67,7 @@ def __init__( Args: model: Name of a registered luminosity function model. - parameters: Model parameters passed to the registered LF function. + parameters: Model parameters passed to the registered model function. meta: Optional metadata stored on the luminosity function object. """ self.model = str(model) @@ -100,21 +85,20 @@ def phi( absolute_mag: FloatInput, z: FloatInput | None = None, ) -> FloatArray: - """Evaluate the luminosity function in absolute-magnitude space. + """Evaluate the luminosity function in absolute magnitude space. Args: absolute_mag: Absolute magnitude value or array. - z: Optional redshift value or array. This is required only for - registered models whose parameters evolve with redshift. + z: Optional redshift value or array. This is required only for registered + models whose parameters evolve with redshift. Returns: - Luminosity function evaluated at ``absolute_mag``. For redshift- - dependent models, the result is evaluated at ``absolute_mag`` and - ``z``. + Luminosity function evaluated at ``absolute_mag``. For redshift dependent + models, the result is evaluated at ``absolute_mag`` and ``z``. Raises: - ValueError: If the model is not registered, or if ``z`` is required - by the selected model but not provided. + ValueError: If the model is not registered, or if ``z`` is required by the + selected model but not provided. """ model_spec = get_lf_model(self.model) @@ -149,26 +133,25 @@ def phi_from_m( h: float | None = None, corrections: Corrections | None = None, ) -> FloatArray: - """Evaluate the luminosity function from apparent magnitudes. + """Evaluate the luminosity function from apparent magnitude values. - This converts apparent magnitude to absolute magnitude using the - supplied cosmology and redshift, then evaluates the registered LF model. + This converts apparent magnitude to absolute magnitude using the supplied + cosmology and redshift, then evaluates the registered model. Args: cosmo_obj: Cosmology object used for the distance conversion. z: Redshift value or array. apparent_mag: Apparent magnitude value or array. h: Optional dimensionless Hubble parameter override. - corrections: Optional correction object with ``k(z)`` and ``e(z)`` - methods. + corrections: Optional correction object with ``k(z)`` and ``e(z)`` methods. Returns: - Luminosity function evaluated at the absolute magnitudes implied by + Luminosity function evaluated at the absolute magnitude values implied by ``apparent_mag`` and ``z``. Raises: - ValueError: If the selected LF model does not provide a - ``phi_from_m`` evaluator. + ValueError: If the selected model does not provide a ``phi_from_m`` + evaluator. """ function = get_lf_from_m_model(self.model) @@ -219,7 +202,23 @@ def with_luminosity_cutoff( cutoff_amplitude: float = 1.0, meta: Mapping[str, object] | None = None, ) -> LuminosityFunction: - """Return a copy of this luminosity function with a luminosity cutoff.""" + """Return a copy of this luminosity function with a luminosity cutoff. + + Args: + m_star: Characteristic magnitude used to define the luminosity ratio. If + not provided, ``self.parameters_dict["m_star"]`` is used when present. + cutoff_power: Power applied to the luminosity ratio in the exponential + cutoff. + cutoff_amplitude: Amplitude multiplying the cutoff term. + meta: Optional metadata merged into the returned luminosity function. + + Returns: + New luminosity function object whose ``phi`` method includes the cutoff. + + Raises: + ValueError: If ``m_star`` is not supplied and the base model does not store + an ``m_star`` parameter. + """ cutoff_m_star = ( self.parameters_dict["m_star"] if m_star is None and "m_star" in self.parameters_dict @@ -252,12 +251,20 @@ def modified_phi( @staticmethod def available_models() -> list[str]: - """Return luminosity function model names available through the API.""" + """Return luminosity function model names available through the API. + + Returns: + Sorted list of registered luminosity function model names. + """ return sorted(LF_MODELS) @staticmethod def available_from_m_models() -> list[str]: - """Return models that support apparent-magnitude evaluation.""" + """Return models that support apparent magnitude evaluation. + + Returns: + Sorted list of registered models with ``phi_from_m`` evaluators. + """ return sorted(LF_FROM_M_MODELS) @staticmethod @@ -281,7 +288,7 @@ def register_phi_star_model( Args: name: Name used to identify the parameter model. - model: Callable or parameter model object. + model: Callable parameter model. overwrite: Whether to replace an existing model with the same name. """ register_phi_star_model(name, model, overwrite=overwrite) @@ -297,7 +304,7 @@ def register_m_star_model( Args: name: Name used to identify the parameter model. - model: Callable or parameter model object. + model: Callable parameter model. overwrite: Whether to replace an existing model with the same name. """ register_m_star_model(name, model, overwrite=overwrite) @@ -313,7 +320,7 @@ def register_alpha_model( Args: name: Name used to identify the parameter model. - model: Callable or parameter model object. + model: Callable parameter model. overwrite: Whether to replace an existing model with the same name. """ register_alpha_model(name, model, overwrite=overwrite) @@ -323,7 +330,16 @@ def _correction_values( corrections: Corrections | None, z: FloatInput, ) -> tuple[FloatArray | None, FloatArray | None]: - """Evaluate optional correction values at redshift.""" + """Evaluate optional correction values at redshift. + + Args: + corrections: Optional correction object with ``k(z)`` and ``e(z)`` methods. + z: Redshift value or array. + + Returns: + Tuple ``(k_correction, e_correction)`` evaluated at ``z``. If no correction + object is supplied, returns ``(None, None)``. + """ if corrections is None: return None, None @@ -331,7 +347,14 @@ def _correction_values( def _make_lf_constructor(model_name: str): - """Create a classmethod constructor for a registered LF model.""" + """Create a classmethod constructor for a registered luminosity function model. + + Args: + model_name: Name of the registered luminosity function model. + + Returns: + Classmethod constructor for ``LuminosityFunction``. + """ @classmethod def constructor( @@ -351,16 +374,16 @@ def constructor( constructor.__qualname__ = f"LuminosityFunction.{model_name}" constructor.__doc__ = f"""Create a ``{model_name}`` luminosity function. -The keyword arguments are passed to the registered low-level LF model. +The keyword arguments are passed to the registered low level model function. Required model parameters must be supplied by the user. Optional model -parameters use their low-level defaults unless explicitly provided. +parameters use their low level defaults unless explicitly provided. Args: meta: Optional metadata stored on the luminosity function object. - **parameters: Parameters passed to the registered LF model. + **parameters: Parameters passed to the registered luminosity function model. Returns: - LuminosityFunction: Configured luminosity function. + Configured luminosity function. Examples: >>> lf = LuminosityFunction.{model_name}(...) @@ -373,8 +396,8 @@ def constructor( def _clean_parameters(parameters: Mapping[str, Any]) -> dict[str, Any]: """Normalize constructor keyword arguments before storing them. - ``None`` values for keyword arguments ending in ``"_kwargs"`` are converted - to empty dictionaries. This keeps optional nested configuration arguments + ``None`` values for keyword arguments ending in ``"_kwargs"`` are converted to + empty dictionaries. This keeps optional nested configuration arguments convenient for users while storing a predictable parameter dictionary. Args: diff --git a/src/lfkit/luminosity_functions/_discovery.py b/src/lfkit/luminosity_functions/_discovery.py index e75a7fb0..d17288db 100644 --- a/src/lfkit/luminosity_functions/_discovery.py +++ b/src/lfkit/luminosity_functions/_discovery.py @@ -1,4 +1,4 @@ -"""Shared luminosity-function discovery helpers.""" +"""Shared luminosity function discovery helpers.""" from __future__ import annotations @@ -15,7 +15,11 @@ def iter_model_functions() -> dict[str, Callable]: - """Return public callable LF models discovered from ``models/``.""" + """Return public callable luminosity function models discovered from ``models/``. + + Returns: + Dictionary mapping exported function names to callable model functions. + """ functions: dict[str, Callable] = {} for module_info in pkgutil.iter_modules(models_pkg.__path__): diff --git a/src/lfkit/luminosity_functions/conditional_integrals.py b/src/lfkit/luminosity_functions/conditional_integrals.py index b6057198..f1d9eb8c 100644 --- a/src/lfkit/luminosity_functions/conditional_integrals.py +++ b/src/lfkit/luminosity_functions/conditional_integrals.py @@ -9,7 +9,7 @@ module does not implement HOD or halo-model machinery. The goal is to support conditional luminosity function evaluation and -integration while keeping halo-model calculations outside LFKit. +integration while keeping halo model calculations outside LFKit. """ from __future__ import annotations @@ -46,9 +46,8 @@ def evaluate_conditional_luminosity_function( absolute magnitudes and conditioning values. Raises: - ValueError: If the inputs contain non-finite values, or if the - evaluated conditional luminosity function contains non-finite or - negative values. + ValueError: If the inputs contain non-finite values, or if the evaluated + conditional luminosity function contains non-finite or negative values. """ absolute_mag_arr = validate_array(absolute_mag, name="absolute_mag") condition_arr = validate_array(condition, name="condition") @@ -86,8 +85,8 @@ def integrate_conditional_luminosity_function( Conditional luminosity function integrated over absolute magnitude. Raises: - ValueError: If the inputs contain invalid values, or if the evaluated - conditional luminosity function contains invalid values. + ValueError: If the inputs contain non-finite values, or if the evaluated + conditional luminosity function contains non-finite or negative values. """ absolute_mag_arr = validate_array(absolute_mag, name="absolute_mag") @@ -127,8 +126,9 @@ def integrate_weighted_conditional_luminosity_function( magnitude. Raises: - ValueError: If the inputs contain invalid values, or if the evaluated - conditional luminosity function or weights contain invalid values. + ValueError: If the inputs contain non-finite values, if the evaluated + conditional luminosity function contains non-finite or negative values, + or if the weights contain non-finite values. """ absolute_mag_arr = validate_array(absolute_mag, name="absolute_mag") condition_arr = validate_array(condition, name="condition") diff --git a/src/lfkit/luminosity_functions/conditional_models.py b/src/lfkit/luminosity_functions/conditional_models.py index 93f2ed9b..426d6cd5 100644 --- a/src/lfkit/luminosity_functions/conditional_models.py +++ b/src/lfkit/luminosity_functions/conditional_models.py @@ -4,7 +4,9 @@ function models. A conditional luminosity function has the form ``Phi(M | x)``, where ``M`` is -absolute magnitude and ``x`` is an external conditioning variable. +absolute magnitude and ``x`` is an external conditioning variable. Callable +model parameters are evaluated at ``x`` before the wrapped luminosity function +is evaluated. """ from __future__ import annotations @@ -23,10 +25,18 @@ def conditionalize_lf_model( lf_model: Callable[..., FloatArray], ) -> Callable[..., FloatArray]: - """Return a conditional version of an LF model. + """Return a conditional version of a luminosity function model. - Keyword arguments that are callable are evaluated as functions of - ``condition``. Non-callable keyword arguments are passed through unchanged. + Callable keyword arguments are interpreted as parameter models and evaluated as + functions of ``condition``. Non-callable keyword arguments are passed through + unchanged. + + Args: + lf_model: Luminosity function model to wrap. + + Returns: + Conditional luminosity function model with signature + ``conditional_model(absolute_mag, condition, **kwargs)``. """ @wraps(lf_model) @@ -54,7 +64,14 @@ def conditional_model( def _conditional_model_name(name: str) -> str: - """Return conditional wrapper name for an LF model.""" + """Return the generated conditional wrapper name for a luminosity function model. + + Args: + name: Base luminosity function model name. + + Returns: + Conditional model name prefixed with ``"conditional_"``. + """ return f"conditional_{name}" @@ -63,7 +80,18 @@ def _validate_lf_output( *, name: str, ) -> FloatArray: - """Validate luminosity function model output.""" + """Validate luminosity function model output. + + Args: + phi: Luminosity function values returned by a model. + name: Model name used in validation errors. + + Returns: + Validated luminosity function values as a float array. + + Raises: + ValueError: If any luminosity function value is negative. + """ phi_arr = validate_array(phi, name=name) if np.any(phi_arr < 0.0): diff --git a/src/lfkit/luminosity_functions/integrals.py b/src/lfkit/luminosity_functions/integrals.py index fa5612fa..9c3254bc 100644 --- a/src/lfkit/luminosity_functions/integrals.py +++ b/src/lfkit/luminosity_functions/integrals.py @@ -89,6 +89,10 @@ def _bind_lf( Returns: Callable with signature ``lf(absolute_mag, z)``. + Raises: + TypeError: If ``model_fn`` cannot be called with the supplied + parameters. + Examples: >>> lf = _bind_lf( ... evolving_schechter, @@ -127,12 +131,16 @@ def _bind_static_lf( evolving luminosity functions use the same integration API. Args: - model_fn: Redshift-independent luminosity-function model callable. + model_fn: Redshift-independent luminosity function model callable. **params: Parameters passed to ``model_fn`` every time it is evaluated. Returns: Callable with signature ``lf(absolute_mag, z)``. + Raises: + TypeError: If ``model_fn`` cannot be called with the supplied + parameters. + Examples: >>> lf = _bind_static_lf( ... schechter, @@ -177,6 +185,10 @@ def integrated_number_density( Returns: NumPy array of number densities evaluated at ``z``. + + Raises: + ValueError: If redshift values are negative or if the magnitude + bounds are invalid. """ return _integrated_lf_between_bounds( z, @@ -196,7 +208,7 @@ def lf_weighted_integral( weight_fn: Callable[[FloatArray, FloatArray], FloatArray], n_m: int = 512, ) -> FloatArray: - r"""Return a weighted luminosity-function integral. + r"""Return a weighted luminosity function integral. This computes @@ -216,6 +228,10 @@ def lf_weighted_integral( Returns: NumPy array of weighted LF integrals evaluated at ``z``. + + Raises: + ValueError: If redshift values are negative, if the magnitude + bounds are invalid, or if ``weight_fn`` returns invalid values. """ return _integrated_lf_between_bounds( z, @@ -258,6 +274,10 @@ def selection_weighted_number_density( Returns: NumPy array of selection-weighted number densities evaluated at ``z``. + + Raises: + ValueError: If redshift values are negative, if the magnitude + bounds are invalid, or if ``selection_fn`` returns invalid values. """ return lf_weighted_integral( z, @@ -292,6 +312,9 @@ def luminosity_weight( Returns: NumPy array of relative luminosity weights. + + Raises: + ValueError: If ``m_reference`` is not finite. """ if not np.isfinite(m_reference): raise ValueError("m_reference must be finite.") @@ -340,6 +363,10 @@ def integrated_luminosity_density( Returns: NumPy array of luminosity densities in units of the reference luminosity. + + Raises: + ValueError: If ``m_reference`` is not finite, if redshift values are + negative, or if the magnitude bounds are invalid. """ def weight_fn( @@ -395,6 +422,10 @@ def mean_luminosity( Returns: NumPy array of mean luminosities. Entries are zero where the integrated number density is zero. + + Raises: + ValueError: If ``m_reference`` is not finite, if redshift values are + negative, or if the magnitude bounds are invalid. """ luminosity_density_arr = integrated_luminosity_density( z, @@ -455,6 +486,10 @@ def cumulative_number_density( Returns: NumPy array of cumulative number densities evaluated at ``z``. + + Raises: + ValueError: If redshift values are negative or if the magnitude + bounds are invalid. """ z_arr = validate_array(z, name="z") m_threshold_arr = validate_array(m_threshold, name="m_threshold") @@ -510,7 +545,7 @@ def selection_fraction( }. This is the generic numerical analogue of model-specific analytic - selection functions. It works for any luminosity-function callable with + selection functions. It works for any luminosity function callable with signature ``lf(M, z)``. Args: @@ -525,6 +560,10 @@ def selection_fraction( Returns: NumPy array of selected fractions. Entries are zero where the total number density is zero. + + Raises: + ValueError: If redshift values are negative or if any magnitude + window is invalid. """ selected = integrated_number_density( z, @@ -599,6 +638,10 @@ def cumulative_selection_function( Returns: NumPy array of cumulative selection fractions. Entries are zero where the total number density is zero. + + Raises: + ValueError: If redshift values are negative or if the supplied + magnitude limits are invalid. """ selected = cumulative_number_density( z, @@ -709,7 +752,25 @@ def _resolve_magnitude_window_bound( e_correction_fn: Callable[[FloatArray], FloatArray] | None, bound_name: str, ) -> FloatArray: - r"""Return an absolute magnitude bound for a magnitude window.""" + r"""Return an absolute magnitude bound for a magnitude window. + + Args: + z: Redshift values. + absolute_mag: Absolute magnitude bound. + apparent_mag: Apparent magnitude bound. + luminosity_distance_mpc_fn: Callable returning luminosity distance in Mpc. + k_correction_fn: Optional K-correction callable. + e_correction_fn: Optional E-correction callable. + bound_name: Human-readable bound name used in error messages. + + Returns: + Absolute magnitude bound evaluated at ``z``. + + Raises: + ValueError: If neither or both of ``absolute_mag`` and + ``apparent_mag`` are supplied, or if an apparent magnitude bound is + supplied without ``luminosity_distance_mpc_fn``. + """ if absolute_mag is None and apparent_mag is None: raise ValueError( f"Must provide either m_{bound_name} or apparent_m_{bound_name}." @@ -773,7 +834,23 @@ def _integrated_lf_between_bounds( n_m: int, weight_fn: Callable[[FloatArray, FloatArray], FloatArray] | None = None, ) -> FloatArray: - r"""Return finite-range LF integral between magnitude bounds.""" + r"""Return a finite-range luminosity function integral. + + Args: + z: Redshift value or array. + lf: Luminosity function callable with signature ``lf(M, z)``. + m_lower: Lower magnitude bound. + m_upper: Upper magnitude bound. + n_m: Number of magnitude-grid points. + weight_fn: Optional weighting function. + + Returns: + Luminosity function integral evaluated at ``z``. + + Raises: + ValueError: If redshift values are negative or if the integration + bounds are invalid. + """ z_arr = validate_array(z, name="z") if np.any(z_arr < 0.0): diff --git a/src/lfkit/luminosity_functions/models/composite.py b/src/lfkit/luminosity_functions/models/composite.py index f02b8359..aaa42da3 100644 --- a/src/lfkit/luminosity_functions/models/composite.py +++ b/src/lfkit/luminosity_functions/models/composite.py @@ -23,7 +23,36 @@ def additive_lf( absolute_mag: FloatInput, *components: Callable[[FloatInput], FloatArray], ) -> FloatArray: - """Return the sum of multiple luminosity function components.""" + r"""Return the sum of multiple luminosity function components. + + This evaluates an additive mixture model, + + .. math:: + + \phi_{\mathrm{tot}}(M) = + \sum_i \phi_i(M), + + where each :math:`\phi_i(M)` is an independently defined luminosity + function component evaluated on the same absolute magnitude grid. + + This helper is useful for building composite populations where different + physical or phenomenological components contribute to the same observed + luminosity function, for example a narrow central population plus a broader + satellite-like component. + + Args: + absolute_mag: Absolute magnitude value(s). + *components: Callable luminosity function components. Each component + must accept ``absolute_mag`` and return values broadcastable to the + same shape. + + Returns: + NumPy array containing the summed luminosity function evaluated at + ``absolute_mag``. + + Raises: + ValueError: If no luminosity function components are provided. + """ absolute_mag_arr = validate_array(absolute_mag, name="absolute_mag") if len(components) == 0: @@ -48,7 +77,60 @@ def two_component_lf( modified_m_star: ParameterValue | None = None, modified_luminosity_fraction: ParameterValue = 0.562, ) -> FloatArray: - """Return the sum of lognormal and modified Schechter components.""" + r"""Return a two-component luminosity function. + + This model combines a lognormal component with a cutoff-modified Schechter + component, + + .. math:: + + \phi_{\mathrm{tot}}(M) = + \phi_{\mathrm{lognormal}}(M) + + \phi_{\mathrm{mod}}(M). + + The lognormal term describes a localized component peaked around + ``lognormal_mean_absolute_mag``. The modified Schechter term provides a + broader luminosity function component with a Schechter-like faint end and a + suppressed bright end. + + If ``modified_m_star`` is not supplied, it is inferred from the lognormal + peak magnitude using + + .. math:: + + M_{\star,\mathrm{mod}} = + M_{\mathrm{lognormal}} + \Delta M(f_L), + + where :math:`f_L` is ``modified_luminosity_fraction`` and + :math:`\Delta M(f_L)` is the magnitude offset corresponding to that + luminosity ratio. + + This is mainly a phenomenological composite model: the two terms should be + interpreted as flexible components of the total luminosity function rather + than as a unique physical decomposition. + + Args: + absolute_mag: Absolute magnitude value(s). + lognormal_mean_absolute_mag: Mean absolute magnitude of the lognormal + component. + lognormal_sigma_log_luminosity: Width of the lognormal component in + log luminosity. + modified_phi_star: Normalization of the modified Schechter component. + modified_alpha: Faint-end slope of the modified Schechter component. + lognormal_amplitude: Amplitude of the lognormal component. + modified_m_star: Characteristic magnitude of the modified Schechter + component. If not provided, it is inferred from + ``modified_luminosity_fraction``. + modified_luminosity_fraction: Luminosity ratio used to infer + ``modified_m_star`` when it is not supplied. + + Returns: + NumPy array containing the combined luminosity function evaluated at + ``absolute_mag``. + + Raises: + ValueError: If ``modified_luminosity_fraction`` is not positive. + """ lognormal_mean_absolute_mag_arr = validate_array( lognormal_mean_absolute_mag, name="lognormal_mean_absolute_mag", diff --git a/src/lfkit/luminosity_functions/models/gaussian.py b/src/lfkit/luminosity_functions/models/gaussian.py index e659ba5d..29feaf52 100644 --- a/src/lfkit/luminosity_functions/models/gaussian.py +++ b/src/lfkit/luminosity_functions/models/gaussian.py @@ -21,7 +21,41 @@ def gaussian_lf( sigma_absolute_mag: ParameterValue, amplitude: ParameterValue = 1.0, ) -> FloatArray: - """Return a Gaussian luminosity function in magnitude space.""" + r"""Return a Gaussian luminosity function in magnitude space. + + This computes + + .. math:: + + \phi(M) = + \frac{A}{\sqrt{2\pi}\,\sigma_M} + \exp\left[ + -\frac{1}{2} + \left(\frac{M - \mu_M}{\sigma_M}\right)^2 + \right], + + where :math:`A` is ``amplitude``, :math:`\mu_M` is + ``mean_absolute_mag``, and :math:`\sigma_M` is + ``sigma_absolute_mag``. + + This model describes a symmetric population in absolute magnitude space. + It is useful for localized luminosity function components whose abundance + falls off approximately normally around a preferred magnitude. + + Args: + absolute_mag: Absolute magnitude value(s). + mean_absolute_mag: Mean absolute magnitude of the Gaussian component. + sigma_absolute_mag: Standard deviation in absolute magnitude. + amplitude: Non-negative integrated amplitude of the Gaussian component. + + Returns: + NumPy array containing the Gaussian luminosity function evaluated at + ``absolute_mag``. + + Raises: + ValueError: If ``sigma_absolute_mag`` is not positive or if + ``amplitude`` is negative. + """ absolute_mag_arr = validate_array(absolute_mag, name="absolute_mag") mean_absolute_mag_arr = validate_array( mean_absolute_mag, @@ -59,7 +93,51 @@ def lognormal_lf( sigma_log_luminosity: ParameterValue, amplitude: ParameterValue = 1.0, ) -> FloatArray: - """Return a lognormal luminosity function in magnitude space.""" + r"""Return a lognormal luminosity function in magnitude space. + + This computes a Gaussian profile in log-luminosity, + + .. math:: + + \phi(M) = + \frac{0.4 A}{\sqrt{2\pi}\,\sigma_{\log L}} + \exp\left[ + -\frac{1}{2} + \left(\frac{\Delta \log_{10} L}{\sigma_{\log L}}\right)^2 + \right], + + where + + .. math:: + + \Delta \log_{10} L = + -0.4\,(M - M_0). + + Here :math:`A` is ``amplitude``, :math:`M_0` is + ``mean_absolute_mag``, and :math:`\sigma_{\log L}` is + ``sigma_log_luminosity``. + + This model is symmetric in logarithmic luminosity rather than in + magnitude. It is useful for compact luminosity components where the natural + scatter is closer to multiplicative scatter in luminosity than additive + scatter in magnitude. + + Args: + absolute_mag: Absolute magnitude value(s). + mean_absolute_mag: Absolute magnitude corresponding to the lognormal + peak. + sigma_log_luminosity: Standard deviation in base-10 log luminosity. + amplitude: Non-negative integrated amplitude of the lognormal + component. + + Returns: + NumPy array containing the lognormal luminosity function evaluated at + ``absolute_mag``. + + Raises: + ValueError: If ``sigma_log_luminosity`` is not positive or if + ``amplitude`` is negative. + """ absolute_mag_arr = validate_array(absolute_mag, name="absolute_mag") mean_absolute_mag_arr = validate_array( mean_absolute_mag, diff --git a/src/lfkit/luminosity_functions/models/modifiers.py b/src/lfkit/luminosity_functions/models/modifiers.py index 9708b682..dbc65f91 100644 --- a/src/lfkit/luminosity_functions/models/modifiers.py +++ b/src/lfkit/luminosity_functions/models/modifiers.py @@ -25,7 +25,27 @@ def apply_luminosity_cutoff( cutoff_amplitude: ParameterValue = 1.0, **base_lf_parameters: ParameterValue, ) -> FloatArray: - """Return a luminosity function multiplied by a luminosity-ratio cutoff.""" + """Return a luminosity function multiplied by a luminosity ratio cutoff. + + The modifier has the form ``exp(-A x**p)``, where ``x`` is the luminosity ratio + relative to ``m_star``, ``A`` is ``cutoff_amplitude``, and ``p`` is + ``cutoff_power``. + + Args: + absolute_mag: Absolute magnitude value or array. + base_lf: Base luminosity function model to modify. + m_star: Characteristic magnitude used to compute the luminosity ratio. + cutoff_power: Positive power applied to the luminosity ratio. + cutoff_amplitude: Non-negative amplitude multiplying the cutoff term. + **base_lf_parameters: Parameters passed to ``base_lf``. + + Returns: + Modified luminosity function evaluated at ``absolute_mag``. + + Raises: + ValueError: If ``cutoff_power`` is not positive or if ``cutoff_amplitude`` + is negative. + """ absolute_mag_arr = validate_array(absolute_mag, name="absolute_mag") cutoff_power_arr = validate_array(cutoff_power, name="cutoff_power") cutoff_amplitude_arr = validate_array( diff --git a/src/lfkit/luminosity_functions/models/power_law.py b/src/lfkit/luminosity_functions/models/power_law.py index a8a5fdb0..ff3192ea 100644 --- a/src/lfkit/luminosity_functions/models/power_law.py +++ b/src/lfkit/luminosity_functions/models/power_law.py @@ -1,4 +1,4 @@ -"""Power-law luminosity function models.""" +"""Power law luminosity function models.""" from __future__ import annotations @@ -24,7 +24,33 @@ def power_law_lf( m_star: ParameterValue, alpha: ParameterValue, ) -> FloatArray: - """Return a single power-law luminosity function.""" + r"""Return a single power law luminosity function in magnitude space. + + This computes + + .. math:: + + \phi(M) = 0.4 \ln(10) \, \phi_\star \, x^{\alpha + 1}, + + where + + .. math:: + + x = 10^{-0.4(M - M_\star)} = L/L_\star. + + Args: + absolute_mag: Absolute magnitude value(s). + phi_star: Non-negative normalization. + m_star: Characteristic magnitude used to define ``x``. + alpha: power law slope in luminosity space. + + Returns: + NumPy array containing the power law luminosity function evaluated at + ``absolute_mag``. + + Raises: + ValueError: If ``phi_star`` is negative. + """ absolute_mag_arr = validate_array(absolute_mag, name="absolute_mag") phi_star_arr = validate_array(phi_star, name="phi_star") alpha_arr = validate_array(alpha, name="alpha") @@ -47,7 +73,41 @@ def double_power_law_lf( alpha: ParameterValue, beta: ParameterValue, ) -> FloatArray: - """Return a double power-law luminosity function.""" + r"""Return a double-power law luminosity function in magnitude space. + + This computes + + .. math:: + + \phi(M) = + \frac{0.4 \ln(10) \, \phi_\star} + {x^{-(\alpha + 1)} + x^{-(\beta + 1)}}, + + where + + .. math:: + + x = 10^{-0.4(M - M_\star)} = L/L_\star. + + The two slopes control the asymptotic behaviour on either side of + ``m_star``. This form is useful when a luminosity function behaves like + one power law at the faint end and another at the bright end, with a smooth + turnover between them. + + Args: + absolute_mag: Absolute magnitude value(s). + phi_star: Non-negative normalization. + m_star: Characteristic magnitude where the transition occurs. + alpha: Faint-end power law slope. + beta: Bright-end power law slope. + + Returns: + NumPy array containing the double-power law luminosity function + evaluated at ``absolute_mag``. + + Raises: + ValueError: If ``phi_star`` is negative. + """ absolute_mag_arr = validate_array(absolute_mag, name="absolute_mag") phi_star_arr = validate_array(phi_star, name="phi_star") alpha_arr = validate_array(alpha, name="alpha") @@ -76,7 +136,43 @@ def broken_power_law_lf( alpha_faint: ParameterValue, alpha_bright: ParameterValue, ) -> FloatArray: - """Return a sharply broken power-law luminosity function.""" + r"""Return a sharply broken power law luminosity function in magnitude space. + + This computes + + .. math:: + + \phi(M) = + 0.4 \ln(10) \, \phi_\star + \begin{cases} + x^{\alpha_{\mathrm{faint}} + 1}, & x < 1, \\ + x^{\alpha_{\mathrm{bright}} + 1}, & x \ge 1, + \end{cases} + + where + + .. math:: + + x = 10^{-0.4(M - M_\star)} = L/L_\star. + + Since smaller luminosity means larger magnitude, the ``x < 1`` branch + corresponds to the faint side of the break and the ``x >= 1`` branch + corresponds to the bright side. + + Args: + absolute_mag: Absolute magnitude value(s). + phi_star: Non-negative normalization. + m_star: Break magnitude used to define ``x``. + alpha_faint: power law slope used for the faint side. + alpha_bright: power law slope used for the bright side. + + Returns: + NumPy array containing the broken-power law luminosity function + evaluated at ``absolute_mag``. + + Raises: + ValueError: If ``phi_star`` is negative. + """ absolute_mag_arr = validate_array(absolute_mag, name="absolute_mag") phi_star_arr = validate_array(phi_star, name="phi_star") alpha_faint_arr = validate_array(alpha_faint, name="alpha_faint") @@ -104,7 +200,38 @@ def log_power_law_lf( m_star: ParameterValue, alpha: ParameterValue, ) -> FloatArray: - """Return a power-law luminosity function using log10 normalization.""" + r"""Return a power law luminosity function using log10 normalization. + + This is a convenience wrapper around :func:`power_law_lf` with + + .. math:: + + \phi_\star = 10^{\log_{10}\phi_\star}. + + The evaluated luminosity function is therefore + + .. math:: + + \phi(M) = + 0.4 \ln(10) \, 10^{\log_{10}\phi_\star} + x^{\alpha + 1}, + + where + + .. math:: + + x = 10^{-0.4(M - M_\star)} = L/L_\star. + + Args: + absolute_mag: Absolute magnitude value(s). + log_phi_star: Base-10 logarithm of the normalization. + m_star: Characteristic magnitude used to define ``x``. + alpha: power law slope in luminosity space. + + Returns: + NumPy array containing the power law luminosity function evaluated at + ``absolute_mag``. + """ phi_star = 10.0 ** validate_array(log_phi_star, name="log_phi_star") return power_law_lf( diff --git a/src/lfkit/luminosity_functions/models/schechter.py b/src/lfkit/luminosity_functions/models/schechter.py index ba519825..809db221 100644 --- a/src/lfkit/luminosity_functions/models/schechter.py +++ b/src/lfkit/luminosity_functions/models/schechter.py @@ -1,49 +1,17 @@ -r"""Luminosity function utilities for LFKit. +r"""Schechter luminosity function models. -This module provides simple standalone functions for evaluating -common galaxy luminosity function parameterization. +This module provides standalone functions for evaluating Schechter based +luminosity function models in rest frame absolute magnitude space. -All luminosity functions in this module are defined in rest-frame absolute -magnitude space. Functions with names ending in ``_from_m`` are convenience -wrappers that accept apparent magnitudes, convert them to absolute -magnitudes, and then evaluate the luminosity function in absolute magnitude -space. +Functions with names ending in ``_from_m`` are convenience wrappers that accept +apparent magnitude values, convert them to absolute magnitude values, and then +evaluate the corresponding Schechter model. Implemented models: - - Standard Schechter LF in rest-frame absolute magnitudes - - Schechter LF with configurable redshift evolution of parameters - - Double-power-law Schechter LF in rest-frame absolute magnitudes - -The magnitude-space Schechter function is - -.. math:: - - \phi(M) = 0.4 \ln(10) \, \phi_\star \, x^{\alpha + 1} \exp(-x), - -where - -.. math:: - - x = 10^{-0.4 (M - M_\star)}. - -Redshift evolution is handled by separate helper functions for -``phi_star(z)``, ``M_star(z)``, and ``alpha(z)``. Built-in options include -constant evolution and common linearized forms such as - -.. math:: - - M_\star(z) = M_{0,\star} - q (z - z_{\mathrm{ref}}) - -.. math:: - - \phi_\star(z) = \phi_{0,\star} \, 10^{0.4 p z}. - -For more information see The CNOC2 Field Galaxy Luminosity Function I: -A Description of Luminosity Function Evolution by Lin et al. 1999 -(arXiv:9902249) or GAMA: ugriz galaxy luminosity functions by Loveday et al. -(arXiv:1111.0166). - -All returned quantities are NumPy arrays of dtype float. + - Standard Schechter luminosity function + - Evolving Schechter luminosity function + - Double Schechter luminosity function + - Cumulative standard and evolving Schechter number densities """ from __future__ import annotations @@ -196,7 +164,7 @@ def double_schechter( beta: float, m_transition: ParameterValue, ) -> FloatArray: - r"""Return a double-power-law Schechter luminosity function in magnitude space. + r"""Return a double-power law Schechter luminosity function in magnitude space. This implements the Loveday/GAMA-style faint-end extension @@ -465,7 +433,7 @@ def double_schechter_from_m( k_correction: ParameterValue | None = None, e_correction: ParameterValue | None = None, ) -> FloatArray: - r"""Return a double-power-law Schechter luminosity function from apparent magnitudes. + r"""Return a double-power law Schechter luminosity function from apparent magnitudes. This uses @@ -566,7 +534,7 @@ def schechter_cumulative( Note: This analytic helper is provided only for the standard - Schechter form. The double-power-law case is not included + Schechter form. The double-power law case is not included here because it is usually cleaner to integrate numerically. Args: diff --git a/src/lfkit/luminosity_functions/parameter_models.py b/src/lfkit/luminosity_functions/parameter_models.py index 44897868..447b1621 100644 --- a/src/lfkit/luminosity_functions/parameter_models.py +++ b/src/lfkit/luminosity_functions/parameter_models.py @@ -1,14 +1,15 @@ -r"""Luminosity-function parameter evolution models for LFKit. +r"""luminosity function parameter-evolution models for LFKit. This module provides helper functions for evaluating redshift-dependent -luminosity function parameters such as ``phi_star(z)``, ``M_star(z)``, -and ``alpha(z)``. +luminosity function parameters such as ``phi_star(z)``, ``M_star(z)``, and +``alpha(z)``. -These helpers are used by the main luminosity function evaluators but do -not evaluate the luminosity function themselves. +These helpers are used by luminosity function models that allow parameter +evolution with redshift. They evaluate only the parameters, not the full +luminosity function. -Built-in options include constant evolution and simple linearized forms -commonly used in the literature. +Built-in options include constant evolution and simple linear forms commonly +used in luminosity function analyses. """ from __future__ import annotations @@ -47,19 +48,18 @@ def phi_star_constant( ) -> FloatArray: r"""Return a constant Schechter normalization over redshift. - This uses + This evaluates .. math:: - \phi_\star(z) = \phi_\star. + \phi_\star(z) = \phi_\star. Args: z: Redshift value or array-like of redshift values. phi_star: Constant Schechter normalization. Returns: - NumPy array of Schechter normalization values with the same - broadcast shape as ``z``. + Schechter normalization evaluated at ``z`` with the same shape as ``z``. """ z_arr = validate_array(z, name="z") return np.full_like(z_arr, fill_value=phi_star, dtype=float) @@ -73,9 +73,11 @@ def phi_star_linear_p( ) -> FloatArray: r"""Return a Schechter normalization with density evolution. - This uses the common density-evolution form + This evaluates - \phi_\star(z) = \phi_{0,\star} \, 10^{0.4 p z}. + .. math:: + + \phi_\star(z) = \phi_{0,\star} 10^{0.4 p z}. Args: z: Redshift value or array-like of redshift values. @@ -83,7 +85,7 @@ def phi_star_linear_p( p: Density-evolution parameter. Returns: - NumPy array of redshift-dependent Schechter normalization values. + Redshift-dependent Schechter normalization evaluated at ``z``. """ z_arr = validate_array(z, name="z") return np.asarray(phi_0_star * 10.0 ** (0.4 * p * z_arr), dtype=float) @@ -94,21 +96,20 @@ def m_star_constant( *, m_star: float, ) -> FloatArray: - r"""Return a constant characteristic magnitude over redshift + r"""Return a constant characteristic magnitude over redshift. - This uses + This evaluates .. math:: - M_\star(z) = M_\star. + M_\star(z) = M_\star. Args: z: Redshift value or array-like of redshift values. m_star: Constant characteristic magnitude. Returns: - NumPy array of characteristic magnitudes with the same - broadcast shape as ``z``. + Characteristic magnitude evaluated at ``z`` with the same shape as ``z``. """ z_arr = validate_array(z, name="z") return np.full_like(z_arr, fill_value=m_star, dtype=float) @@ -123,11 +124,11 @@ def m_star_linear_q( ) -> FloatArray: r"""Return a characteristic magnitude with luminosity evolution. - This uses the common luminosity-evolution form + This evaluates .. math:: - M_\star(z) = M_{0,\star} - q (z - z_{\mathrm{ref}}). + M_\star(z) = M_{0,\star} - q (z - z_{\mathrm{ref}}). Args: z: Redshift value or array-like of redshift values. @@ -136,7 +137,7 @@ def m_star_linear_q( z_ref: Reference redshift. Returns: - NumPy array of redshift-dependent characteristic magnitudes. + Redshift-dependent characteristic magnitude evaluated at ``z``. """ z_arr = validate_array(z, name="z") return np.asarray(m_0_star - q * (z_arr - z_ref), dtype=float) @@ -149,19 +150,18 @@ def alpha_constant( ) -> FloatArray: r"""Return a constant faint-end slope over redshift. - This uses + This evaluates .. math:: - \alpha(z) = \alpha. + \alpha(z) = \alpha. Args: z: Redshift value or array-like of redshift values. alpha: Constant faint-end slope. Returns: - NumPy array of faint-end slope values with the same - broadcast shape as ``z``. + Faint-end slope evaluated at ``z`` with the same shape as ``z``. """ z_arr = validate_array(z, name="z") return np.full_like(z_arr, fill_value=alpha, dtype=float) @@ -176,20 +176,20 @@ def alpha_linear( ) -> FloatArray: r"""Return a faint-end slope that varies linearly with redshift. - This uses + This evaluates .. math:: - \alpha(z) = \alpha_0 + \alpha_1 (z - z_{\mathrm{ref}}). + \alpha(z) = \alpha_0 + \alpha_1 (z - z_{\mathrm{ref}}). Args: z: Redshift value or array-like of redshift values. alpha_0: Faint-end slope at the reference redshift. - alpha_1: Linear slope with redshift. + alpha_1: Linear redshift-evolution coefficient. z_ref: Reference redshift. Returns: - NumPy array of redshift-dependent faint-end slope values. + Redshift-dependent faint-end slope evaluated at ``z``. """ z_arr = validate_array(z, name="z") return np.asarray(alpha_0 + alpha_1 * (z_arr - z_ref), dtype=float) @@ -204,14 +204,12 @@ def get_parameter_model( r"""Return a registered luminosity function parameter model. Args: - model_name: Name of the requested model. - registry: Mapping from model names to model callables. - model_kind: Human-readable parameter kind used in error messages, - for example ``"phi_model"``, ``"m_star_model"``, or - ``"alpha_model"``. + model_name: Name of the requested parameter-evolution model. + registry: Mapping from model names to parameter-evolution callables. + model_kind: Human-readable parameter kind used in error messages. Returns: - Registered parameter-model callable. + Registered parameter-evolution callable. Raises: ValueError: If ``model_name`` is not present in ``registry``. @@ -225,7 +223,11 @@ def get_parameter_model( def available_lf_parameter_models() -> dict[str, list[str]]: - """Return available luminosity function parameter evolution models.""" + """Return available luminosity function parameter-evolution models. + + Returns: + Dictionary mapping parameter names to sorted lists of registered model names. + """ return { "phi_star": sorted(PHI_STAR_MODELS), "m_star": sorted(M_STAR_MODELS), @@ -241,7 +243,20 @@ def _register_parameter_model( model_kind: str, overwrite: bool = False, ) -> None: - """Register a luminosity function parameter evolution model.""" + """Register a luminosity function parameter-evolution model. + + Args: + name: Name used to register the model. + model: Callable parameter-evolution model. + registry: Registry to update. + model_kind: Human-readable parameter kind used in error messages. + overwrite: If ``True``, replace an existing model with the same name. + + Raises: + ValueError: If ``name`` is empty or already registered and + ``overwrite=False``. + TypeError: If ``model`` is not callable. + """ if not name: raise ValueError(f"{model_kind} model name cannot be empty.") @@ -263,7 +278,13 @@ def register_phi_star_model( *, overwrite: bool = False, ) -> None: - """Register a phi_star evolution model.""" + """Register a ``phi_star`` evolution model. + + Args: + name: Name used to register the model. + model: Callable accepting redshift and returning ``phi_star(z)``. + overwrite: If ``True``, replace an existing model with the same name. + """ _register_parameter_model( name, model, @@ -279,7 +300,13 @@ def register_m_star_model( *, overwrite: bool = False, ) -> None: - """Register an M_star evolution model.""" + """Register an ``M_star`` evolution model. + + Args: + name: Name used to register the model. + model: Callable accepting redshift and returning ``M_star(z)``. + overwrite: If ``True``, replace an existing model with the same name. + """ _register_parameter_model( name, model, @@ -295,7 +322,13 @@ def register_alpha_model( *, overwrite: bool = False, ) -> None: - """Register an alpha evolution model.""" + """Register an ``alpha`` evolution model. + + Args: + name: Name used to register the model. + model: Callable accepting redshift and returning ``alpha(z)``. + overwrite: If ``True``, replace an existing model with the same name. + """ _register_parameter_model( name, model, @@ -315,25 +348,22 @@ def evaluate_lf_parameters( alpha_model: str = "constant", alpha_kwargs: Mapping[str, ParameterValue] | None = None, ) -> tuple[FloatArray, FloatArray, FloatArray]: - r"""Evaluate evolving luminosity function parameters at redshift ``z``. + r"""Evaluate luminosity function parameters at redshift ``z``. Args: z: Redshift value or array-like of redshift values. - phi_model: Evolution model for ``phi_star``. - phi_kwargs: Keyword arguments passed to the selected - ``phi_star`` evolution model. - m_star_model: Evolution model for ``M_star``. - m_star_kwargs: Keyword arguments passed to the selected - ``M_star`` evolution model. - alpha_model: Evolution model for ``alpha``. - alpha_kwargs: Keyword arguments passed to the selected - ``alpha`` evolution model. + phi_model: Registered evolution model used for ``phi_star``. + phi_kwargs: Keyword arguments passed to the selected ``phi_star`` model. + m_star_model: Registered evolution model used for ``M_star``. + m_star_kwargs: Keyword arguments passed to the selected ``M_star`` model. + alpha_model: Registered evolution model used for ``alpha``. + alpha_kwargs: Keyword arguments passed to the selected ``alpha`` model. Returns: Tuple ``(phi_star, m_star, alpha)`` evaluated at ``z``. Raises: - ValueError: If an unsupported evolution model is requested. + ValueError: If an unsupported parameter-evolution model is requested. """ z_arr = validate_array(z, name="z") diff --git a/src/lfkit/luminosity_functions/redshift_density.py b/src/lfkit/luminosity_functions/redshift_density.py index 1f442caf..7427b975 100644 --- a/src/lfkit/luminosity_functions/redshift_density.py +++ b/src/lfkit/luminosity_functions/redshift_density.py @@ -1,19 +1,20 @@ -r"""Luminosity-function redshift-density utilities. +r"""Luminosity function redshift density utilities. This module provides helpers for converting a luminosity function callable into -an LF-integrated redshift-density curve. +a redshift density curve. -The core operation is +The core operation integrates the luminosity function over the observable +absolute magnitude range implied by an apparent magnitude limit, - n_lf(z) = int phi(M, z) dM +.. math:: -over the observable absolute magnitude range implied by an apparent magnitude -limit. A second helper multiplies this LF-integrated density by a user-supplied -redshift or volume weight. + n_{\mathrm{LF}}(z) = + \int_{M_{\mathrm{bright}}}^{M_{\mathrm{lim}}(z)} + \phi(M, z)\,dM. -The cosmology-dependent distance and volume pieces are supplied as callables. -This keeps the interface independent of CCL, Astropy, or any other cosmology -backend, which is useful for downstream packages such as Binny. +A second helper multiplies this integrated number density by a user supplied +redshift or volume weight. Distance and volume quantities are supplied as +callables, keeping the interface independent of any cosmology backend. Magnitude corrections are supplied as scalar or array-like values evaluated on the same redshift grid. @@ -94,6 +95,11 @@ def lf_integrated_number_density( Returns: NumPy array of LF-integrated number densities evaluated at ``z``. + + Raises: + ValueError: If any redshift value is negative, if ``m_lim`` or + ``m_bright`` is not finite, if ``luminosity_distance_mpc_fn`` returns + invalid values, or if the magnitude integration bounds are invalid. """ z_arr = validate_array(z, name="z") @@ -182,6 +188,12 @@ def lf_weighted_redshift_density( Returns: NumPy array of LF-weighted redshift-density values. + + Raises: + ValueError: If any redshift value is negative, if the luminosity function + number density cannot be evaluated, if ``volume_weight_fn`` returns + negative or non-finite values, or if normalization is requested but the + integral over redshift is not positive and finite. """ z_arr = validate_array(z, name="z") @@ -227,7 +239,21 @@ def _optional_correction_array( *, name: str, ) -> FloatArray: - """Return an optional scalar or array correction broadcast to redshift.""" + """Return an optional correction broadcast to a redshift grid. + + Args: + correction: Optional scalar or array-like correction value. + z: Redshift grid used as the target broadcast shape. + name: Correction name used in validation errors. + + Returns: + Correction values broadcast to the shape of ``z``. If ``correction`` is + ``None``, returns zeros with the same shape as ``z``. + + Raises: + ValueError: If ``correction`` is not scalar and cannot be broadcast to the + shape of ``z``. + """ if correction is None: return np.zeros_like(z, dtype=float) diff --git a/src/lfkit/luminosity_functions/registry.py b/src/lfkit/luminosity_functions/registry.py index 23cb67dd..b2417919 100644 --- a/src/lfkit/luminosity_functions/registry.py +++ b/src/lfkit/luminosity_functions/registry.py @@ -1,4 +1,4 @@ -"""Automatic luminosity-function registries.""" +"""Automatic luminosity function registries.""" from __future__ import annotations @@ -12,7 +12,15 @@ @dataclass(frozen=True) class LFModel: - """Description of a registered luminosity-function model.""" + """Description of a registered luminosity function model. + + Attributes: + name: Public model name. + function: Registered model callable. + independent_variable: Name of the first independent variable. + requires_z: Whether the model requires a second independent variable such + as redshift or condition. + """ name: str function: Callable @@ -25,7 +33,12 @@ def discover_lf_models() -> tuple[ dict[str, LFModel], dict[str, Callable], ]: - """Discover LF models, conditional LF models, and apparent-magnitude evaluators.""" + """Discover luminosity function registries. + + Returns: + Tuple containing registered luminosity function models, conditional + luminosity function models, and apparent magnitude evaluators. + """ lf_models: dict[str, LFModel] = {} conditional_lf_models: dict[str, LFModel] = {} from_m_models: dict[str, Callable] = {} @@ -40,7 +53,12 @@ def _discover_models_package( lf_models: dict[str, LFModel], from_m_models: dict[str, Callable], ) -> None: - """Discover LF models from ``luminosity_functions.models``.""" + """Discover luminosity function models from ``luminosity_functions.models``. + + Args: + lf_models: Registry updated with discovered luminosity function models. + from_m_models: Registry updated with apparent magnitude evaluators. + """ for name, obj in iter_model_functions().items(): _register_lf_model( name, @@ -54,7 +72,12 @@ def _discover_models_package( def _discover_conditional_models( conditional_lf_models: dict[str, LFModel], ) -> None: - """Discover conditional LF models.""" + """Discover conditional luminosity function models. + + Args: + conditional_lf_models: Registry updated with discovered conditional + luminosity function models. + """ _register_module_lf_models( conditional_models, lf_models=conditional_lf_models, @@ -71,7 +94,17 @@ def _register_lf_model( from_m_models: dict[str, Callable] | None, name_transform: Callable[[str], str] | None, ) -> None: - """Register one public LF function.""" + """Register one public luminosity function callable. + + Args: + name: Candidate function name. + obj: Candidate callable. + lf_models: Registry updated with luminosity function models. + from_m_models: Optional registry updated with apparent magnitude + evaluators. + name_transform: Optional callable used to convert function names to public + model names. + """ if not callable(obj): return @@ -108,7 +141,16 @@ def _register_module_lf_models( from_m_models: dict[str, Callable] | None, name_transform: Callable[[str], str] | None, ) -> None: - """Register public LF functions from one module.""" + """Register public luminosity function callables from one module. + + Args: + module: Module object with an ``__all__`` declaration. + lf_models: Registry updated with luminosity function models. + from_m_models: Optional registry updated with apparent magnitude + evaluators. + name_transform: Optional callable used to convert function names to public + model names. + """ for name in getattr(module, "__all__", []): _register_lf_model( name, @@ -120,7 +162,14 @@ def _register_module_lf_models( def _public_model_name(name: str) -> str: - """Return the public model name for LF functions.""" + """Return the public model name for a luminosity function callable. + + Args: + name: Function name to convert. + + Returns: + Public model name with ``conditional_`` and ``_lf`` affixes removed. + """ public_name = name if public_name.startswith("conditional_"): @@ -135,7 +184,15 @@ def _public_model_name(name: str) -> str: def _requires_second_independent_variable( signature: inspect.Signature, ) -> bool: - """Return whether model needs a second positional independent variable.""" + """Return whether a model requires a second independent variable. + + Args: + signature: Function signature to inspect. + + Returns: + ``True`` if the second positional argument is a supported independent + variable name, otherwise ``False``. + """ params = list(signature.parameters.values()) if len(params) < 2: @@ -153,51 +210,94 @@ def _requires_second_independent_variable( def available_lf_models() -> tuple[str, ...]: - """Return available luminosity-function model names.""" + """Return available luminosity function model names. + + Returns: + Sorted tuple of registered luminosity function model names. + """ return tuple(sorted(LF_MODELS)) def available_conditional_lf_models() -> tuple[str, ...]: - """Return available conditional luminosity-function model names.""" + """Return available conditional luminosity function model names. + + Returns: + Sorted tuple of registered conditional luminosity function model names. + """ return tuple(sorted(CONDITIONAL_LF_MODELS)) def available_lf_from_m_models() -> tuple[str, ...]: - """Return LF models with apparent-magnitude evaluators.""" + """Return luminosity function models with apparent magnitude evaluators. + + Returns: + Sorted tuple of model names that provide apparent magnitude evaluators. + """ return tuple(sorted(LF_FROM_M_MODELS)) def get_lf_model(name: str) -> LFModel: - """Return a registered luminosity-function model.""" + """Return a registered luminosity function model. + + Args: + name: Name of the registered luminosity function model. + + Returns: + Registered luminosity function model description. + + Raises: + ValueError: If ``name`` is not registered. + """ try: return LF_MODELS[name] except KeyError as exc: available = ", ".join(available_lf_models()) raise ValueError( - f"Unknown luminosity-function model {name!r}. " + f"Unknown luminosity function model {name!r}. " f"Available models: {available}." ) from exc def get_conditional_lf_model(name: str) -> LFModel: - """Return a registered conditional luminosity-function model.""" + """Return a registered conditional luminosity function model. + + Args: + name: Name of the registered conditional luminosity function model. + + Returns: + Registered conditional luminosity function model description. + + Raises: + ValueError: If ``name`` is not registered. + """ try: return CONDITIONAL_LF_MODELS[name] except KeyError as exc: available = ", ".join(available_conditional_lf_models()) raise ValueError( - f"Unknown conditional luminosity-function model {name!r}. " + f"Unknown conditional luminosity function model {name!r}. " f"Available conditional models: {available}." ) from exc def get_lf_from_m_model(name: str) -> Callable: - """Return an apparent-magnitude LF evaluator.""" + """Return an apparent magnitude luminosity function evaluator. + + Args: + name: Name of the luminosity function model. + + Returns: + Registered apparent magnitude evaluator. + + Raises: + ValueError: If ``name`` does not have a registered apparent magnitude + evaluator. + """ try: return LF_FROM_M_MODELS[name] except KeyError as exc: available = ", ".join(available_lf_from_m_models()) raise ValueError( - f"phi_from_m is not defined for luminosity-function model {name!r}. " + f"phi_from_m is not defined for luminosity function model {name!r}. " f"Available models: {available}." ) from exc diff --git a/tests/test_api_luminosity_function.py b/tests/test_api_luminosity_function.py index 6b82ca9d..3165e81b 100644 --- a/tests/test_api_luminosity_function.py +++ b/tests/test_api_luminosity_function.py @@ -296,7 +296,7 @@ def test_unsupported_model_raises_clear_error(): parameters={}, ) - with pytest.raises(ValueError, match="Unknown luminosity-function model"): + with pytest.raises(ValueError, match="Unknown luminosity function model"): lf.phi(-20.0, 0.5)