From f772aa4515520595096554511b79f075bde297a2 Mon Sep 17 00:00:00 2001 From: denehoffman Date: Thu, 31 Oct 2024 16:02:22 -0400 Subject: [PATCH 01/26] docs: update README with codspeed badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c673b42..817ea7a 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Codecov PyPI - Version + CodSpeed Badge

`laddu` (/ˈlʌduː/) is a library for analysis of particle physics data. It is intended to be a simple and efficient alternative to some of the [other tools](#alternatives) out there. `laddu` is written in Rust with bindings to Python via [`PyO3`](https://github.com/PyO3/pyo3) and [`maturin`](https://github.com/PyO3/maturin) and is the spiritual successor to [`rustitude`](https://github.com/denehoffman/rustitude), one of my first Rust projects. The goal of this project is to allow users to perform complex amplitude analyses (like partial-wave analyses) without complex code or configuration files. From ebeabcdb41edc34f0df60ece7aba475e4fa9d342 Mon Sep 17 00:00:00 2001 From: denehoffman Date: Fri, 1 Nov 2024 09:59:45 -0400 Subject: [PATCH 02/26] feat: add python stub file for vectors --- python/laddu/utils/vectors/__init__.pyi | 38 +++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 python/laddu/utils/vectors/__init__.pyi diff --git a/python/laddu/utils/vectors/__init__.pyi b/python/laddu/utils/vectors/__init__.pyi new file mode 100644 index 0000000..ad039ab --- /dev/null +++ b/python/laddu/utils/vectors/__init__.pyi @@ -0,0 +1,38 @@ +import numpy as np +import numpy.typing as npt + +class Vector3: + mag: float + mag2: float + costheta: float + theta: float + phi: float + unit: Vector3 + px: float + py: float + pz: float + def __init__(self, px: float, py: float, pz: float): ... + def __add__(self, other: Vector3) -> Vector3: ... + def dot(self, other: Vector3) -> float: ... + def cross(self, other: Vector3) -> Vector3: ... + def to_numpy(self) -> npt.NDArray[np.float64]: ... + +class Vector4: + mag: float + mag2: float + vec3: Vector3 + e: float + px: float + py: float + pz: float + momentum: Vector3 + beta: Vector3 + m: float + m2: float + def __init__(self, e: float, px: float, py: float, pz: float): ... + def __add__(self, other: Vector4) -> Vector4: ... + def boost(self, beta: Vector3) -> Vector4: ... + def boost_along(self, other: Vector4) -> Vector4: ... + def to_numpy(self) -> npt.NDArray[np.float64]: ... + @staticmethod + def from_momentum(momentum: Vector3, mass: float) -> Vector4: ... From 66726b0fe0b1e21c63ce582ac8c284f6a94fa2de Mon Sep 17 00:00:00 2001 From: denehoffman Date: Fri, 1 Nov 2024 10:00:06 -0400 Subject: [PATCH 03/26] feat: add stable ABI with minimum python version of 3.7 --- Cargo.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index acec312..4aeb238 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,10 @@ auto_ops = "0.3.0" rand = "0.8.5" rand_chacha = "0.3.1" rayon = { version = "1.10.0", optional = true } -pyo3 = { version = "0.22.5", optional = true, features = ["num-complex"] } +pyo3 = { version = "0.22.5", optional = true, features = [ + "num-complex", + "abi3-py37", +] } numpy = { version = "0.22.0", optional = true, features = ["nalgebra"] } ganesh = "0.12.2" thiserror = "1.0.64" From f2c169decbf8d1ef8b011448ea00dcdb0a968bee Mon Sep 17 00:00:00 2001 From: denehoffman Date: Fri, 1 Nov 2024 12:38:54 -0400 Subject: [PATCH 04/26] fix: use incremental builds for maturin development --- .justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.justfile b/.justfile index a0574e3..d067674 100644 --- a/.justfile +++ b/.justfile @@ -2,4 +2,4 @@ default: just --list develop: - maturin develop -r --uv --strip + CARGO_INCREMENTAL=true maturin develop -r --uv --strip From 79379ccd2b00e1f035411769941e159834c879bc Mon Sep 17 00:00:00 2001 From: denehoffman Date: Fri, 1 Nov 2024 12:39:28 -0400 Subject: [PATCH 05/26] docs: add automatic documentation and readthedocs support --- .readthedocs.yaml | 19 ++++++++++ docs/Makefile | 20 ++++++++++ docs/make.bat | 35 ++++++++++++++++++ docs/requirements.txt | 3 ++ docs/source/conf.py | 37 +++++++++++++++++++ docs/source/index.rst | 11 ++++++ docs/source/modules/index.rst | 10 +++++ .../modules/laddu/amplitudes/breit_wigner.rst | 6 +++ .../modules/laddu/amplitudes/common.rst | 6 +++ .../source/modules/laddu/amplitudes/index.rst | 12 ++++++ .../modules/laddu/amplitudes/kmatrix.rst | 6 +++ docs/source/modules/laddu/amplitudes/ylm.rst | 6 +++ docs/source/modules/laddu/amplitudes/zlm.rst | 6 +++ docs/source/modules/laddu/data.rst | 6 +++ docs/source/modules/laddu/likelihoods.rst | 6 +++ docs/source/modules/laddu/utils/index.rst | 9 +++++ docs/source/modules/laddu/utils/variables.rst | 6 +++ docs/source/modules/laddu/utils/vectors.rst | 6 +++ pyproject.toml | 1 + 19 files changed, 211 insertions(+) create mode 100644 .readthedocs.yaml create mode 100644 docs/Makefile create mode 100644 docs/make.bat create mode 100644 docs/requirements.txt create mode 100644 docs/source/conf.py create mode 100644 docs/source/index.rst create mode 100644 docs/source/modules/index.rst create mode 100644 docs/source/modules/laddu/amplitudes/breit_wigner.rst create mode 100644 docs/source/modules/laddu/amplitudes/common.rst create mode 100644 docs/source/modules/laddu/amplitudes/index.rst create mode 100644 docs/source/modules/laddu/amplitudes/kmatrix.rst create mode 100644 docs/source/modules/laddu/amplitudes/ylm.rst create mode 100644 docs/source/modules/laddu/amplitudes/zlm.rst create mode 100644 docs/source/modules/laddu/data.rst create mode 100644 docs/source/modules/laddu/likelihoods.rst create mode 100644 docs/source/modules/laddu/utils/index.rst create mode 100644 docs/source/modules/laddu/utils/variables.rst create mode 100644 docs/source/modules/laddu/utils/vectors.rst diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..1f0ca08 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,19 @@ +# .readthedocs.yaml +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +version: 2 + +build: + os: ubuntu-24.04 + tools: + python: "3.12" + +sphinx: + configuration: docs/conf.py + +formats: + - pdf + +python: + install: + - requirements: docs/requirements.txt diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d0c3cbf --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..747ffb7 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..dee52b7 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,3 @@ +laddu +sphinx==8.1.3 +sphinx-rtd-theme==3.0.1 diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..c71ca5c --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,37 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +import laddu + +project = "laddu" +copyright = "2024, Nathaniel Dene Hoffman" +author = "Nathaniel Dene Hoffman" +release = laddu.__version__ +version = release + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = ["sphinx.ext.autodoc", "sphinx.ext.autosummary", "sphinx.ext.napoleon"] + +autodoc_default_options = { + "members": True, + "undoc-members": True, +} + +autodoc_member_order = "groupwise" + +templates_path = ["_templates"] +exclude_patterns = [] + + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = "sphinx_rtd_theme" +html_static_path = ["_static"] diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..00e03d6 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,11 @@ +laddu +===== + +``laddu`` is an library for analyzing particle physics data. It is written in Rust with bindings to Python which are documented here. + +.. toctree:: + :hidden: + :caption: API Reference + :name: sec-api + + modules/index diff --git a/docs/source/modules/index.rst b/docs/source/modules/index.rst new file mode 100644 index 0000000..49e2e1d --- /dev/null +++ b/docs/source/modules/index.rst @@ -0,0 +1,10 @@ +Submodules +========== + +.. toctree:: + :maxdepth: 1 + + laddu/amplitudes/index + laddu/data + laddu/likelihoods + laddu/utils/index diff --git a/docs/source/modules/laddu/amplitudes/breit_wigner.rst b/docs/source/modules/laddu/amplitudes/breit_wigner.rst new file mode 100644 index 0000000..048be14 --- /dev/null +++ b/docs/source/modules/laddu/amplitudes/breit_wigner.rst @@ -0,0 +1,6 @@ +Breit-Wigner +============ + +.. automodule:: laddu.amplitudes.breit_wigner + :members: + :undoc-members: diff --git a/docs/source/modules/laddu/amplitudes/common.rst b/docs/source/modules/laddu/amplitudes/common.rst new file mode 100644 index 0000000..fa4d078 --- /dev/null +++ b/docs/source/modules/laddu/amplitudes/common.rst @@ -0,0 +1,6 @@ +Common +====== + +.. automodule:: laddu.amplitudes.common + :members: + :undoc-members: diff --git a/docs/source/modules/laddu/amplitudes/index.rst b/docs/source/modules/laddu/amplitudes/index.rst new file mode 100644 index 0000000..2453a27 --- /dev/null +++ b/docs/source/modules/laddu/amplitudes/index.rst @@ -0,0 +1,12 @@ +Amplitudes +========== + +.. toctree:: + :maxdepth: 1 + :caption: Amplitudes: + + common + ylm + zlm + breit_wigner + kmatrix diff --git a/docs/source/modules/laddu/amplitudes/kmatrix.rst b/docs/source/modules/laddu/amplitudes/kmatrix.rst new file mode 100644 index 0000000..6812c86 --- /dev/null +++ b/docs/source/modules/laddu/amplitudes/kmatrix.rst @@ -0,0 +1,6 @@ +K-Matrix +======== + +.. automodule:: laddu.amplitudes.kmatrix + :members: + :undoc-members: diff --git a/docs/source/modules/laddu/amplitudes/ylm.rst b/docs/source/modules/laddu/amplitudes/ylm.rst new file mode 100644 index 0000000..83463c2 --- /dev/null +++ b/docs/source/modules/laddu/amplitudes/ylm.rst @@ -0,0 +1,6 @@ +Ylm +=== + +.. automodule:: laddu.amplitudes.ylm + :members: + :undoc-members: diff --git a/docs/source/modules/laddu/amplitudes/zlm.rst b/docs/source/modules/laddu/amplitudes/zlm.rst new file mode 100644 index 0000000..80ab4fb --- /dev/null +++ b/docs/source/modules/laddu/amplitudes/zlm.rst @@ -0,0 +1,6 @@ +Zlm +=== + +.. automodule:: laddu.amplitudes.zlm + :members: + :undoc-members: diff --git a/docs/source/modules/laddu/data.rst b/docs/source/modules/laddu/data.rst new file mode 100644 index 0000000..e39671f --- /dev/null +++ b/docs/source/modules/laddu/data.rst @@ -0,0 +1,6 @@ +Data +==== + +.. automodule:: laddu.data + :members: + :undoc-members: diff --git a/docs/source/modules/laddu/likelihoods.rst b/docs/source/modules/laddu/likelihoods.rst new file mode 100644 index 0000000..51559c4 --- /dev/null +++ b/docs/source/modules/laddu/likelihoods.rst @@ -0,0 +1,6 @@ +Likelihoods +=========== + +.. automodule:: laddu.likelihoods + :members: + :undoc-members: diff --git a/docs/source/modules/laddu/utils/index.rst b/docs/source/modules/laddu/utils/index.rst new file mode 100644 index 0000000..d93e072 --- /dev/null +++ b/docs/source/modules/laddu/utils/index.rst @@ -0,0 +1,9 @@ +Utilities +========= + +.. toctree:: + :maxdepth: 1 + :caption: Utility Modules: + + vectors + variables diff --git a/docs/source/modules/laddu/utils/variables.rst b/docs/source/modules/laddu/utils/variables.rst new file mode 100644 index 0000000..31c374d --- /dev/null +++ b/docs/source/modules/laddu/utils/variables.rst @@ -0,0 +1,6 @@ +Variables +========= + +.. automodule:: laddu.utils.variables + :members: + :undoc-members: diff --git a/docs/source/modules/laddu/utils/vectors.rst b/docs/source/modules/laddu/utils/vectors.rst new file mode 100644 index 0000000..d9a3d76 --- /dev/null +++ b/docs/source/modules/laddu/utils/vectors.rst @@ -0,0 +1,6 @@ +Vectors +======= + +.. automodule:: laddu.utils.vectors + :members: + :undoc-members: diff --git a/pyproject.toml b/pyproject.toml index 26b78c1..87932f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,7 @@ amptools-to-laddu = "laddu:convert.run" [project.optional-dependencies] tests = ["pytest"] +docs = ["sphinx", "sphinx-rtd-theme"] [tool.maturin] python-source = "python" From 9452da71dca81e01972b94e7caf275f43504591a Mon Sep 17 00:00:00 2001 From: denehoffman Date: Fri, 1 Nov 2024 12:52:05 -0400 Subject: [PATCH 06/26] fix: correct path to sphinx config --- .readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 1f0ca08..54286a4 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -9,7 +9,7 @@ build: python: "3.12" sphinx: - configuration: docs/conf.py + configuration: docs/source/conf.py formats: - pdf From c47bae2218082846b96aae58cdd48b40073da18e Mon Sep 17 00:00:00 2001 From: denehoffman Date: Fri, 1 Nov 2024 20:17:55 -0400 Subject: [PATCH 07/26] ci: add documentation commands to justfile --- .justfile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.justfile b/.justfile index d067674..a55cfe4 100644 --- a/.justfile +++ b/.justfile @@ -3,3 +3,10 @@ default: develop: CARGO_INCREMENTAL=true maturin develop -r --uv --strip + +pydoc: + make -C docs clean + make -C docs html + +odoc: + firefox ./docs/build/html/index.html From e53494f6cb530beee8e19c4d743e17f9752a859d Mon Sep 17 00:00:00 2001 From: denehoffman Date: Fri, 1 Nov 2024 20:18:14 -0400 Subject: [PATCH 08/26] fix: add amplitude module-level documentation --- docs/source/modules/laddu/amplitudes/index.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/source/modules/laddu/amplitudes/index.rst b/docs/source/modules/laddu/amplitudes/index.rst index 2453a27..49304f3 100644 --- a/docs/source/modules/laddu/amplitudes/index.rst +++ b/docs/source/modules/laddu/amplitudes/index.rst @@ -1,6 +1,11 @@ Amplitudes ========== +.. automodule:: laddu.amplitudes + :members: + :undoc-members: + + .. toctree:: :maxdepth: 1 :caption: Amplitudes: From 8ccaef92397cebc68072aef82871e30715aaac1b Mon Sep 17 00:00:00 2001 From: denehoffman Date: Fri, 1 Nov 2024 20:18:51 -0400 Subject: [PATCH 09/26] feat: test documentation --- src/python.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/python.rs b/src/python.rs index c8c05a7..94c8c91 100644 --- a/src/python.rs +++ b/src/python.rs @@ -46,7 +46,21 @@ pub(crate) mod laddu { fn __add__(&self, other: Self) -> Self { Self(self.0 + other.0) } - fn dot(&self, other: Self) -> Float { + /// The dot product + /// + /// This method is documented. + /// + /// Parameters + /// ---------- + /// other : Vector3 + /// A vector input with which the dot product is taken + /// + /// Returns + /// ------- + /// float + /// The dot product of this vector and `other` + /// + pub fn dot(&self, other: Self) -> Float { self.0.dot(&other.0) } fn cross(&self, other: Self) -> Self { From 655055cc1493f88295d55482734961be3d498cca Mon Sep 17 00:00:00 2001 From: denehoffman Date: Sun, 3 Nov 2024 13:21:47 -0500 Subject: [PATCH 10/26] fix: correct `PolAngle` by normalizing the beam vector --- src/utils/variables.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/variables.rs b/src/utils/variables.rs index 6781f40..f74dec4 100644 --- a/src/utils/variables.rs +++ b/src/utils/variables.rs @@ -268,7 +268,7 @@ impl Variable for PolAngle { let y = beam.vec3().cross(&-recoil.vec3()).unit(); Float::atan2( y.dot(&event.eps[self.beam]), - beam.vec3().dot(&event.eps[self.beam].cross(&y)), + beam.vec3().unit().dot(&event.eps[self.beam].cross(&y)), ) } } From 48b4484e13e2875aae36333829fc01ca6b63ccb4 Mon Sep 17 00:00:00 2001 From: denehoffman Date: Sun, 3 Nov 2024 13:23:48 -0500 Subject: [PATCH 11/26] fix: correct phase in Zlm --- src/amplitudes/zlm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/amplitudes/zlm.rs b/src/amplitudes/zlm.rs index bf32c39..81982b4 100644 --- a/src/amplitudes/zlm.rs +++ b/src/amplitudes/zlm.rs @@ -70,7 +70,7 @@ impl Amplitude for Zlm { ); let pol_angle = self.polarization.pol_angle.value(event); let pgamma = self.polarization.pol_magnitude.value(event); - let phase = Complex::new(Float::cos(pol_angle), Float::sin(pol_angle)); + let phase = Complex::new(Float::cos(-pol_angle), Float::sin(-pol_angle)); let zlm = ylm * phase; cache.store_complex_scalar( self.csid, From 0f564faacda0a2e497d521128d1954f92006f5a5 Mon Sep 17 00:00:00 2001 From: denehoffman Date: Sun, 3 Nov 2024 14:23:47 -0500 Subject: [PATCH 12/26] fix: use the unweighted total number of events and divide data likelihood terms by `n_data` This seems to achieve parity with AmpTools --- src/likelihoods.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/likelihoods.rs b/src/likelihoods.rs index 0cd04a0..5431ac7 100644 --- a/src/likelihoods.rs +++ b/src/likelihoods.rs @@ -113,7 +113,7 @@ impl NLL { #[cfg(feature = "rayon")] pub fn project(&self, parameters: &[Float]) -> Vec { let mc_result = self.mc_evaluator.evaluate(parameters); - let n_mc = self.mc_evaluator.dataset.weighted_len(); + let n_mc = self.mc_evaluator.dataset.len() as Float; mc_result .par_iter() .zip(self.mc_evaluator.dataset.par_iter()) @@ -134,7 +134,7 @@ impl NLL { #[cfg(not(feature = "rayon"))] pub fn project(&self, parameters: &[Float]) -> Vec { let mc_result = self.mc_evaluator.evaluate(parameters); - let n_mc = self.mc_evaluator.dataset.weighted_len(); + let n_mc = self.mc_evaluator.dataset.len() as Float; mc_result .iter() .zip(self.mc_evaluator.dataset.iter()) @@ -162,17 +162,18 @@ impl LikelihoodTerm for NLL { /// result is given by the following formula: /// /// ```math - /// NLL(\vec{p}) = -2 \left(\sum_{e \in \text{Data}} \text{weight}(e) \ln(\mathcal{L}(e)) - \frac{1}{N_{\text{MC}}} \sum_{e \in \text{MC}} \text{weight}(e) \mathcal{L}(e) \right) + /// NLL(\vec{p}) = -2 \left(\sum_{e \in \text{Data}} \text{weight}(e) \ln(\mathcal{L}(e) / N_{\text{DATA}}) - \frac{1}{N_{\text{MC}}} \sum_{e \in \text{MC}} \text{weight}(e) \mathcal{L}(e) \right) /// ``` #[cfg(feature = "rayon")] fn evaluate(&self, parameters: &[Float]) -> Float { let data_result = self.data_evaluator.evaluate(parameters); + let n_data = self.data_evaluator.dataset.len() as Float; let mc_result = self.mc_evaluator.evaluate(parameters); - let n_mc = self.mc_evaluator.dataset.weighted_len(); + let n_mc = self.mc_evaluator.dataset.len() as Float; let data_term: Float = data_result .par_iter() .zip(self.data_evaluator.dataset.par_iter()) - .map(|(l, e)| e.weight * Float::ln(l.re)) + .map(|(l, e)| e.weight * Float::ln(l.re / n_data)) .parallel_sum_with_accumulator::>(); let mc_term: Float = mc_result .par_iter() @@ -189,17 +190,18 @@ impl LikelihoodTerm for NLL { /// result is given by the following formula: /// /// ```math - /// NLL(\vec{p}) = -2 \left(\sum_{e \in \text{Data}} \text{weight}(e) \ln(\mathcal{L}(e)) - \frac{N_{\text{Data}}}{N_{\text{MC}}} \sum_{e \in \text{MC}} \text{weight}(e) \mathcal{L}(e) \right) + /// NLL(\vec{p}) = -2 \left(\sum_{e \in \text{Data}} \text{weight}(e) \ln(\mathcal{L}(e) / N_{\text{DATA}}) - \frac{1}{N_{\text{MC}}} \sum_{e \in \text{MC}} \text{weight}(e) \mathcal{L}(e) \right) /// ``` #[cfg(not(feature = "rayon"))] fn evaluate(&self, parameters: &[Float]) -> Float { let data_result = self.data_evaluator.evaluate(parameters); + let n_data = self.data_evaluator.dataset.len() as Float; let mc_result = self.mc_evaluator.evaluate(parameters); - let n_mc = self.mc_evaluator.dataset.weighted_len(); + let n_mc = self.mc_evaluator.dataset.len() as Float; let data_term: Float = data_result .iter() .zip(self.data_evaluator.dataset.iter()) - .map(|(l, e)| e.weight * Float::ln(l.re)) + .map(|(l, e)| e.weight * Float::ln(l.re / n_data)) .sum_with_accumulator::>(); let mc_term: Float = mc_result .iter() @@ -220,7 +222,7 @@ impl LikelihoodTerm for NLL { let data_parameters = Parameters::new(parameters, &data_resources.constants); let mc_resources = self.mc_evaluator.resources.read(); let mc_parameters = Parameters::new(parameters, &mc_resources.constants); - let n_mc = self.mc_evaluator.dataset.weighted_len(); + let n_mc = self.mc_evaluator.dataset.len() as Float; let data_term: DVector = self .data_evaluator .dataset @@ -336,7 +338,7 @@ impl LikelihoodTerm for NLL { let n_data = self.data_evaluator.dataset.weighted_len(); let mc_resources = self.mc_evaluator.resources.read(); let mc_parameters = Parameters::new(parameters, &mc_resources.constants); - let n_mc = self.mc_evaluator.dataset.weighted_len(); + let n_mc = self.mc_evaluator.dataset.len() as Float; let data_term: DVector = self .data_evaluator .dataset From b204769acf209ffecf0a6d8d9385dcdadb0fe543 Mon Sep 17 00:00:00 2001 From: denehoffman Date: Sun, 3 Nov 2024 14:24:20 -0500 Subject: [PATCH 13/26] ci: docstrings are not exported with `maturin develop` --- .justfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.justfile b/.justfile index a55cfe4..db1b84f 100644 --- a/.justfile +++ b/.justfile @@ -4,7 +4,9 @@ default: develop: CARGO_INCREMENTAL=true maturin develop -r --uv --strip -pydoc: +makedocs: + CARGO_INCREMENTAL=true maturin build -r --strip + uv pip install ./target/wheels/* make -C docs clean make -C docs html From 40e2252f5cc49712942e4349959f51b858790b88 Mon Sep 17 00:00:00 2001 From: denehoffman Date: Sun, 3 Nov 2024 16:30:34 -0500 Subject: [PATCH 14/26] fix: update results of `example_1` given likelihood and Zlm changes --- python_examples/example_1/example_1.svg | 3474 +++++++++++------------ python_examples/example_1/example_1.txt | 2 +- 2 files changed, 1738 insertions(+), 1738 deletions(-) diff --git a/python_examples/example_1/example_1.svg b/python_examples/example_1/example_1.svg index 18e684d..f08d2b6 100644 --- a/python_examples/example_1/example_1.svg +++ b/python_examples/example_1/example_1.svg @@ -6,7 +6,7 @@ - 2024-10-31T15:42:10.046502 + 2024-11-03T15:14:47.292893 image/svg+xml @@ -40,813 +40,813 @@ z +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> +" clip-path="url(#pcae40e8602)" style="fill: #007bc0; opacity: 0.1"/> - - + @@ -905,7 +905,7 @@ z - + @@ -946,7 +946,7 @@ z - + @@ -982,7 +982,7 @@ z - + @@ -1029,7 +1029,7 @@ z - + @@ -1085,7 +1085,7 @@ z - + @@ -1100,124 +1100,124 @@ z - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -1542,12 +1542,12 @@ z - - + @@ -1560,12 +1560,12 @@ L 8 0 - + - + @@ -1576,12 +1576,12 @@ L 8 0 - + - + @@ -1592,12 +1592,12 @@ L 8 0 - + - + @@ -1608,12 +1608,12 @@ L 8 0 - + - + @@ -1624,12 +1624,12 @@ L 8 0 - + - + @@ -1641,12 +1641,12 @@ L 8 0 - + - + @@ -1658,12 +1658,12 @@ L 8 0 - + - + @@ -1675,173 +1675,173 @@ L 8 0 - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -1956,416 +1956,416 @@ z +" clip-path="url(#pcae40e8602)" style="fill: none; stroke: #000000; stroke-linejoin: miter"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2729,7 +2729,7 @@ L 574.585 152.53125 - + @@ -2757,7 +2757,7 @@ L 574.585 189.4425 - + @@ -2791,808 +2791,808 @@ z +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> +" clip-path="url(#p6180711b8f)" style="fill: #ef3a47; opacity: 0.1"/> - + @@ -3607,7 +3607,7 @@ z - + @@ -3622,7 +3622,7 @@ z - + @@ -3637,7 +3637,7 @@ z - + @@ -3652,7 +3652,7 @@ z - + @@ -3667,7 +3667,7 @@ z - + @@ -3682,119 +3682,119 @@ z - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -3831,224 +3831,224 @@ z - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -4078,470 +4078,470 @@ z +" clip-path="url(#p6180711b8f)" style="fill: none; stroke: #000000; stroke-linejoin: miter"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -4743,7 +4743,7 @@ L 1314.72 152.29125 - + @@ -4771,7 +4771,7 @@ L 1314.72 189.2025 - + @@ -4795,10 +4795,10 @@ L 1314.72 189.2025 - + - + diff --git a/python_examples/example_1/example_1.txt b/python_examples/example_1/example_1.txt index 6930ad9..872e63e 100644 --- a/python_examples/example_1/example_1.txt +++ b/python_examples/example_1/example_1.txt @@ -2,5 +2,5 @@ ┃ ┃ f0(1500) Width ┃ f2'(1525) Width ┃ S/D Ratio ┃ S-D Phase ┃ ┡━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━┩ │ Truth │ 0.11200 │ 0.08600 │ 1.41421 │ 0.78540 │ -│ Fit │ 0.10828 ± 0.00079 │ 0.08575 ± 0.00039 │ 1.46706 ± 0.00479 │ 0.93561 ± 0.00314 │ +│ Fit │ 0.11252 ± 0.00104 │ 0.08656 ± 0.00031 │ 1.44949 ± 0.00771 │ 0.80024 ± 0.00369 │ └───────┴───────────────────┴───────────────────┴───────────────────┴───────────────────┘ From 8119755c037620026b9e13256a5d8015c3278083 Mon Sep 17 00:00:00 2001 From: denehoffman Date: Sun, 3 Nov 2024 16:57:27 -0500 Subject: [PATCH 15/26] docs: add documentation for `Vector3` in Python API --- src/python.rs | 135 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 134 insertions(+), 1 deletion(-) diff --git a/src/python.rs b/src/python.rs index 94c8c91..23625ec 100644 --- a/src/python.rs +++ b/src/python.rs @@ -34,6 +34,18 @@ pub(crate) mod laddu { env!("CARGO_PKG_VERSION").to_string() } + /// A 3-momentum vector formed from Cartesian components + /// + /// Parameters + /// ---------- + /// px, py, pz : float + /// The Cartesian components of the 3-vector + /// + /// Returns + /// ------- + /// Vector3 + /// A new 3-momentum vector made from the given components + /// #[pyclass] #[derive(Clone)] struct Vector3(nalgebra::Vector3); @@ -48,7 +60,7 @@ pub(crate) mod laddu { } /// The dot product /// - /// This method is documented. + /// Calculates the dot product of two Vector3s. /// /// Parameters /// ---------- @@ -63,45 +75,166 @@ pub(crate) mod laddu { pub fn dot(&self, other: Self) -> Float { self.0.dot(&other.0) } + /// The cross product + /// + /// Calculates the cross product of two Vector3s. + /// + /// Parameters + /// ---------- + /// other : Vector3 + /// A vector input with which the cross product is taken + /// + /// Returns + /// ------- + /// Vector3 + /// The cross product of this vector and `other` + /// fn cross(&self, other: Self) -> Self { Self(self.0.cross(&other.0)) } + /// The magnitude of the 3-vector + /// + /// This is calculated as: + /// + /// .. math:: \sqrt{p_x^2 + p_y^2 + p_z^2} + /// + /// Returns + /// ------- + /// float + /// The magnitude of this vector #[getter] fn mag(&self) -> Float { self.0.mag() } + /// The magnitude-squared of the 3-vector + /// + /// This is calculated as: + /// + /// .. math:: p_x^2 + p_y^2 + p_z^2 + /// + /// Returns + /// ------- + /// float + /// The squared magnitude of this vector + /// #[getter] fn mag2(&self) -> Float { self.0.mag2() } + /// The cosine of the polar angle of this vector in spherical coordinates + /// + /// The polar angle is defined in the range + /// + /// .. math:: 0 \leq \theta \leq \pi + /// + /// so the cosine falls in the range + /// + /// .. math:: -1 \leq \cos\theta \leq +1 + /// + /// This is calculated as: + /// + /// .. math:: \cos\theta = \frac{p_z}{|\vec{p}|} + /// + /// Returns + /// ------- + /// float + /// The cosine of the polar angle of this vector + /// #[getter] fn costheta(&self) -> Float { self.0.costheta() } + /// The polar angle of this vector in spherical coordinates + /// + /// The polar angle is defined in the range + /// + /// .. math:: 0 \leq \theta \leq \pi + /// + /// This is calculated as: + /// + /// .. math:: \theta = \arccos\left(\frac{p_z}{|\vec{p}|}\right) + /// + /// Returns + /// ------- + /// float + /// The polar angle of this vector + /// #[getter] fn theta(&self) -> Float { self.0.theta() } + /// The azimuthal angle of this vector in spherical coordinates + /// + /// The azimuthal angle is defined in the range + /// + /// .. math:: 0 \leq \varphi \leq 2\pi + /// + /// This is calculated as: + /// + /// .. math:: \varphi = \text{sgn}(p_y)\arccos\left(\frac{p_x}{\sqrt{p_x^2 + p_y^2}}\right) + /// + /// although the actual calculation just uses the `atan2` function + /// + /// Returns + /// ------- + /// float + /// The azimuthal angle of this vector + /// #[getter] fn phi(&self) -> Float { self.0.phi() } + /// The normalized unit vector pointing in the direction of this vector + /// + /// Returns + /// ------- + /// Vector3 + /// A unit vector pointing in the same direction as this vector + /// #[getter] fn unit(&self) -> Self { Self(self.0.unit()) } + /// The x-component of this vector + /// + /// Returns + /// ------- + /// float + /// The x-component + /// #[getter] fn px(&self) -> Float { self.0.px() } + /// The y-component of this vector + /// + /// Returns + /// ------- + /// float + /// The y-component + /// #[getter] fn py(&self) -> Float { self.0.py() } + /// The z-component of this vector + /// + /// Returns + /// ------- + /// float + /// The z-component + /// #[getter] fn pz(&self) -> Float { self.0.pz() } + /// Convert the 3-vector to a `numpy` array + /// + /// Returns + /// ------- + /// numpy_vec: array_like + /// A `numpy` array built from the components of this `Vector3` + /// fn to_numpy<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray1> { PyArray1::from_slice_bound(py, self.0.as_slice()) } From 1975fee36329b8834f3e0eb9ee4aac80c2501a2a Mon Sep 17 00:00:00 2001 From: denehoffman Date: Mon, 4 Nov 2024 10:24:16 -0500 Subject: [PATCH 16/26] feat: add gamma factor calculation to 4-momentum --- python/laddu/utils/vectors/__init__.pyi | 1 + src/python.rs | 4 ++++ src/utils/vectors.rs | 8 ++++++++ 3 files changed, 13 insertions(+) diff --git a/python/laddu/utils/vectors/__init__.pyi b/python/laddu/utils/vectors/__init__.pyi index ad039ab..4c93fd1 100644 --- a/python/laddu/utils/vectors/__init__.pyi +++ b/python/laddu/utils/vectors/__init__.pyi @@ -26,6 +26,7 @@ class Vector4: py: float pz: float momentum: Vector3 + gamma: float beta: Vector3 m: float m2: float diff --git a/src/python.rs b/src/python.rs index 23625ec..7671f7d 100644 --- a/src/python.rs +++ b/src/python.rs @@ -294,6 +294,10 @@ pub(crate) mod laddu { Vector3(self.0.momentum().into()) } #[getter] + fn gamma(&self) -> Float { + self.0.gamma() + } + #[getter] fn beta(&self) -> Vector3 { Vector3(self.0.beta()) } diff --git a/src/utils/vectors.rs b/src/utils/vectors.rs index e2729c5..12003a4 100644 --- a/src/utils/vectors.rs +++ b/src/utils/vectors.rs @@ -26,6 +26,8 @@ pub trait FourMomentum: FourVector { fn pz(&self) -> Float; /// The three-momentum fn momentum(&self) -> VectorView; + /// The $`\gamma`$ factor $`\frac{1}{\sqrt{1 - \beta^2}}`$. + fn gamma(&self) -> Float; /// The $`\vec{\beta}`$ vector $`\frac{\vec{p}}{E}`$. fn beta(&self) -> Vector3; /// The mass of the corresponding object. @@ -118,6 +120,12 @@ impl FourMomentum for Vector4 { self.vec3() } + fn gamma(&self) -> Float { + let beta = self.beta(); + let b2 = beta.dot(&beta); + 1.0 / Float::sqrt(1.0 - b2) + } + fn beta(&self) -> Vector3 { self.momentum().unscale(self.e()) } From 2d6ade8695d3138689ab77fc80bf5b90ba9f36b1 Mon Sep 17 00:00:00 2001 From: denehoffman Date: Mon, 4 Nov 2024 10:24:44 -0500 Subject: [PATCH 17/26] docs: document`vectors` Python API --- src/python.rs | 277 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 271 insertions(+), 6 deletions(-) diff --git a/src/python.rs b/src/python.rs index 7671f7d..9790174 100644 --- a/src/python.rs +++ b/src/python.rs @@ -96,21 +96,22 @@ pub(crate) mod laddu { /// /// This is calculated as: /// - /// .. math:: \sqrt{p_x^2 + p_y^2 + p_z^2} + /// .. math:: |\vec{p}| = \sqrt{p_x^2 + p_y^2 + p_z^2} /// /// Returns /// ------- /// float /// The magnitude of this vector + /// #[getter] fn mag(&self) -> Float { self.0.mag() } - /// The magnitude-squared of the 3-vector + /// The squared magnitude of the 3-vector /// /// This is calculated as: /// - /// .. math:: p_x^2 + p_y^2 + p_z^2 + /// .. math:: |\vec{p}|^2 = p_x^2 + p_y^2 + p_z^2 /// /// Returns /// ------- @@ -173,7 +174,7 @@ pub(crate) mod laddu { /// /// .. math:: \varphi = \text{sgn}(p_y)\arccos\left(\frac{p_x}{\sqrt{p_x^2 + p_y^2}}\right) /// - /// although the actual calculation just uses the `atan2` function + /// although the actual calculation just uses the ``atan2`` function /// /// Returns /// ------- @@ -228,12 +229,12 @@ pub(crate) mod laddu { fn pz(&self) -> Float { self.0.pz() } - /// Convert the 3-vector to a `numpy` array + /// Convert the 3-vector to a ``numpy`` array /// /// Returns /// ------- /// numpy_vec: array_like - /// A `numpy` array built from the components of this `Vector3` + /// A ``numpy`` array built from the components of this ``Vector3`` /// fn to_numpy<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray1> { PyArray1::from_slice_bound(py, self.0.as_slice()) @@ -246,6 +247,23 @@ pub(crate) mod laddu { } } + /// A 4-momentum vector formed from energy and Cartesian 3-momentum components + /// + /// This vector is ordered with energy as the zeroeth component (:math:`[E, p_x, p_y, p_z]`) and assumes a :math:`(+---)` + /// signature + /// + /// Parameters + /// ---------- + /// e : float + /// The energy component + /// px, py, pz : float + /// The Cartesian components of the 3-vector + /// + /// Returns + /// ------- + /// Vector4 + /// A new 4-momentum vector made from the given components + /// #[pyclass] #[derive(Clone)] struct Vector4(nalgebra::Vector4); @@ -258,64 +276,277 @@ pub(crate) mod laddu { fn __add__(&self, other: Self) -> Self { Self(self.0 + other.0) } + /// The magnitude of the 4-vector + /// + /// This is calculated as: + /// + /// .. math:: |p| = \sqrt{E^2 - (p_x^2 + p_y^2 + p_z^2)} + /// + /// Returns + /// ------- + /// float + /// The magnitude of this vector + /// + /// See Also + /// -------- + /// Vector4.m + /// #[getter] fn mag(&self) -> Float { self.0.mag() } + /// The squared magnitude of the 4-vector + /// + /// This is calculated as: + /// + /// .. math:: |p|^2 = E^2 - (p_x^2 + p_y^2 + p_z^2) + /// + /// Returns + /// ------- + /// float + /// The squared magnitude of this vector + /// + /// See Also + /// -------- + /// Vector4.m2 + /// #[getter] fn mag2(&self) -> Float { self.0.mag2() } + /// The 3-vector part of this 4-vector + /// + /// Returns + /// ------- + /// Vector3 + /// The internal 3-vector + /// + /// See Also + /// -------- + /// Vector4.momentum + /// #[getter] fn vec3(&self) -> Vector3 { Vector3(self.0.vec3().into()) } + /// Boost the given 4-momentum according to a boost velocity + /// + /// The resulting 4-momentum is equal to the original boosted to an inertial frame with + /// relative velocity :math:`\beta`: + /// + /// .. math:: \left[E'; \vec{p}'\right] = \left[ \gamma E - \vec{\beta}\cdot\vec{p}; \vec{p} + \left(\frac{(\gamma - 1) \vec{p}\cdot\vec{\beta}}{\beta^2} - \gamma E\right)\vec{\beta}\right] + /// + /// Parameters + /// ---------- + /// beta : Vector3 + /// The relative velocity needed to get to the new frame from the current one + /// + /// Returns + /// ------- + /// Vector4 + /// The boosted 4-momentum + /// + /// See Also + /// -------- + /// Vector4.beta + /// Vector4.gamma + /// Vector4.boost_along + /// fn boost(&self, beta: &Vector3) -> Self { Self(self.0.boost(&beta.0)) } + /// The energy associated with this vector + /// + /// Returns + /// ------- + /// float + /// The energy + /// #[getter] fn e(&self) -> Float { self.0[0] } + /// The x-component of this vector + /// + /// Returns + /// ------- + /// float + /// The x-component + /// #[getter] fn px(&self) -> Float { self.0.px() } + /// The y-component of this vector + /// + /// Returns + /// ------- + /// float + /// The y-component + /// #[getter] fn py(&self) -> Float { self.0.py() } + /// The z-component of this vector + /// + /// Returns + /// ------- + /// float + /// The z-component + /// #[getter] fn pz(&self) -> Float { self.0.pz() } + /// The 3-momentum part of this 4-momentum + /// + /// Returns + /// ------- + /// Vector3 + /// The internal 3-momentum + /// + /// See Also + /// -------- + /// Vector4.vec3 + /// #[getter] fn momentum(&self) -> Vector3 { Vector3(self.0.momentum().into()) } + /// The relativistic gamma factor + /// + /// The :math:`\gamma` factor is equivalent to + /// + /// .. math:: \gamma = \frac{1}{\sqrt{1 - \beta^2}} + /// + /// Returns + /// ------- + /// float + /// The associated :math:`\gamma` factor + /// + /// See Also + /// -------- + /// Vector4.beta + /// Vector4.boost + /// Vector4.boost_along + /// #[getter] fn gamma(&self) -> Float { self.0.gamma() } + /// The velocity 3-vector + /// + /// The :math:`\beta` vector is equivalent to + /// + /// .. math:: \vec{\beta} = \frac{\vec{p}}{E} + /// + /// Returns + /// ------- + /// Vector3 + /// The associated velocity vector + /// + /// See Also + /// -------- + /// Vector4.gamma + /// Vector4.boost + /// Vector4.boost_along + /// #[getter] fn beta(&self) -> Vector3 { Vector3(self.0.beta()) } + /// The invariant mass associated with the four-momentum + /// + /// This is calculated as: + /// + /// .. math:: m = \sqrt{E^2 - (p_x^2 + p_y^2 + p_z^2)} + /// + /// Returns + /// ------- + /// float + /// The magnitude of this vector + /// + /// See Also + /// -------- + /// Vector4.mag + /// #[getter] fn m(&self) -> Float { self.0.m() } + /// The square of the invariant mass associated with the four-momentum + /// + /// This is calculated as: + /// + /// .. math:: m^2 = E^2 - (p_x^2 + p_y^2 + p_z^2) + /// + /// Returns + /// ------- + /// float + /// The squared magnitude of this vector + /// + /// See Also + /// -------- + /// Vector4.mag2 + /// #[getter] fn m2(&self) -> Float { self.0.m2() } + /// Boost the given 4-momentum into the rest frame of another + /// + /// The resulting 4-momentum is equal to the original boosted into the rest frame + /// of `other` + /// + /// Boosting a vector by itself should yield that vector in its own rest frame + /// + /// Parameters + /// ---------- + /// other : Vector4 + /// The 4-momentum whose rest-frame is the boost target + /// + /// Returns + /// ------- + /// Vector4 + /// The boosted 4-momentum + /// + /// See Also + /// -------- + /// Vector4.boost + /// fn boost_along(&self, other: &Self) -> Self { Self(self.0.boost_along(&other.0)) } + /// Construct a 4-momentum from a 3-momentum and corresponding `mass` + /// + /// The mass-energy equivalence is used to compute the energy of the 4-momentum: + /// + /// .. math:: E = \sqrt{m^2 + p^2} + /// + /// Parameters + /// ---------- + /// momentum : Vector3 + /// The spatial 3-momentum + /// mass : float + /// The associated rest mass + /// + /// Returns + /// ------- + /// Vector4 + /// A new 4-momentum with the given spatial `momentum` and `mass` + /// #[staticmethod] fn from_momentum(momentum: &Vector3, mass: Float) -> Self { Self(nalgebra::Vector4::from_momentum(&momentum.0, mass)) } + /// Convert the 4-vector to a `numpy` array + /// + /// Returns + /// ------- + /// numpy_vec: array_like + /// A ``numpy`` array built from the components of this ``Vector4`` + /// fn to_numpy<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray1> { PyArray1::from_slice_bound(py, self.0.as_slice()) } @@ -327,6 +558,27 @@ pub(crate) mod laddu { } } + /// A single event + /// + /// Events are composed of a set of 4-momenta of particles in the overall + /// center-of-momentum frame, polarizations or helicities described by 3-vectors, and a + /// weight + /// + /// Parameters + /// ---------- + /// p4s : list[Vector4] + /// 4-momenta of each particle in the event in the overall center-of-momentum frame + /// eps : list[Vector3] + /// 3-vectors describing the polarization or helicity of the particles + /// given in `p4s` + /// weight : float + /// The weight associated with this event + /// + /// Returns + /// ------- + /// event : Event + /// An event formed from the given components + /// #[pyclass] #[derive(Clone)] struct Event(Arc); @@ -344,20 +596,33 @@ pub(crate) mod laddu { pub(crate) fn __str__(&self) -> String { self.0.to_string() } + /// The list of 4-momenta for each particle in the event + /// #[getter] pub(crate) fn get_p4s(&self) -> Vec { self.0.p4s.iter().map(|p4| Vector4(*p4)).collect() } + /// The list of 3-vectors describing the polarization or helicity of particles in + /// the event + /// #[getter] pub(crate) fn get_eps(&self) -> Vec { self.0.eps.iter().map(|eps_vec| Vector3(*eps_vec)).collect() } + /// The weight of this event relative to othersvin a Dataset + /// #[getter] pub(crate) fn get_weight(&self) -> Float { self.0.weight } } + /// A set of Events + /// + /// Parameters + /// ---------- + /// events : list[Event] + /// #[pyclass] #[derive(Clone)] struct Dataset(Arc); From 9bd054c1f26ead820d4d38f2b18356234e7d3196 Mon Sep 17 00:00:00 2001 From: denehoffman Date: Tue, 5 Nov 2024 16:44:03 -0500 Subject: [PATCH 18/26] fix: make sure code works if no pol angle/magnitude are provided --- python/laddu/convert.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/laddu/convert.py b/python/laddu/convert.py index 1eb8a6e..dee9cde 100644 --- a/python/laddu/convert.py +++ b/python/laddu/convert.py @@ -125,8 +125,8 @@ def run(): output_file = args[""] tree_name = args["--tree"] pol_in_beam = args["--pol-in-beam"] - pol_angle = float(args["--pol-angle"]) * np.pi / 180 - pol_magnitude = float(args["--pol-magnitude"]) + pol_angle = float(args["--pol-angle"]) * np.pi / 180 if args["--pol-angle"] else None + pol_magnitude = float(args["--pol-magnitude"]) if args["--pol-magnitude"] else None num_entries = int(args["-n"]) if args["-n"] else None convert_from_amptools( From 74af349793979195c02d1828a7317c451f1d7a21 Mon Sep 17 00:00:00 2001 From: denehoffman Date: Tue, 5 Nov 2024 16:44:30 -0500 Subject: [PATCH 19/26] docs: fix data format which said that `eps` vectors have a "p" in their column names --- README.md | 6 +++--- src/lib.rs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 817ea7a..7565ff4 100644 --- a/README.md +++ b/README.md @@ -246,9 +246,9 @@ The data format for `laddu` is a bit different from some of the alternatives lik | `p4_0_Px` | `Float32` | Beam Momentum (x-component) | | `p4_0_Py` | `Float32` | Beam Momentum (y-component) | | `p4_0_Pz` | `Float32` | Beam Momentum (z-component) | -| `eps_0_Px` | `Float32` | Beam Polarization (x-component) | -| `eps_0_Py` | `Float32` | Beam Polarization (y-component) | -| `eps_0_Pz` | `Float32` | Beam Polarization (z-component) | +| `eps_0_x` | `Float32` | Beam Polarization (x-component) | +| `eps_0_y` | `Float32` | Beam Polarization (y-component) | +| `eps_0_z` | `Float32` | Beam Polarization (z-component) | | `p4_1_E` | `Float32` | Recoil Proton Energy | | `p4_1_Px` | `Float32` | Recoil Proton Momentum (x-component) | | `p4_1_Py` | `Float32` | Recoil Proton Momentum (y-component) | diff --git a/src/lib.rs b/src/lib.rs index c6ca0a1..46a256a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -220,9 +220,9 @@ //! | `p4_0_Px` | `Float32` | Beam Momentum (x-component) | //! | `p4_0_Py` | `Float32` | Beam Momentum (y-component) | //! | `p4_0_Pz` | `Float32` | Beam Momentum (z-component) | -//! | `eps_0_Px` | `Float32` | Beam Polarization (x-component) | -//! | `eps_0_Py` | `Float32` | Beam Polarization (y-component) | -//! | `eps_0_Pz` | `Float32` | Beam Polarization (z-component) | +//! | `eps_0_x` | `Float32` | Beam Polarization (x-component) | +//! | `eps_0_y` | `Float32` | Beam Polarization (y-component) | +//! | `eps_0_z` | `Float32` | Beam Polarization (z-component) | //! | `p4_1_E` | `Float32` | Recoil Proton Energy | //! | `p4_1_Px` | `Float32` | Recoil Proton Momentum (x-component) | //! | `p4_1_Py` | `Float32` | Recoil Proton Momentum (y-component) | From c73dbfc709865875bd854da27257e046b57e72c8 Mon Sep 17 00:00:00 2001 From: denehoffman Date: Tue, 5 Nov 2024 16:45:51 -0500 Subject: [PATCH 20/26] docs: more documentation for Python API I think I might want to redocument some classes using the `Attributes` field, but I'd like to get the PR through before I worry about the nitpicks since there are other important commits here --- src/python.rs | 625 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 621 insertions(+), 4 deletions(-) diff --git a/src/python.rs b/src/python.rs index 9790174..e7b6262 100644 --- a/src/python.rs +++ b/src/python.rs @@ -566,9 +566,9 @@ pub(crate) mod laddu { /// /// Parameters /// ---------- - /// p4s : list[Vector4] + /// p4s : list of Vector4 /// 4-momenta of each particle in the event in the overall center-of-momentum frame - /// eps : list[Vector3] + /// eps : list of Vector3 /// 3-vectors describing the polarization or helicity of the particles /// given in `p4s` /// weight : float @@ -609,7 +609,7 @@ pub(crate) mod laddu { pub(crate) fn get_eps(&self) -> Vec { self.0.eps.iter().map(|eps_vec| Vector3(*eps_vec)).collect() } - /// The weight of this event relative to othersvin a Dataset + /// The weight of this event relative to others in a Dataset /// #[getter] pub(crate) fn get_weight(&self) -> Float { @@ -619,9 +619,17 @@ pub(crate) mod laddu { /// A set of Events /// + /// Datasets can be created from lists of Events or by using the provided ``laddu.open`` function + /// + /// Datasets can also be indexed directly to access individual Events + /// /// Parameters /// ---------- - /// events : list[Event] + /// events : list of Event + /// + /// See Also + /// -------- + /// laddu.open /// #[pyclass] #[derive(Clone)] @@ -638,16 +646,44 @@ pub(crate) mod laddu { fn __len__(&self) -> usize { self.0.len() } + /// Get the number of Events in the Dataset + /// + /// Returns + /// ------- + /// n_events : int + /// The number of Events + /// fn len(&self) -> usize { self.0.len() } + /// Get the weighted number of Events in the Dataset + /// + /// Returns + /// ------- + /// n_events : float + /// The sum of all Event weights + /// fn weighted_len(&self) -> Float { self.0.weighted_len() } + /// The weights associated with the Dataset + /// + /// Returns + /// ------- + /// weights : array_like + /// A ``numpy`` array of Event weights + /// #[getter] fn weights<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray1> { PyArray1::from_slice_bound(py, &self.0.weights()) } + /// The internal list of Events stored in the Dataset + /// + /// Returns + /// ------- + /// events : list of Event + /// The Events in the Dataset + /// #[getter] fn events(&self) -> Vec { self.0 @@ -662,6 +698,28 @@ pub(crate) mod laddu { .ok_or(PyIndexError::new_err("index out of range")) .map(|rust_event| Event(rust_event.clone())) } + /// Separates a Dataset into histogram bins by a Variable value + /// + /// Currently supports ``laddu.Mass`` as the binning variable. + /// + /// Parameters + /// ---------- + /// variable : Mass + /// The Variable by which each Event is binned + /// bins : int + /// The number of equally-spaced bins + /// range : tuple[float, float] + /// The minimum and maximum bin edges + /// + /// Returns + /// ------- + /// datasets : BinnedDataset + /// A structure that holds a list of Datasets binned by the given `variable` + /// + /// See Also + /// -------- + /// laddu.Mass + /// #[pyo3(signature = (variable, bins, range))] fn bin_by( &self, @@ -676,11 +734,33 @@ pub(crate) mod laddu { }; Ok(BinnedDataset(self.0.bin_by(rust_variable, bins, range))) } + /// Generate a new bootstrapped Dataset by randomly resampling the original with replacement + /// + /// The new Dataset is resampled with a random generator seeded by the provided `seed` + /// + /// Parameters + /// ---------- + /// seed : int + /// The random seed used in the resampling process + /// + /// Returns + /// ------- + /// Dataset + /// A bootstrapped Dataset + /// fn bootstrap(&self, seed: usize) -> Dataset { Dataset(self.0.bootstrap(seed)) } } + /// A collection of Datasets binned by a Variable + /// + /// BinnedDatasets can be indexed directly to access the underlying Datasets by bin + /// + /// See Also + /// -------- + /// laddu.Dataset.bin_by + /// #[pyclass] struct BinnedDataset(rust::data::BinnedDataset); @@ -689,17 +769,29 @@ pub(crate) mod laddu { fn __len__(&self) -> usize { self.0.len() } + /// Get the number of bins in the BinnedDataset + /// + /// Returns + /// ------- + /// n : int + /// The number of bins fn len(&self) -> usize { self.0.len() } + /// The number of bins in the BinnedDataset + /// #[getter] fn bins(&self) -> usize { self.0.bins() } + /// The minimum and maximum values of the binning Variable used to create this BinnedDataset + /// #[getter] fn range(&self) -> (Float, Float) { self.0.range() } + /// The edges of each bin in the BinnedDataset + /// #[getter] fn edges<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray1> { PyArray1::from_slice_bound(py, &self.0.edges()) @@ -712,11 +804,48 @@ pub(crate) mod laddu { } } + /// Open a Dataset from a file + /// + /// Data should be stored in Parquet format with each column being filled with 32-bit floats + /// + /// Valid/required column names have the following formats: + /// + /// ``p4_{particle index}_{E|Px|Py|Pz}`` (four-momentum components for each particle) + /// + /// ``eps_{particle index}_{x|y|z}`` (polarization/helicity vectors for each particle) + /// + /// ``weight`` (the weight of the Event) + /// + /// For example, the four-momentum of the 0th particle in the event would be stored in columns + /// with the names ``p4_0_E``, ``p4_0_Px``, ``p4_0_Py``, and ``p4_0_Pz``. That particle's + /// polarization could be stored in the columns ``eps_0_x``, ``eps_0_y``, and ``eps_0_z``. This + /// could continue for an arbitrary number of particles. The ``weight`` column is always + /// required. + /// #[pyfunction] fn open(path: &str) -> PyResult { Ok(Dataset(rust::data::open(path)?)) } + /// The invariant mass of an arbitrary combination of constituent particles in an Event + /// + /// This variable is calculated by summing up the 4-momenta of each particle listed by index in + /// `constituents` and taking the invariant magnitude of the resulting 4-vector. + /// + /// Parameters + /// ---------- + /// constituents : list of int + /// The indices of particles to combine to create the final 4-momentum + /// + /// Returns + /// ------- + /// mass_variable : Mass + /// A Variable that corresponds to the mass of the constituent particles + /// + /// See Also + /// -------- + /// laddu.utils.vectors.Vector4.m + /// #[pyclass] #[derive(Clone)] struct Mass(rust::utils::variables::Mass); @@ -727,14 +856,82 @@ pub(crate) mod laddu { fn new(constituents: Vec) -> Self { Self(rust::utils::variables::Mass::new(&constituents)) } + /// The value of this Variable for the given Event + /// + /// Parameters + /// ---------- + /// event : Event + /// The Event upon which the Variable is calculated + /// + /// Returns + /// ------- + /// value : float + /// The value of the Variable for the given `event` + /// fn value(&self, event: &Event) -> Float { self.0.value(&event.0) } + /// All values of this Variable on the given Dataset + /// + /// Parameters + /// ---------- + /// dataset : Dataset + /// The Dataset upon which the Variable is calculated + /// + /// Returns + /// ------- + /// values : array_like + /// The values of the Variable for each Event in the given `dataset` + /// fn value_on<'py>(&self, py: Python<'py>, dataset: &Dataset) -> Bound<'py, PyArray1> { PyArray1::from_slice_bound(py, &self.0.value_on(&dataset.0)) } } + /// The cosine of the polar decay angle in the rest frame of the given `resonance` + /// + /// This Variable is calculated by forming the given frame (helicity or Gottfried-Jackson) and + /// calculating the spherical angles according to one of the decaying `daughter` particles. + /// + /// The helicity frame is defined in terms of the following Cartesian axes in the rest frame of + /// the `resonance`: + /// + /// .. math:: \hat{z} \propto -\vec{p}'_{\text{recoil}} + /// .. math:: \hat{y} \propto \vec{p}_{\text{beam}} \times (-\vec{p}_{\text{recoil}}) + /// .. math:: \hat{x} = \hat{y} \times \hat{z} + /// + /// where primed vectors are in the rest frame of the `resonance` and unprimed vectors are in + /// the center-of-momentum frame. + /// + /// The Gottfried-Jackson frame differs only in the definition of :math:`\hat{z}`: + /// + /// .. math:: \hat{z} \propto \vec{p}'_{\text{beam}} + /// + /// Parameters + /// ---------- + /// beam : int + /// The index of the `beam` particle + /// recoil : list of int + /// Indices of particles which are combined to form the recoiling particle (particles which + /// are not `beam` or part of the `resonance`) + /// daughter : list of int + /// Indices of particles which are combined to form one of the decay products of the + /// `resonance` + /// resonance : list of int + /// Indices of particles which are combined to form the `resonance` + /// frame : {'Helicity', 'HX', 'HEL', 'GottfriedJackson', 'Gottfried Jackson', 'GJ', 'Gottfried-Jackson'} + /// The frame to use in the calculation + /// + /// + /// Returns + /// ------- + /// costheta : CosTheta + /// A Variable that corresponds to the cosine of the polar decay angle in the given frame + /// + /// See Also + /// -------- + /// laddu.utils.vectors.Vector3.costheta + /// #[pyclass] #[derive(Clone)] struct CosTheta(rust::utils::variables::CosTheta); @@ -758,14 +955,82 @@ pub(crate) mod laddu { frame.parse().unwrap(), )) } + /// The value of this Variable for the given Event + /// + /// Parameters + /// ---------- + /// event : Event + /// The Event upon which the Variable is calculated + /// + /// Returns + /// ------- + /// value : float + /// The value of the Variable for the given `event` + /// fn value(&self, event: &Event) -> Float { self.0.value(&event.0) } + /// All values of this Variable on the given Dataset + /// + /// Parameters + /// ---------- + /// dataset : Dataset + /// The Dataset upon which the Variable is calculated + /// + /// Returns + /// ------- + /// values : array_like + /// The values of the Variable for each Event in the given `dataset` + /// fn value_on<'py>(&self, py: Python<'py>, dataset: &Dataset) -> Bound<'py, PyArray1> { PyArray1::from_slice_bound(py, &self.0.value_on(&dataset.0)) } } + /// The aziumuthal decay angle in the rest frame of the given `resonance` + /// + /// This Variable is calculated by forming the given frame (helicity or Gottfried-Jackson) and + /// calculating the spherical angles according to one of the decaying `daughter` particles. + /// + /// The helicity frame is defined in terms of the following Cartesian axes in the rest frame of + /// the `resonance`: + /// + /// .. math:: \hat{z} \propto -\vec{p}'_{\text{recoil}} + /// .. math:: \hat{y} \propto \vec{p}_{\text{beam}} \times (-\vec{p}_{\text{recoil}}) + /// .. math:: \hat{x} = \hat{y} \times \hat{z} + /// + /// where primed vectors are in the rest frame of the `resonance` and unprimed vectors are in + /// the center-of-momentum frame. + /// + /// The Gottfried-Jackson frame differs only in the definition of :math:`\hat{z}`: + /// + /// .. math:: \hat{z} \propto \vec{p}'_{\text{beam}} + /// + /// Parameters + /// ---------- + /// beam : int + /// The index of the `beam` particle + /// recoil : list of int + /// Indices of particles which are combined to form the recoiling particle (particles which + /// are not `beam` or part of the `resonance`) + /// daughter : list of int + /// Indices of particles which are combined to form one of the decay products of the + /// `resonance` + /// resonance : list of int + /// Indices of particles which are combined to form the `resonance` + /// frame : {'Helicity', 'HX', 'HEL', 'GottfriedJackson', 'Gottfried Jackson', 'GJ', 'Gottfried-Jackson'} + /// The frame to use in the calculation + /// + /// + /// Returns + /// ------- + /// phi : Phi + /// A Variable that corresponds to the azimuthal decay angle in the given frame + /// + /// See Also + /// -------- + /// laddu.utils.vectors.Vector3.phi + /// #[pyclass] #[derive(Clone)] struct Phi(rust::utils::variables::Phi); @@ -789,14 +1054,69 @@ pub(crate) mod laddu { frame.parse().unwrap(), )) } + /// The value of this Variable for the given Event + /// + /// Parameters + /// ---------- + /// event : Event + /// The Event upon which the Variable is calculated + /// + /// Returns + /// ------- + /// value : float + /// The value of the Variable for the given `event` + /// fn value(&self, event: &Event) -> Float { self.0.value(&event.0) } + /// All values of this Variable on the given Dataset + /// + /// Parameters + /// ---------- + /// dataset : Dataset + /// The Dataset upon which the Variable is calculated + /// + /// Returns + /// ------- + /// values : array_like + /// The values of the Variable for each Event in the given `dataset` + /// fn value_on<'py>(&self, py: Python<'py>, dataset: &Dataset) -> Bound<'py, PyArray1> { PyArray1::from_slice_bound(py, &self.0.value_on(&dataset.0)) } } + /// A Variable used to define both spherical decay angles in the given frame + /// + /// This class combines ``laddu.CosTheta`` and ``laddu.Phi`` into a single + /// object + /// + /// Parameters + /// ---------- + /// beam : int + /// The index of the `beam` particle + /// recoil : list of int + /// Indices of particles which are combined to form the recoiling particle (particles which + /// are not `beam` or part of the `resonance`) + /// daughter : list of int + /// Indices of particles which are combined to form one of the decay products of the + /// `resonance` + /// resonance : list of int + /// Indices of particles which are combined to form the `resonance` + /// frame : {'Helicity', 'HX', 'HEL', 'GottfriedJackson', 'Gottfried Jackson', 'GJ', 'Gottfried-Jackson'} + /// The frame to use in the calculation + /// + /// Returns + /// ------- + /// angles : Angles + /// A set of Variables corresponding to the spherical decay angles of a particle in the + /// given frame + /// + /// See Also + /// -------- + /// laddu.CosTheta + /// laddu.Phi + /// #[pyclass] #[derive(Clone)] struct Angles(rust::utils::variables::Angles); @@ -829,6 +1149,25 @@ pub(crate) mod laddu { } } + /// The polar angle of the given polarization vector with respect to the production plane + /// + /// The `beam` and `recoil` particles define the plane of production, and this Variable + /// describes the polar angle of the `beam` relative to this plane + /// + /// Parameters + /// ---------- + /// beam : int + /// The index of the `beam` particle + /// recoil : list of int + /// Indices of particles which are combined to form the recoiling particle (particles which + /// are not `beam` or part of the `resonance`) + /// + /// Returns + /// ------- + /// pol_angle : PolAngle + /// A Variable describing the polar angle of the polarization vector with respect to the + /// production plane + /// #[pyclass] #[derive(Clone)] struct PolAngle(rust::utils::variables::PolAngle); @@ -839,14 +1178,57 @@ pub(crate) mod laddu { fn new(beam: usize, recoil: Vec) -> Self { Self(rust::utils::variables::PolAngle::new(beam, &recoil)) } + /// The value of this Variable for the given Event + /// + /// Parameters + /// ---------- + /// event : Event + /// The Event upon which the Variable is calculated + /// + /// Returns + /// ------- + /// value : float + /// The value of the Variable for the given `event` + /// fn value(&self, event: &Event) -> Float { self.0.value(&event.0) } + /// All values of this Variable on the given Dataset + /// + /// Parameters + /// ---------- + /// dataset : Dataset + /// The Dataset upon which the Variable is calculated + /// + /// Returns + /// ------- + /// values : array_like + /// The values of the Variable for each Event in the given `dataset` + /// fn value_on<'py>(&self, py: Python<'py>, dataset: &Dataset) -> Bound<'py, PyArray1> { PyArray1::from_slice_bound(py, &self.0.value_on(&dataset.0)) } } + /// The magnitude of the given particle's polarization vector + /// + /// This Variable simply represents the magnitude of the polarization vector of the particle + /// with the index `beam` + /// + /// Parameters + /// ---------- + /// beam : int + /// The index of the `beam` particle + /// + /// Returns + /// ------- + /// pol_mag : PolMagnitude + /// A Variable representing the magnitude of the given polarization vector + /// + /// See Also + /// -------- + /// laddu.utils.vectors.Vector3.mag + /// #[pyclass] #[derive(Clone)] struct PolMagnitude(rust::utils::variables::PolMagnitude); @@ -857,14 +1239,61 @@ pub(crate) mod laddu { fn new(beam: usize) -> Self { Self(rust::utils::variables::PolMagnitude::new(beam)) } + /// The value of this Variable for the given Event + /// + /// Parameters + /// ---------- + /// event : Event + /// The Event upon which the Variable is calculated + /// + /// Returns + /// ------- + /// value : float + /// The value of the Variable for the given `event` + /// fn value(&self, event: &Event) -> Float { self.0.value(&event.0) } + /// All values of this Variable on the given Dataset + /// + /// Parameters + /// ---------- + /// dataset : Dataset + /// The Dataset upon which the Variable is calculated + /// + /// Returns + /// ------- + /// values : array_like + /// The values of the Variable for each Event in the given `dataset` + /// fn value_on<'py>(&self, py: Python<'py>, dataset: &Dataset) -> Bound<'py, PyArray1> { PyArray1::from_slice_bound(py, &self.0.value_on(&dataset.0)) } } + /// A Variable used to define both the polarization angle and magnitude of the given particle`` + /// + /// This class combines ``laddu.PolAngle`` and ``laddu.PolMagnitude`` into a single + /// object + /// + /// Parameters + /// ---------- + /// beam : int + /// The index of the `beam` particle + /// recoil : list of int + /// Indices of particles which are combined to form the recoiling particle (particles which + /// are not `beam` or part of the `resonance`) + /// + /// Returns + /// ------- + /// polarization : Polarization + /// A set of Variables corresponding to the polarization angle and magnitude of the `beam` + /// + /// See Also + /// -------- + /// laddu.PolAngle + /// laddu.PolMagnitude + /// #[pyclass] #[derive(Clone)] struct Polarization(rust::utils::variables::Polarization); @@ -884,22 +1313,53 @@ pub(crate) mod laddu { } } + /// An object which holds a registered ``Amplitude`` + /// + /// See Also + /// -------- + /// laddu.Manager.register + /// #[pyclass] #[derive(Clone)] struct AmplitudeID(rust::amplitudes::AmplitudeID); + /// A mathematical expression formed from AmplitudeIDs + /// #[pyclass] #[derive(Clone)] pub(crate) struct Expression(pub(crate) rust::amplitudes::Expression); #[pymethods] impl AmplitudeID { + /// The real part of a complex Amplitude + /// + /// Returns + /// ------- + /// Expression + /// The real part of the given Amplitude + /// fn real(&self) -> Expression { Expression(self.0.real()) } + /// The imaginary part of a complex Amplitude + /// + /// Returns + /// ------- + /// Expression + /// The imaginary part of the given Amplitude + /// fn imag(&self) -> Expression { Expression(self.0.imag()) } + /// The norm-squared of a complex Amplitude + /// + /// This is computed as :math:`AA^*` where :math:`A^*` is the complex conjugate + /// + /// Returns + /// ------- + /// Expression + /// The norm-squared of the given Amplitude + /// fn norm_sqr(&self) -> Expression { Expression(self.0.norm_sqr()) } @@ -931,12 +1391,35 @@ pub(crate) mod laddu { #[pymethods] impl Expression { + /// The real part of a complex Expression + /// + /// Returns + /// ------- + /// Expression + /// The real part of the given Expression + /// fn real(&self) -> Expression { Expression(self.0.real()) } + /// The imaginary part of a complex Expression + /// + /// Returns + /// ------- + /// Expression + /// The imaginary part of the given Expression + /// fn imag(&self) -> Expression { Expression(self.0.imag()) } + /// The norm-squared of a complex Expression + /// + /// This is computed as :math:`AA^*` where :math:`A^*` is the complex conjugate + /// + /// Returns + /// ------- + /// Expression + /// The norm-squared of the given Expression + /// fn norm_sqr(&self) -> Expression { Expression(self.0.norm_sqr()) } @@ -966,9 +1449,17 @@ pub(crate) mod laddu { } } + /// A class which can be used to register Amplitudes and store precalculated data + /// #[pyclass] struct Manager(rust::amplitudes::Manager); + /// An Amplitude which can be registered by a Manager + /// + /// See Also + /// -------- + /// laddu.Manager + /// #[pyclass] struct Amplitude(Box); @@ -978,24 +1469,89 @@ pub(crate) mod laddu { fn new() -> Self { Self(rust::amplitudes::Manager::default()) } + /// Register an Amplitude with the Manager + /// + /// Parameters + /// ---------- + /// amplitude : Amplitude + /// The Amplitude to register + /// + /// Returns + /// ------- + /// AmplitudeID + /// A reference to the registered `amplitude` that can be used to form complex + /// Expressions + /// + /// Raises + /// ------ + /// ValueError + /// If the name of the `amplitude` has already been registered + /// fn register(&mut self, amplitude: &Amplitude) -> PyResult { Ok(AmplitudeID(self.0.register(amplitude.0.clone())?)) } + /// Load an Expression by precalculating each term over the given Dataset + /// + /// Parameters + /// ---------- + /// dataset : Dataset + /// The Dataset to use in precalculation + /// expression : Expression + /// The expression to use in precalculation + /// + /// Returns + /// ------- + /// Evaluator + /// An object that can be used to evaluate the `expression` over each event in the + /// `dataset` + /// + /// Notes + /// ----- + /// While the given `expression` will be the one evaluated in the end, all registered + /// Amplitudes will be loaded, and all of their parameters will be included in the final + /// expression. These parameters will have no effect on evaluation, but they must be + /// included in function calls. + /// fn load(&self, dataset: &Dataset, expression: &Expression) -> Evaluator { Evaluator(self.0.load(&dataset.0, &expression.0)) } } + /// A class which can be used to evaluate a stored Expression + /// + /// See Also + /// -------- + /// laddu.Manager.load + /// #[pyclass] #[derive(Clone)] struct Evaluator(rust::amplitudes::Evaluator); #[pymethods] impl Evaluator { + /// The free parameters used by the Evaluator + /// + /// Returns + /// ------- + /// parameters : list of str + /// The list of parameter names + /// #[getter] fn parameters(&self) -> Vec { self.0.parameters() } + /// Activates Amplitudes in the Expression by name + /// + /// Parameters + /// ---------- + /// arg : str or list of str + /// Names of Amplitudes to be activated + /// + /// Raises + /// ------ + /// TypeError + /// If `arg` is not a str or list of str + /// fn activate(&self, arg: &Bound<'_, PyAny>) -> PyResult<()> { if let Ok(string_arg) = arg.extract::() { self.0.activate(&string_arg); @@ -1009,9 +1565,25 @@ pub(crate) mod laddu { } Ok(()) } + /// Activates all Amplitudes in the Expression + /// fn activate_all(&self) { self.0.activate_all(); } + /// Deactivates Amplitudes in the Expression by name + /// + /// Deactivated Amplitudes act as zeros in the Expression + /// + /// Parameters + /// ---------- + /// arg : str or list of str + /// Names of Amplitudes to be deactivated + /// + /// Raises + /// ------ + /// TypeError + /// If `arg` is not a str or list of str + /// fn deactivate(&self, arg: &Bound<'_, PyAny>) -> PyResult<()> { if let Ok(string_arg) = arg.extract::() { self.0.deactivate(&string_arg); @@ -1025,9 +1597,25 @@ pub(crate) mod laddu { } Ok(()) } + /// Deactivates all Amplitudes in the Expression + /// fn deactivate_all(&self) { self.0.deactivate_all(); } + /// Isolates Amplitudes in the Expression by name + /// + /// Activates the Amplitudes given in `arg` and deactivates the rest + /// + /// Parameters + /// ---------- + /// arg : str or list of str + /// Names of Amplitudes to be isolated + /// + /// Raises + /// ------ + /// TypeError + /// If `arg` is not a str or list of str + /// fn isolate(&self, arg: &Bound<'_, PyAny>) -> PyResult<()> { if let Ok(string_arg) = arg.extract::() { self.0.isolate(&string_arg); @@ -1041,6 +1629,18 @@ pub(crate) mod laddu { } Ok(()) } + /// Evaluate the stored Expression over the stored Dataset + /// + /// Parameters + /// ---------- + /// parameters : list of float + /// The values to use for the free parameters + /// + /// Returns + /// ------- + /// result : array_like + /// A ``numpy`` array of complex values for each Event in the Dataset + /// fn evaluate<'py>( &self, py: Python<'py>, @@ -1215,6 +1815,23 @@ pub(crate) mod laddu { Ok(options) } + /// A (extended) negative log-likelihood evaluator + /// + /// Parameters + /// ---------- + /// manager : Manager + /// The Manager to use for precalculation + /// ds_data : Dataset + /// A Dataset representing true signal data + /// ds_mc : Dataset + /// A Dataset of physically flat Monte Carlo data used for normalization + /// expression : Expression + /// The Expression to evaluate + /// + /// Returns + /// ------- + /// NLL + /// The negative log-likelihood evaluator #[pyclass] #[derive(Clone)] struct NLL(Box); From ffd1b03c61d61cc3cf2f5085368ba2fe19356c72 Mon Sep 17 00:00:00 2001 From: denehoffman Date: Thu, 7 Nov 2024 13:04:43 -0500 Subject: [PATCH 21/26] feat: add methods to serialize/deserialize fit results --- Cargo.toml | 6 ++- python/laddu/likelihoods/__init__.pyi | 7 +++- src/data.rs | 4 +- src/lib.rs | 18 ++++++--- src/likelihoods.rs | 54 ++++++++++++++++++++++++++- src/python.rs | 9 +++++ 6 files changed, 86 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4aeb238..133b038 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,10 +34,12 @@ pyo3 = { version = "0.22.5", optional = true, features = [ "abi3-py37", ] } numpy = { version = "0.22.0", optional = true, features = ["nalgebra"] } -ganesh = "0.12.2" -thiserror = "1.0.64" +ganesh = "0.13.0" +thiserror = "2.0.0" shellexpand = "3.1.0" accurate = "0.4.1" +serde = "1.0.214" +serde-pickle = "1.1.1" [dev-dependencies] approx = "0.5.1" diff --git a/python/laddu/likelihoods/__init__.pyi b/python/laddu/likelihoods/__init__.pyi index 976a67b..e94dda4 100644 --- a/python/laddu/likelihoods/__init__.pyi +++ b/python/laddu/likelihoods/__init__.pyi @@ -1,4 +1,5 @@ -from typing import Literal, Sequence +from collections.abc import Sequence +from typing import Literal import numpy as np import numpy.typing as npt @@ -74,6 +75,10 @@ class Status: n_f_evals: int n_g_evals: int + def save_as(self, path: str): ... + @staticmethod + def load(path: str) -> Status: ... + class Bound: lower: float upper: float diff --git a/src/data.rs b/src/data.rs index f3ddeb3..619ade3 100644 --- a/src/data.rs +++ b/src/data.rs @@ -386,8 +386,8 @@ fn batch_to_event(batch: &RecordBatch, row: usize) -> Event { /// Open a Parquet file and read the data into a [`Dataset`]. #[cfg(feature = "rayon")] -pub fn open(file_path: &str) -> Result, LadduError> { - let file_path = Path::new(&*shellexpand::full(file_path)?).canonicalize()?; +pub fn open>(file_path: T) -> Result, LadduError> { + let file_path = Path::new(&*shellexpand::full(file_path.as_ref())?).canonicalize()?; let file = File::open(file_path)?; let builder = ParquetRecordBatchReaderBuilder::try_new(file)?; let reader = builder.build()?; diff --git a/src/lib.rs b/src/lib.rs index 46a256a..c45988e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -290,7 +290,7 @@ pub mod prelude { pub use crate::data::{open, BinnedDataset, Dataset, Event}; pub use crate::likelihoods::{ LikelihoodEvaluator, LikelihoodExpression, LikelihoodID, LikelihoodManager, LikelihoodTerm, - MinimizerOptions, NLL, + MinimizerOptions, ReadWrite, NLL, }; pub use crate::resources::{ Cache, ComplexMatrixID, ComplexScalarID, ComplexVectorID, MatrixID, ParameterID, @@ -302,6 +302,7 @@ pub mod prelude { }; pub use crate::utils::vectors::{FourMomentum, FourVector, ThreeMomentum, ThreeVector}; pub use crate::{Float, LadduError, PI}; + pub use ganesh::Status; pub use nalgebra::{DVector, Vector3, Vector4}; pub use num::Complex; } @@ -348,6 +349,9 @@ pub enum LadduError { /// Name of amplitude which is already registered name: String, }, + /// An error returned by the Python pickle (de)serializer + #[error("Pickle conversion error: {0}")] + PickleError(#[from] serde_pickle::Error), /// A custom fallback error for errors too complex or too infrequent to warrant their own error /// category. #[error("{0}")] @@ -360,11 +364,13 @@ impl From for PyErr { use pyo3::exceptions::*; let err_string = err.to_string(); match err { - LadduError::ParquetError(_) => PyIOError::new_err(err_string), - LadduError::ArrowError(_) => PyIOError::new_err(err_string), - LadduError::IOError(_) => PyIOError::new_err(err_string), - LadduError::LookupError(_) => PyValueError::new_err(err_string), - LadduError::RegistrationError { .. } => PyValueError::new_err(err_string), + LadduError::LookupError(_) | LadduError::RegistrationError { .. } => { + PyValueError::new_err(err_string) + } + LadduError::ParquetError(_) + | LadduError::ArrowError(_) + | LadduError::IOError(_) + | LadduError::PickleError(_) => PyIOError::new_err(err_string), LadduError::Custom(_) => PyException::new_err(err_string), } } diff --git a/src/likelihoods.rs b/src/likelihoods.rs index 5431ac7..f020167 100644 --- a/src/likelihoods.rs +++ b/src/likelihoods.rs @@ -2,6 +2,9 @@ use std::{ collections::HashMap, convert::Infallible, fmt::{Debug, Display}, + fs::File, + io::{BufReader, BufWriter}, + path::Path, sync::Arc, }; @@ -9,7 +12,7 @@ use crate::{ amplitudes::{AmplitudeValues, Evaluator, Expression, GradientValues, Manager}, data::Dataset, resources::Parameters, - Float, + Float, LadduError, }; use accurate::{sum::Klein, traits::*}; use auto_ops::*; @@ -22,6 +25,55 @@ use num::Complex; #[cfg(feature = "rayon")] use rayon::prelude::*; +use serde::{de::DeserializeOwned, Serialize}; + +/// A trait which allows structs with [`Serialize`] and [`Deserialize`](`serde::Deserialize`) to be +/// written and read from files with a certain set of types/extensions. +/// +/// Currently, Python's pickle format is supported supported, since it's an easy-to-parse standard +/// that supports floating point values better that JSON or TOML +pub trait ReadWrite: Serialize + DeserializeOwned { + /// Save a [`serde`]-object to a file path, using the extension to determine the file format + fn save_as>(&self, file_path: T) -> Result<(), LadduError> { + let expanded_path = shellexpand::full(file_path.as_ref())?; + let file_path = Path::new(expanded_path.as_ref()); + let extension = file_path + .extension() + .and_then(|ext| ext.to_str().map(|ext| ext.to_string())) + .unwrap_or("".to_string()); + let file = File::create(file_path)?; + let mut writer = BufWriter::new(file); + match extension.as_str() { + "pkl" | "pickle" => serde_pickle::to_writer(&mut writer, self, Default::default())?, + _ => { + return Err(LadduError::Custom(format!( + "Unsupported file extension: {}\nValid options are \".pkl\" or \".pickle\"", + extension + ))) + } + }; + Ok(()) + } + /// Load a [`serde`]-object from a file path, using the extension to determine the file format + fn load>(file_path: T) -> Result { + let file_path = Path::new(&*shellexpand::full(file_path.as_ref())?).canonicalize()?; + let extension = file_path + .extension() + .and_then(|ext| ext.to_str().map(|ext| ext.to_string())) + .unwrap_or("".to_string()); + let file = File::open(file_path.clone())?; + let reader = BufReader::new(file); + match extension.as_str() { + "pkl" | "pickle" => Ok(serde_pickle::from_reader(reader, Default::default())?), + _ => Err(LadduError::Custom(format!( + "Unsupported file extension: {}\nValid options are \".pkl\" or \".pickle\"", + extension + ))), + } + } +} + +impl ReadWrite for Status {} /// A trait which describes a term that can be used like a likelihood (more correctly, a negative /// log-likelihood) in a minimization. diff --git a/src/python.rs b/src/python.rs index e7b6262..9f21969 100644 --- a/src/python.rs +++ b/src/python.rs @@ -16,6 +16,7 @@ pub(crate) mod laddu { use crate as rust; use crate::likelihoods::LikelihoodTerm as RustLikelihoodTerm; use crate::likelihoods::MinimizerOptions; + use crate::prelude::ReadWrite; use crate::utils::variables::Variable; use crate::utils::vectors::{FourMomentum, FourVector, ThreeMomentum, ThreeVector}; use crate::Float; @@ -2180,6 +2181,14 @@ pub(crate) mod laddu { fn __repr__(&self) -> String { format!("{:?}", self.0) } + fn save_as(&self, path: &str) -> PyResult<()> { + self.0.save_as(path)?; + Ok(()) + } + #[staticmethod] + fn load(path: &str) -> PyResult { + Ok(Status(ganesh::Status::load(path)?)) + } } #[pyclass] From f547445a2e70c441b494946883b75d69e193107d Mon Sep 17 00:00:00 2001 From: denehoffman Date: Thu, 7 Nov 2024 13:05:10 -0500 Subject: [PATCH 22/26] style: resolve lint warning of `len` without `is_empty` --- src/resources.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/resources.rs b/src/resources.rs index 26e525f..ba4ae97 100644 --- a/src/resources.rs +++ b/src/resources.rs @@ -35,6 +35,7 @@ impl<'a> Parameters<'a> { } /// The number of free parameters. + #[allow(clippy::len_without_is_empty)] pub fn len(&self) -> usize { self.parameters.len() } From 2e2d5730d7e29093724aa16f99e6db82fc4daa81 Mon Sep 17 00:00:00 2001 From: denehoffman Date: Thu, 7 Nov 2024 15:26:06 -0500 Subject: [PATCH 23/26] docs: fix typo in K-Matrix Rust docs --- src/amplitudes/kmatrix.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/amplitudes/kmatrix.rs b/src/amplitudes/kmatrix.rs index 2c24fbf..7f8522c 100644 --- a/src/amplitudes/kmatrix.rs +++ b/src/amplitudes/kmatrix.rs @@ -702,7 +702,7 @@ impl KopfKMatrixRho { /// | ------------- | ------- | /// | 0 | $`\pi\pi`$ | /// | 1 | $`2\pi 2\pi`$ | - /// | 1 | $`K\bar{K}`$ | + /// | 2 | $`K\bar{K}`$ | /// /// | Pole names | /// | ---------- | From 247300e31c0bd534984cf4752daf0b2cce52757b Mon Sep 17 00:00:00 2001 From: denehoffman Date: Thu, 7 Nov 2024 15:26:37 -0500 Subject: [PATCH 24/26] docs: finish first pass documenting Python API This is just rough documentation, but I think it's better to at least have something out there --- src/python.rs | 987 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 928 insertions(+), 59 deletions(-) diff --git a/src/python.rs b/src/python.rs index 9f21969..a0385ed 100644 --- a/src/python.rs +++ b/src/python.rs @@ -42,11 +42,6 @@ pub(crate) mod laddu { /// px, py, pz : float /// The Cartesian components of the 3-vector /// - /// Returns - /// ------- - /// Vector3 - /// A new 3-momentum vector made from the given components - /// #[pyclass] #[derive(Clone)] struct Vector3(nalgebra::Vector3); @@ -260,10 +255,6 @@ pub(crate) mod laddu { /// px, py, pz : float /// The Cartesian components of the 3-vector /// - /// Returns - /// ------- - /// Vector4 - /// A new 4-momentum vector made from the given components /// #[pyclass] #[derive(Clone)] @@ -575,11 +566,6 @@ pub(crate) mod laddu { /// weight : float /// The weight associated with this event /// - /// Returns - /// ------- - /// event : Event - /// An event formed from the given components - /// #[pyclass] #[derive(Clone)] struct Event(Arc); @@ -838,11 +824,6 @@ pub(crate) mod laddu { /// constituents : list of int /// The indices of particles to combine to create the final 4-momentum /// - /// Returns - /// ------- - /// mass_variable : Mass - /// A Variable that corresponds to the mass of the constituent particles - /// /// See Also /// -------- /// laddu.utils.vectors.Vector4.m @@ -923,12 +904,6 @@ pub(crate) mod laddu { /// frame : {'Helicity', 'HX', 'HEL', 'GottfriedJackson', 'Gottfried Jackson', 'GJ', 'Gottfried-Jackson'} /// The frame to use in the calculation /// - /// - /// Returns - /// ------- - /// costheta : CosTheta - /// A Variable that corresponds to the cosine of the polar decay angle in the given frame - /// /// See Also /// -------- /// laddu.utils.vectors.Vector3.costheta @@ -1022,12 +997,6 @@ pub(crate) mod laddu { /// frame : {'Helicity', 'HX', 'HEL', 'GottfriedJackson', 'Gottfried Jackson', 'GJ', 'Gottfried-Jackson'} /// The frame to use in the calculation /// - /// - /// Returns - /// ------- - /// phi : Phi - /// A Variable that corresponds to the azimuthal decay angle in the given frame - /// /// See Also /// -------- /// laddu.utils.vectors.Vector3.phi @@ -1107,12 +1076,6 @@ pub(crate) mod laddu { /// frame : {'Helicity', 'HX', 'HEL', 'GottfriedJackson', 'Gottfried Jackson', 'GJ', 'Gottfried-Jackson'} /// The frame to use in the calculation /// - /// Returns - /// ------- - /// angles : Angles - /// A set of Variables corresponding to the spherical decay angles of a particle in the - /// given frame - /// /// See Also /// -------- /// laddu.CosTheta @@ -1140,10 +1103,22 @@ pub(crate) mod laddu { frame.parse().unwrap(), )) } + /// The Variable representing the cosine of the polar spherical decay angle + /// + /// Returns + /// ------- + /// CosTheta + /// #[getter] fn costheta(&self) -> CosTheta { CosTheta(self.0.costheta.clone()) } + // The Variable representing the polar azimuthal decay angle + // + // Returns + // ------- + // Phi + // #[getter] fn phi(&self) -> Phi { Phi(self.0.phi.clone()) @@ -1163,12 +1138,6 @@ pub(crate) mod laddu { /// Indices of particles which are combined to form the recoiling particle (particles which /// are not `beam` or part of the `resonance`) /// - /// Returns - /// ------- - /// pol_angle : PolAngle - /// A Variable describing the polar angle of the polarization vector with respect to the - /// production plane - /// #[pyclass] #[derive(Clone)] struct PolAngle(rust::utils::variables::PolAngle); @@ -1221,11 +1190,6 @@ pub(crate) mod laddu { /// beam : int /// The index of the `beam` particle /// - /// Returns - /// ------- - /// pol_mag : PolMagnitude - /// A Variable representing the magnitude of the given polarization vector - /// /// See Also /// -------- /// laddu.utils.vectors.Vector3.mag @@ -1285,11 +1249,6 @@ pub(crate) mod laddu { /// Indices of particles which are combined to form the recoiling particle (particles which /// are not `beam` or part of the `resonance`) /// - /// Returns - /// ------- - /// polarization : Polarization - /// A set of Variables corresponding to the polarization angle and magnitude of the `beam` - /// /// See Also /// -------- /// laddu.PolAngle @@ -1304,10 +1263,22 @@ pub(crate) mod laddu { fn new(beam: usize, recoil: Vec) -> Self { Polarization(rust::utils::variables::Polarization::new(beam, &recoil)) } + /// The Variable representing the magnitude of the polarization vector + /// + /// Returns + /// ------- + /// PolMagnitude + /// #[getter] fn pol_magnitude(&self) -> PolMagnitude { PolMagnitude(self.0.pol_magnitude) } + /// The Variable representing the polar angle of the polarization vector + /// + /// Returns + /// ------- + /// PolAngle + /// #[getter] fn pol_angle(&self) -> PolAngle { PolAngle(self.0.pol_angle.clone()) @@ -1486,7 +1457,7 @@ pub(crate) mod laddu { /// Raises /// ------ /// ValueError - /// If the name of the `amplitude` has already been registered + /// If the name of the ``amplitude`` has already been registered /// fn register(&mut self, amplitude: &Amplitude) -> PyResult { Ok(AmplitudeID(self.0.register(amplitude.0.clone())?)) @@ -1829,10 +1800,6 @@ pub(crate) mod laddu { /// expression : Expression /// The Expression to evaluate /// - /// Returns - /// ------- - /// NLL - /// The negative log-likelihood evaluator #[pyclass] #[derive(Clone)] struct NLL(Box); @@ -1853,21 +1820,58 @@ pub(crate) mod laddu { &expression.0, )) } + /// The underlying signal dataset used in calculating the NLL + /// + /// Returns + /// ------- + /// Dataset + /// #[getter] fn data(&self) -> Dataset { Dataset(self.0.data_evaluator.dataset.clone()) } + /// The underlying Monte Carlo dataset used in calculating the NLL + /// + /// Returns + /// ------- + /// Dataset + /// #[getter] fn mc(&self) -> Dataset { Dataset(self.0.mc_evaluator.dataset.clone()) } + /// Turn an ``NLL`` into a term that can be used by a ``LikelihoodManager`` + /// + /// Returns + /// ------- + /// term : LikelihoodTerm + /// The isolated NLL which can be used to build more complex models + /// fn as_term(&self) -> LikelihoodTerm { LikelihoodTerm(self.0.clone()) } + /// The names of the free parameters used to evaluate the NLL + /// + /// Returns + /// ------- + /// parameters : list of str + /// #[getter] fn parameters(&self) -> Vec { self.0.parameters() } + /// Activates Amplitudes in the NLL by name + /// + /// Parameters + /// ---------- + /// arg : str or list of str + /// Names of Amplitudes to be activated + /// + /// Raises + /// ------ + /// TypeError + /// If `arg` is not a str or list of str + /// fn activate(&self, arg: &Bound<'_, PyAny>) -> PyResult<()> { if let Ok(string_arg) = arg.extract::() { self.0.activate(&string_arg); @@ -1881,9 +1885,25 @@ pub(crate) mod laddu { } Ok(()) } + /// Activates all Amplitudes in the JNLL + /// fn activate_all(&self) { self.0.activate_all(); } + /// Deactivates Amplitudes in the NLL by name + /// + /// Deactivated Amplitudes act as zeros in the NLL + /// + /// Parameters + /// ---------- + /// arg : str or list of str + /// Names of Amplitudes to be deactivated + /// + /// Raises + /// ------ + /// TypeError + /// If `arg` is not a str or list of str + /// fn deactivate(&self, arg: &Bound<'_, PyAny>) -> PyResult<()> { if let Ok(string_arg) = arg.extract::() { self.0.deactivate(&string_arg); @@ -1897,9 +1917,25 @@ pub(crate) mod laddu { } Ok(()) } + /// Deactivates all Amplitudes in the NLL + /// fn deactivate_all(&self) { self.0.deactivate_all(); } + /// Isolates Amplitudes in the NLL by name + /// + /// Activates the Amplitudes given in `arg` and deactivates the rest + /// + /// Parameters + /// ---------- + /// arg : str or list of str + /// Names of Amplitudes to be isolated + /// + /// Raises + /// ------ + /// TypeError + /// If `arg` is not a str or list of str + /// fn isolate(&self, arg: &Bound<'_, PyAny>) -> PyResult<()> { if let Ok(string_arg) = arg.extract::() { self.0.isolate(&string_arg); @@ -1913,9 +1949,41 @@ pub(crate) mod laddu { } Ok(()) } + /// Evaluate the extended negative log-likelihood over the stored Datasets + /// + /// This is defined as + /// + /// .. math:: NLL(\vec{p}; D, MC) = -2 \left( \sum_{e \in D} (e_w \log(\mathcal{L}(e) / N_D)) - \frac{1}{N_{MC}} \sum_{e \in MC} (e_w \mathcal{L}(e)) \right) + /// + /// Parameters + /// ---------- + /// parameters : list of float + /// The values to use for the free parameters + /// + /// Returns + /// ------- + /// result : float + /// The total negative log-likelihood + /// fn evaluate(&self, parameters: Vec) -> Float { self.0.evaluate(¶meters) } + /// Project the model over the Monte Carlo dataset with the given parameter values + /// + /// This is defined as + /// + /// .. math:: e_w(\vec{p}) = \frac{e_w}{N_{MC}} \mathcal{L}(e) + /// + /// Parameters + /// ---------- + /// parameters : list of float + /// The values to use for the free parameters + /// + /// Returns + /// ------- + /// result : array_like + /// Weights for every Monte Carlo event which represent the fit to data + /// fn project<'py>( &self, py: Python<'py>, @@ -1923,6 +1991,69 @@ pub(crate) mod laddu { ) -> Bound<'py, PyArray1> { PyArray1::from_slice_bound(py, &self.0.project(¶meters)) } + + /// Minimize the NLL with respect to the free parameters in the model + /// + /// This method "runs the fit". Given an initial position `p0` and optional `bounds`, this + /// method performs a minimization over the negative log-likelihood, optimizing the model + /// over the stored signal data and Monte Carlo. + /// + /// Parameters + /// ---------- + /// p0 : array_like + /// The initial parameters at the start of optimization + /// bounds : list of tuple of float, optional + /// A list of lower and upper bound pairs for each parameter (use ``None`` for no bound) + /// method : {'lbfgsb', 'nelder-mead', 'nelder_mead'} + /// The minimization algorithm to use (see additional parameters for fine-tuning) + /// max_steps : int, default=4000 + /// The maximum number of algorithm steps to perform + /// debug : bool, default=False + /// Set to ``True`` to print out debugging information at each step + /// verbose : bool, default=False + /// Set to ``True`` to print verbose information at each step + /// show_step : bool, default=True + /// Include step number in verbose output + /// show_x : bool, default=True + /// Include current best position in verbose output + /// show_fx : bool, default=True + /// Include current best NLL in verbose output + /// observers : Observer or list of Observers + /// Callback functions which are applied after every algorithm step + /// tol_x_rel : float + /// The relative position tolerance used by termination methods (default is machine + /// epsilon) + /// tol_x_abs : float + /// The absolute position tolerance used by termination methods (default is machine + /// epsilon) + /// tol_f_rel : float + /// The relative function tolerance used by termination methods (default is machine + /// epsilon) + /// tol_f_abs : float + /// The absolute function tolerance used by termination methods (default is machine + /// epsilon) + /// tol_g_abs : float + /// The absolute gradient tolerance used by termination methods (default is the cube + /// root of machine epsilon) + /// g_tolerance : float, default=1e-5 + /// Another gradient tolerance used by termination methods (particularly L-BFGS-B) + /// adaptive : bool, default=False + /// Use adaptive values for Nelder-Mead parameters + /// alpha : float, optional + /// Overwrite the default :math:`\alpha` parameter in the Nelder-Mead algorithm + /// beta : float, optional + /// Overwrite the default :math:`\beta` parameter in the Nelder-Mead algorithm + /// gamma : float, optional + /// Overwrite the default :math:`\gamma` parameter in the Nelder-Mead algorithm + /// delta : float, optional + /// Overwrite the default :math:`\delta` parameter in the Nelder-Mead algorithm + /// simplex_expansion_method : {'greedy_minimization', 'greedy_expansion'} + /// The expansion method used by the Nelder-Mead algorithm + /// nelder_mead_f_terminator : {'stddev', 'absolute', 'stddev', 'none'} + /// The function terminator used by the Nelder-Mead algorithm + /// nelder_mead_x_terminator : {'singer', 'diameter', 'rowan', 'higham', 'none'} + /// The positional terminator used by the Nelder-Mead algorithm + /// #[pyo3(signature = (p0, bounds=None, method="lbfgsb", max_steps=4000, debug=false, verbose=false, **kwargs))] #[allow(clippy::too_many_arguments)] fn minimize( @@ -1954,14 +2085,28 @@ pub(crate) mod laddu { } } + /// A term in an expression with multiple likelihood components + /// + /// See Also + /// -------- + /// NLL.as_term + /// #[pyclass] #[derive(Clone)] struct LikelihoodTerm(Box); + /// An object which holds a registered ``LikelihoodTerm`` + /// + /// See Also + /// -------- + /// laddu.LikelihoodManager.register + /// #[pyclass] #[derive(Clone)] struct LikelihoodID(rust::likelihoods::LikelihoodID); + /// A mathematical expression formed from LikelihoodIDs + /// #[pyclass] #[derive(Clone)] struct LikelihoodExpression(rust::likelihoods::LikelihoodExpression); @@ -2022,6 +2167,8 @@ pub(crate) mod laddu { } } + /// A class which can be used to register LikelihoodTerms and store precalculated data + /// #[pyclass] #[derive(Clone)] struct LikelihoodManager(rust::likelihoods::LikelihoodManager); @@ -2032,29 +2179,156 @@ pub(crate) mod laddu { fn new() -> Self { Self(rust::likelihoods::LikelihoodManager::default()) } + /// Register a LikelihoodTerm with the LikelihoodManager + /// + /// Parameters + /// ---------- + /// term : LikelihoodTerm + /// The LikelihoodTerm to register + /// + /// Returns + /// ------- + /// LikelihoodID + /// A reference to the registered ``likelihood`` that can be used to form complex + /// LikelihoodExpressions + /// fn register(&mut self, likelihood_term: &LikelihoodTerm) -> LikelihoodID { LikelihoodID(self.0.register(likelihood_term.0.clone())) } + /// The free parameters used by all terms in the LikelihoodManager + /// + /// Returns + /// ------- + /// parameters : list of str + /// The list of parameter names + /// fn parameters(&self) -> Vec { self.0.parameters() } + /// Load a LikelihoodExpression by precalculating each term over their internal Datasets + /// + /// Parameters + /// ---------- + /// likelihood_expression : LikelihoodExpression + /// The expression to use in precalculation + /// + /// Returns + /// ------- + /// LikelihoodEvaluator + /// An object that can be used to evaluate the `likelihood_expression` over all managed + /// terms + /// + /// Notes + /// ----- + /// While the given `likelihood_expression` will be the one evaluated in the end, all registered + /// Amplitudes will be loaded, and all of their parameters will be included in the final + /// expression. These parameters will have no effect on evaluation, but they must be + /// included in function calls. + /// + /// See Also + /// -------- + /// LikelihoodManager.parameters + /// fn load(&self, likelihood_expression: &LikelihoodExpression) -> LikelihoodEvaluator { LikelihoodEvaluator(self.0.load(&likelihood_expression.0)) } } + /// A class which can be used to evaluate a collection of LikelihoodTerms managed by a + /// LikelihoodManager + /// #[pyclass] struct LikelihoodEvaluator(rust::likelihoods::LikelihoodEvaluator); #[pymethods] impl LikelihoodEvaluator { + /// A list of the names of the free parameters across all terms in all models + /// + /// Returns + /// ------- + /// parameters : list of str + /// #[getter] fn parameters(&self) -> Vec { self.0.parameters() } + /// Evaluate the sum of all terms in the evaluator + /// + /// Parameters + /// ---------- + /// parameters : list of float + /// The values to use for the free parameters + /// + /// Returns + /// ------- + /// result : float + /// The total negative log-likelihood summed over all terms + /// fn evaluate(&self, parameters: Vec) -> Float { self.0.evaluate(¶meters) } + /// Minimize all LikelihoodTerms with respect to the free parameters in the model + /// + /// This method "runs the fit". Given an initial position `p0` and optional `bounds`, this + /// method performs a minimization over the tatal negative log-likelihood, optimizing the model + /// over the stored signal data and Monte Carlo. + /// + /// Parameters + /// ---------- + /// p0 : array_like + /// The initial parameters at the start of optimization + /// bounds : list of tuple of float, optional + /// A list of lower and upper bound pairs for each parameter (use ``None`` for no bound) + /// method : {'lbfgsb', 'nelder-mead', 'nelder_mead'} + /// The minimization algorithm to use (see additional parameters for fine-tuning) + /// max_steps : int, default=4000 + /// The maximum number of algorithm steps to perform + /// debug : bool, default=False + /// Set to ``True`` to print out debugging information at each step + /// verbose : bool, default=False + /// Set to ``True`` to print verbose information at each step + /// show_step : bool, default=True + /// Include step number in verbose output + /// show_x : bool, default=True + /// Include current best position in verbose output + /// show_fx : bool, default=True + /// Include current best NLL in verbose output + /// observers : Observer or list of Observers + /// Callback functions which are applied after every algorithm step + /// tol_x_rel : float + /// The relative position tolerance used by termination methods (default is machine + /// epsilon) + /// tol_x_abs : float + /// The absolute position tolerance used by termination methods (default is machine + /// epsilon) + /// tol_f_rel : float + /// The relative function tolerance used by termination methods (default is machine + /// epsilon) + /// tol_f_abs : float + /// The absolute function tolerance used by termination methods (default is machine + /// epsilon) + /// tol_g_abs : float + /// The absolute gradient tolerance used by termination methods (default is the cube + /// root of machine epsilon) + /// g_tolerance : float, default=1e-5 + /// Another gradient tolerance used by termination methods (particularly L-BFGS-B) + /// adaptive : bool, default=False + /// Use adaptive values for Nelder-Mead parameters + /// alpha : float, optional + /// Overwrite the default :math:`\alpha` parameter in the Nelder-Mead algorithm + /// beta : float, optional + /// Overwrite the default :math:`\beta` parameter in the Nelder-Mead algorithm + /// gamma : float, optional + /// Overwrite the default :math:`\gamma` parameter in the Nelder-Mead algorithm + /// delta : float, optional + /// Overwrite the default :math:`\delta` parameter in the Nelder-Mead algorithm + /// simplex_expansion_method : {'greedy_minimization', 'greedy_expansion'} + /// The expansion method used by the Nelder-Mead algorithm + /// nelder_mead_f_terminator : {'stddev', 'absolute', 'stddev', 'none'} + /// The function terminator used by the Nelder-Mead algorithm + /// nelder_mead_x_terminator : {'singer', 'diameter', 'rowan', 'higham', 'none'} + /// The positional terminator used by the Nelder-Mead algorithm + /// #[pyo3(signature = (p0, bounds=None, method="lbfgsb", max_steps=4000, debug=false, verbose=false, **kwargs))] #[allow(clippy::too_many_arguments)] fn minimize( @@ -2086,6 +2360,17 @@ pub(crate) mod laddu { } } + /// A parameterized scalar term which can be added to a LikelihoodManager + /// + /// Parameters + /// ---------- + /// name : str + /// The name of the new scalar parameter + /// + /// Returns + /// ------- + /// LikelihoodTerm + /// #[pyfunction] fn LikelihoodScalar(name: String) -> LikelihoodTerm { LikelihoodTerm(rust::likelihoods::LikelihoodScalar::new(name)) @@ -2103,15 +2388,30 @@ pub(crate) mod laddu { } } + /// The status/result of a minimization + /// + /// #[pyclass] #[derive(Clone)] pub(crate) struct Status(pub(crate) ganesh::Status); #[pymethods] impl Status { + /// The current best position in parameter space + /// + /// Returns + /// ------- + /// array_like + /// #[getter] fn x<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray1> { PyArray1::from_slice_bound(py, self.0.x.as_slice()) } + /// The uncertainty on each parameter (``None`` if it wasn't calculated) + /// + /// Returns + /// ------- + /// array_like or None + /// #[getter] fn err<'py>(&self, py: Python<'py>) -> Option>> { self.0 @@ -2119,14 +2419,32 @@ pub(crate) mod laddu { .clone() .map(|err| PyArray1::from_slice_bound(py, err.as_slice())) } + /// The initial position at the start of the minimization + /// + /// Returns + /// ------- + /// array_like + /// #[getter] fn x0<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray1> { PyArray1::from_slice_bound(py, self.0.x0.as_slice()) } + /// The optimized value of the objective function + /// + /// Returns + /// ------- + /// float + /// #[getter] fn fx(&self) -> Float { self.0.fx } + /// The covariance matrix (``None`` if it wasn't calculated) + /// + /// Returns + /// ------- + /// array_like or None + /// #[getter] fn cov<'py>(&self, py: Python<'py>) -> Option>> { self.0.cov.clone().map(|cov| { @@ -2139,6 +2457,12 @@ pub(crate) mod laddu { .unwrap() }) } + /// The Hessian matrix (``None`` if it wasn't calculated) + /// + /// Returns + /// ------- + /// array_like or None + /// #[getter] fn hess<'py>(&self, py: Python<'py>) -> Option>> { self.0.hess.clone().map(|hess| { @@ -2152,14 +2476,32 @@ pub(crate) mod laddu { .unwrap() }) } + /// A status message from the optimizer at the end of the algorithm + /// + /// Returns + /// ------- + /// str + /// #[getter] fn message(&self) -> String { self.0.message.clone() } + /// The state of the optimizer's convergence conditions + /// + /// Returns + /// ------- + /// bool + /// #[getter] fn converged(&self) -> bool { self.0.converged } + /// Parameter bounds which were applied to the fitting algorithm + /// + /// Returns + /// ------- + /// list of Bound or None + /// #[getter] fn bounds(&self) -> Option> { self.0 @@ -2167,10 +2509,22 @@ pub(crate) mod laddu { .clone() .map(|bounds| bounds.iter().map(|bound| ParameterBound(*bound)).collect()) } + /// The number of times the objective function was evaluated + /// + /// Returns + /// ------- + /// int + /// #[getter] fn n_f_evals(&self) -> usize { self.0.n_f_evals } + /// The number of times the gradient of the objective function was evaluated + /// + /// Returns + /// ------- + /// int + /// #[getter] fn n_g_evals(&self) -> usize { self.0.n_g_evals @@ -2181,51 +2535,176 @@ pub(crate) mod laddu { fn __repr__(&self) -> String { format!("{:?}", self.0) } + /// Save the fit result to a file + /// + /// Parameters + /// ---------- + /// path : str + /// The path of the new file (overwrites if the file exists!) + /// + /// Raises + /// ------ + /// IOError + /// If anything fails when trying to write the file + /// + /// Notes + /// ----- + /// Valid file path names must have either the ".pickle" or ".pkl" extension + /// fn save_as(&self, path: &str) -> PyResult<()> { self.0.save_as(path)?; Ok(()) } + /// Load a fit result from a file + /// + /// Parameters + /// ---------- + /// path : str + /// The path of the existing fit file + /// + /// Returns + /// ------- + /// Status + /// The fit result contained in the file + /// + /// Raises + /// ------ + /// IOError + /// If anything fails when trying to read the file + /// + /// Notes + /// ----- + /// Valid file path names must have either the ".pickle" or ".pkl" extension + /// #[staticmethod] fn load(path: &str) -> PyResult { Ok(Status(ganesh::Status::load(path)?)) } } + /// A class representing a lower and upper bound on a free parameter + /// #[pyclass] #[derive(Clone)] #[pyo3(name = "Bound")] struct ParameterBound(ganesh::Bound); #[pymethods] impl ParameterBound { + /// The lower bound + /// + /// Returns + /// ------- + /// float + /// #[getter] fn lower(&self) -> Float { self.0.lower() } + /// The upper bound + /// + /// Returns + /// ------- + /// float + /// #[getter] fn upper(&self) -> Float { self.0.upper() } } + /// A class, typically used to allow Amplitudes to take either free parameters or constants as + /// inputs + /// + /// See Also + /// -------- + /// laddu.parameter + /// laddu.constant + /// #[pyclass] #[derive(Clone)] struct ParameterLike(rust::amplitudes::ParameterLike); + /// A free parameter which floats during an optimization + /// + /// Parameters + /// ---------- + /// name : str + /// The name of the free parameter + /// + /// Returns + /// ------- + /// ParameterLike + /// An object that can be used as the input for many Amplitude constructors + /// + /// Notes + /// ----- + /// Two free parameters with the same name are shared in a fit + /// #[pyfunction] fn parameter(name: &str) -> ParameterLike { ParameterLike(rust::amplitudes::parameter(name)) } + /// A term which stays constant during an optimization + /// + /// Parameters + /// ---------- + /// value : float + /// The numerical value of the constant + /// + /// Returns + /// ------- + /// ParameterLike + /// An object that can be used as the input for many Amplitude constructors + /// #[pyfunction] fn constant(value: Float) -> ParameterLike { ParameterLike(rust::amplitudes::constant(value)) } + /// An Amplitude which represents a single scalar value + /// + /// Parameters + /// ---------- + /// name : str + /// The Amplitude name + /// value : ParameterLike + /// The scalar parameter contained in the Amplitude + /// + /// Returns + /// ------- + /// Amplitude + /// An Amplitude which can be registered by a ``Manager`` + /// + /// See Also + /// -------- + /// laddu.Manager + /// #[pyfunction] fn Scalar(name: &str, value: ParameterLike) -> Amplitude { Amplitude(rust::amplitudes::common::Scalar::new(name, value.0)) } + /// An Amplitude which represents a complex scalar value + /// + /// Parameters + /// ---------- + /// name : str + /// The Amplitude name + /// re: ParameterLike + /// The real part of the complex value contained in the Amplitude + /// im: ParameterLike + /// The imaginary part of the complex value contained in the Amplitude + /// + /// Returns + /// ------- + /// Amplitude + /// An Amplitude which can be registered by a ``Manager`` + /// + /// See Also + /// -------- + /// laddu.Manager + /// #[pyfunction] fn ComplexScalar(name: &str, re: ParameterLike, im: ParameterLike) -> Amplitude { Amplitude(rust::amplitudes::common::ComplexScalar::new( @@ -2233,6 +2712,26 @@ pub(crate) mod laddu { )) } + /// An Amplitude which represents a complex scalar value in polar form + /// + /// Parameters + /// ---------- + /// name : str + /// The Amplitude name + /// r: ParameterLike + /// The magnitude of the complex value contained in the Amplitude + /// theta: ParameterLike + /// The argument of the complex value contained in the Amplitude + /// + /// Returns + /// ------- + /// Amplitude + /// An Amplitude which can be registered by a ``Manager`` + /// + /// See Also + /// -------- + /// laddu.Manager + /// #[pyfunction] fn PolarComplexScalar(name: &str, r: ParameterLike, theta: ParameterLike) -> Amplitude { Amplitude(rust::amplitudes::common::PolarComplexScalar::new( @@ -2240,11 +2739,70 @@ pub(crate) mod laddu { )) } + /// An spherical harmonic Amplitude + /// + /// Computes a spherical harmonic (:math:`Y_{\ell}^m(\theta, \varphi)`) + /// + /// Parameters + /// ---------- + /// name : str + /// The Amplitude name + /// l : int + /// The total orbital momentum (:math:`l \geq 0`) + /// m : int + /// The orbital moment (:math:`-l \leq m \leq l`) + /// angles : Angles + /// The spherical angles to use in the calculation + /// + /// Returns + /// ------- + /// Amplitude + /// An Amplitude which can be registered by a ``Manager`` + /// + /// See Also + /// -------- + /// laddu.Manager + /// #[pyfunction] fn Ylm(name: &str, l: usize, m: isize, angles: &Angles) -> Amplitude { Amplitude(rust::amplitudes::ylm::Ylm::new(name, l, m, &angles.0)) } + /// An spherical harmonic Amplitude for polarized beam experiments + /// + /// Computes a polarized spherical harmonic (:math:`Z_{\ell}^{(r)m}(\theta, \varphi; P_\gamma, \Phi)`) with additional + /// polarization-related factors (see notes) + /// + /// Parameters + /// ---------- + /// name : str + /// The Amplitude name + /// l : int + /// The total orbital momentum (:math:`l \geq 0`) + /// m : int + /// The orbital moment (:math:`-l \leq m \leq l`) + /// r : {'+', 'plus', 'pos', 'positive', '-', 'minus', 'neg', 'negative'} + /// The reflectivity (related to naturality of parity exchange) + /// angles : Angles + /// The spherical angles to use in the calculation + /// polarization : Polarization + /// The beam polarization to use in the calculation + /// + /// Returns + /// ------- + /// Amplitude + /// An Amplitude which can be registered by a ``Manager`` + /// + /// See Also + /// -------- + /// laddu.Manager + /// + /// Notes + /// ----- + /// This amplitude is described in [Mathieu]_ + /// + /// .. [Mathieu] Mathieu, V., Albaladejo, M., Fernández-Ramírez, C., Jackura, A. W., Mikhasenko, M., Pilloni, A., & Szczepaniak, A. P. (2019). Moments of angular distribution and beam asymmetries in :math:`\eta\pi^0` photoproduction at GlueX. Physical Review D, 100(5). `doi:10.1103/physrevd.100.054017 `_ + /// #[pyfunction] fn Zlm( name: &str, @@ -2264,6 +2822,36 @@ pub(crate) mod laddu { )) } + /// An relativistic Breit-Wigner Amplitude + /// + /// This Amplitude represents a relativistic Breit-Wigner with known angular momentum + /// + /// Parameters + /// ---------- + /// name : str + /// The Amplitude name + /// mass : ParameterLike + /// The mass of the resonance + /// width : ParameterLike + /// The (nonrelativistic) width of the resonance + /// l : int + /// The total orbital momentum (:math:`l > 0`) + /// daughter_1_mass : Mass + /// The mass of the first decay product + /// daughter_2_mass : Mass + /// The mass of the second decay product + /// resonance_mass: Mass + /// The total mass of the resonance + /// + /// Returns + /// ------- + /// Amplitude + /// An Amplitude which can be registered by a ``Manager`` + /// + /// See Also + /// -------- + /// laddu.Manager + /// #[pyfunction] fn BreitWigner( name: &str, @@ -2285,6 +2873,63 @@ pub(crate) mod laddu { )) } + /// A fixed K-Matrix Amplitude for :math:`f_0` mesons + /// + /// Parameters + /// ---------- + /// name : str + /// The Amplitude name + /// couplings : list of list of ParameterLike + /// Each initial-state coupling (as a list of pairs of real and imaginary parts) + /// channel : int + /// The channel onto which the K-Matrix is projected + /// mass: Mass + /// The total mass of the resonance + /// + /// Returns + /// ------- + /// Amplitude + /// An Amplitude which can be registered by a ``Manager`` + /// + /// See Also + /// -------- + /// laddu.Manager + /// + /// Notes + /// ----- + /// This Amplitude follows the prescription of [Kopf]_ and fixes the K-Matrix to data + /// from that paper, leaving the couplings to the initial state free + /// + /// +---------------+-------------------+ + /// | Channel index | Channel | + /// +===============+===================+ + /// | 0 | :math:`\pi\pi` | + /// +---------------+-------------------+ + /// | 1 | :math:`2\pi 2\pi` | + /// +---------------+-------------------+ + /// | 2 | :math:`K\bar{K}` | + /// +---------------+-------------------+ + /// | 3 | :math:`\eta\eta` | + /// +---------------+-------------------+ + /// | 4 | :math:`\eta\eta'` | + /// +---------------+-------------------+ + /// + /// +-------------------+ + /// | Pole names | + /// +===================+ + /// | :math:`f_0(500)` | + /// +-------------------+ + /// | :math:`f_0(980)` | + /// +-------------------+ + /// | :math:`f_0(1370)` | + /// +-------------------+ + /// | :math:`f_0(1500)` | + /// +-------------------+ + /// | :math:`f_0(1710)` | + /// +-------------------+ + /// + /// .. [Kopf] Kopf, B., Albrecht, M., Koch, H., Küßner, M., Pychy, J., Qin, X., & Wiedner, U. (2021). Investigation of the lightest hybrid meson candidate with a coupled-channel analysis of :math:`\bar{p}p`-, :math:`\pi^- p`- and :math:`\pi \pi`-Data. The European Physical Journal C, 81(12). `doi:10.1140/epjc/s10052-021-09821-2 `__ + /// #[pyfunction] fn KopfKMatrixF0( name: &str, @@ -2300,6 +2945,57 @@ pub(crate) mod laddu { )) } + /// A fixed K-Matrix Amplitude for :math:`f_2` mesons + /// + /// Parameters + /// ---------- + /// name : str + /// The Amplitude name + /// couplings : list of list of ParameterLike + /// Each initial-state coupling (as a list of pairs of real and imaginary parts) + /// channel : int + /// The channel onto which the K-Matrix is projected + /// mass: Mass + /// The total mass of the resonance + /// + /// Returns + /// ------- + /// Amplitude + /// An Amplitude which can be registered by a ``Manager`` + /// + /// See Also + /// -------- + /// laddu.Manager + /// + /// Notes + /// ----- + /// This Amplitude follows the prescription of [Kopf]_ and fixes the K-Matrix to data + /// from that paper, leaving the couplings to the initial state free + /// + /// +---------------+-------------------+ + /// | Channel index | Channel | + /// +===============+===================+ + /// | 0 | :math:`\pi\pi` | + /// +---------------+-------------------+ + /// | 1 | :math:`2\pi 2\pi` | + /// +---------------+-------------------+ + /// | 2 | :math:`K\bar{K}` | + /// +---------------+-------------------+ + /// | 3 | :math:`\eta\eta` | + /// +---------------+-------------------+ + /// + /// +---------------------+ + /// | Pole names | + /// +=====================+ + /// | :math:`f_2(1270)` | + /// +---------------------+ + /// | :math:`f_2'(1525)` | + /// +---------------------+ + /// | :math:`f_2(1810)` | + /// +---------------------+ + /// | :math:`f_2(1950)` | + /// +---------------------+ + /// #[pyfunction] fn KopfKMatrixF2( name: &str, @@ -2315,6 +3011,49 @@ pub(crate) mod laddu { )) } + /// A fixed K-Matrix Amplitude for :math:`a_0` mesons + /// + /// Parameters + /// ---------- + /// name : str + /// The Amplitude name + /// couplings : list of list of ParameterLike + /// Each initial-state coupling (as a list of pairs of real and imaginary parts) + /// channel : int + /// The channel onto which the K-Matrix is projected + /// mass: Mass + /// The total mass of the resonance + /// + /// Returns + /// ------- + /// Amplitude + /// An Amplitude which can be registered by a ``Manager`` + /// + /// See Also + /// -------- + /// laddu.Manager + /// + /// Notes + /// ----- + /// This Amplitude follows the prescription of [Kopf]_ and fixes the K-Matrix to data + /// from that paper, leaving the couplings to the initial state free + /// + /// +---------------+-------------------+ + /// | Channel index | Channel | + /// +===============+===================+ + /// | 0 | :math:`\pi\eta` | + /// +---------------+-------------------+ + /// | 1 | :math:`K\bar{K}` | + /// +---------------+-------------------+ + /// + /// +-------------------+ + /// | Pole names | + /// +===================+ + /// | :math:`a_0(980)` | + /// +-------------------+ + /// | :math:`a_0(1450)` | + /// +-------------------+ + /// #[pyfunction] fn KopfKMatrixA0( name: &str, @@ -2330,6 +3069,51 @@ pub(crate) mod laddu { )) } + /// A fixed K-Matrix Amplitude for :math:`a_2` mesons + /// + /// This Amplitude follows the prescription of [Kopf]_ and fixes the K-Matrix to data + /// from that paper, leaving the couplings to the initial state free + /// + /// Parameters + /// ---------- + /// name : str + /// The Amplitude name + /// couplings : list of list of ParameterLike + /// Each initial-state coupling (as a list of pairs of real and imaginary parts) + /// channel : int + /// The channel onto which the K-Matrix is projected + /// mass: Mass + /// The total mass of the resonance + /// + /// Returns + /// ------- + /// Amplitude + /// An Amplitude which can be registered by a ``Manager`` + /// + /// See Also + /// -------- + /// laddu.Manager + /// + /// Notes + /// ----- + /// +---------------+-------------------+ + /// | Channel index | Channel | + /// +===============+===================+ + /// | 0 | :math:`\pi\eta` | + /// +---------------+-------------------+ + /// | 1 | :math:`K\bar{K}` | + /// +---------------+-------------------+ + /// | 2 | :math:`\pi\eta'` | + /// +---------------+-------------------+ + /// + /// +-------------------+ + /// | Pole names | + /// +===================+ + /// | :math:`a_2(1320)` | + /// +-------------------+ + /// | :math:`a_2(1700)` | + /// +-------------------+ + /// #[pyfunction] fn KopfKMatrixA2( name: &str, @@ -2345,6 +3129,51 @@ pub(crate) mod laddu { )) } + /// A fixed K-Matrix Amplitude for :math:`\rho` mesons + /// + /// Parameters + /// ---------- + /// name : str + /// The Amplitude name + /// couplings : list of list of ParameterLike + /// Each initial-state coupling (as a list of pairs of real and imaginary parts) + /// channel : int + /// The channel onto which the K-Matrix is projected + /// mass: Mass + /// The total mass of the resonance + /// + /// Returns + /// ------- + /// Amplitude + /// An Amplitude which can be registered by a ``Manager`` + /// + /// See Also + /// -------- + /// laddu.Manager + /// + /// Notes + /// ----- + /// This Amplitude follows the prescription of [Kopf]_ and fixes the K-Matrix to data + /// from that paper, leaving the couplings to the initial state free + /// + /// +---------------+-------------------+ + /// | Channel index | Channel | + /// +===============+===================+ + /// | 0 | :math:`\pi\pi` | + /// +---------------+-------------------+ + /// | 1 | :math:`2\pi 2\pi` | + /// +---------------+-------------------+ + /// | 2 | :math:`K\bar{K}` | + /// +---------------+-------------------+ + /// + /// +--------------------+ + /// | Pole names | + /// +====================+ + /// | :math:`\rho(770)` | + /// +--------------------+ + /// | :math:`\rho(1700)` | + /// +--------------------+ + /// #[pyfunction] fn KopfKMatrixRho( name: &str, @@ -2359,7 +3188,47 @@ pub(crate) mod laddu { &mass.0, )) } - + /// A fixed K-Matrix Amplitude for the :math:`\pi_1(1600)` hybrid meson + /// + /// Parameters + /// ---------- + /// name : str + /// The Amplitude name + /// couplings : list of list of ParameterLike + /// Each initial-state coupling (as a list of pairs of real and imaginary parts) + /// channel : int + /// The channel onto which the K-Matrix is projected + /// mass: Mass + /// The total mass of the resonance + /// + /// Returns + /// ------- + /// Amplitude + /// An Amplitude which can be registered by a ``Manager`` + /// + /// See Also + /// -------- + /// laddu.Manager + /// + /// Notes + /// ----- + /// This Amplitude follows the prescription of [Kopf]_ and fixes the K-Matrix to data + /// from that paper, leaving the couplings to the initial state free + /// + /// +---------------+-------------------+ + /// | Channel index | Channel | + /// +===============+===================+ + /// | 0 | :math:`\pi\eta` | + /// +---------------+-------------------+ + /// | 1 | :math:`\pi\eta'` | + /// +---------------+-------------------+ + /// + /// +---------------------+ + /// | Pole names | + /// +=====================+ + /// | :math:`\pi_1(1600)` | + /// +---------------------+ + /// #[pyfunction] fn KopfKMatrixPi1( name: &str, From a054510b18012d73122a10f19f40d66185112c7e Mon Sep 17 00:00:00 2001 From: denehoffman Date: Thu, 7 Nov 2024 15:28:51 -0500 Subject: [PATCH 25/26] ci: separate command for rebuilding docs and making docfiles --- .justfile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.justfile b/.justfile index db1b84f..e8f3033 100644 --- a/.justfile +++ b/.justfile @@ -4,11 +4,15 @@ default: develop: CARGO_INCREMENTAL=true maturin develop -r --uv --strip -makedocs: +builddocs: CARGO_INCREMENTAL=true maturin build -r --strip uv pip install ./target/wheels/* make -C docs clean make -C docs html +makedocs: + make -C docs clean + make -C docs html + odoc: firefox ./docs/build/html/index.html From d73052c46b267cc3a25bb2c42e673f558dfe6b2e Mon Sep 17 00:00:00 2001 From: denehoffman Date: Thu, 7 Nov 2024 15:37:50 -0500 Subject: [PATCH 26/26] docs: add RTDs documentation badge to README and link to repo in docs --- README.md | 2 ++ docs/source/index.rst | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7565ff4..80db038 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,8 @@ Crates.io Version docs.rs + + Read the Docs Codecov diff --git a/docs/source/index.rst b/docs/source/index.rst index 00e03d6..1dcb286 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -3,9 +3,11 @@ laddu ``laddu`` is an library for analyzing particle physics data. It is written in Rust with bindings to Python which are documented here. +Right now, this page just documents the basic Python API of the library, but the plan is to eventually write a tutorial here for general use. See the `GitHub Repository `_ for more details. + .. toctree:: - :hidden: :caption: API Reference + :maxdepth: 1 :name: sec-api modules/index