Skip to main content

Python bindings for the Rustitude library

Project description

Demystifying Amplitude Analysis

GitHub Release GitHub last commit GitHub Actions Workflow Status GitHub License Crates.io Version docs.rs Codecov PyPI - Version Read the Docs

Table of Contents:

Introduction

This project began with a desire to make a fast but easy to use interface for fitting amplitudes to particle physics data. That being said, there are performant methods such as AmpTools, which is written in C++, but in my personal experience, it can be a bit tricky to use and extend, and it generally requires a lot of boilerplate code to generate new amplitudes or plotting scripts. On the other hand, there are also libraries like PyPWA (written in Python) which seem like they could be easy to use, but often fail in this aspect due to Python's limiting syntax, speed issues, and a general lack of documentation and ongoing development. There have been attempts to bridge the gap between AmpTools and Python, most recently (and successfully) PyAmpTools. The difficulty with this method is that it relies on PyROOT, which also means you need ROOT installed (and built with your version of Python). For now, I'll spare you the anti-ROOT rant and just say that ROOT should be an opt-in, not a requirement. So where does that leave rustitude?

As the name suggests, rustitude was written in Rust, so let's get the obvious downside out of the way: not many particle physicists know how to write Rust code. Hopefully, this will change over the next decade (and there has already been some support from the US government, of all places). While Rust carries the disadvantage of relative obscurity compared to C++, it also has many benefits. No null means no null references (Tony Hoare's "billion dollar mistake"). In Rust, references are always valid, a guarantee made by a very helpful and only occasionally frustrating borrow checker. Rust "crates" are set up in a way which encourages documentation (see rustitude-core's documentation), and the basic syntax is fairly easy to learn for people who have been using optional type checking in Python. Perhaps one of the biggest benefits of Rust is how easy it is to employ parallelization, but the two reasons I like it most are that it's incredibly easy to write Python bindings (that's what this library is after all) and it has a package manager. This second point is important -- unlike C/C++, where a developer is swamped with some menagerie Makefile, CMakeLists.txt, or some scons monstrosity which may only work on "X" system and only if you install and use make, cmake, g++, or whatever (oh yeah, and you made sure all your external dependencies are linked correctly, right? Right?), Rust supports adding a package by simply adding a line to Cargo.toml (or using the cargo add command). In many ways, package management in Rust is actually simpler than Python, since there's only one prefered method of creating and managing projects, formatting, linting, and compiling.

Now I've covered why I don't like some of the existing solutions, and why I chose to use Rust, but what does this project have that makes it stand out? Here are some reasons to entice you:

  • rustitude will automatically parallelize amplitudes over the events in a dataset. There's no reason for a developer to ever write parallelized code themselves.
  • Implementing Node on a struct is all that is needed to use it as an amplitude. This means developers need only write two to three total methods to describe the entire functionality of their amplitude, and one of these just gives the names and order of the amplitude's input parameters.
  • A major goal of rustitude was to increase processing speed by sacrificing memory. This is done by precalculating parts of amplitudes which don't change when the free parameter inputs change. AmpTools supports a version of this, but only on the level of each general amplitude rather than on an individual basis. The simplest example of this is the Ylm amplitude (spherical harmonic), which can be entirely precalculated given the value of l and m. In AmpTools, different instances of Ylm with different ls and ms share precalculated data, whereas in rustitude, they don't. The AmpTools implementation of Ylm needs to calculate a spherical harmonic for every event on every function call, while rustitude just needs to look up a value in an array!
  • The majority of the library (the public interface) has Python bindings, so if there is no need for custom amplitudes, a developer never actually has to write any Rust code, and the resulting calculations will be as performant as if they were written in Rust.

Installation

Adding rustitude to an existing Rust project is as simple as

cargo add rustitude

The Python installation is equally straightforward:

pip install rustitude

Usage

See the rustitude-core crate for a more in-depth tutorial on writing custom amplitudes in Rust. This package is mostly focused on the Python side of things. Here is the setup for an example analysis:

import rustitude as rt
from rustitude import gluex
import numpy as np

# Start by creating some amplitudes:
f0p = gluex.resonances.KMatrixF0('f0+', channel=2)
f0n = gluex.resonances.KMatrixF0('f0-', channel=2)
f2 = gluex.resonances.KMatrixF2('f2', channel=2)
a0p = gluex.resonances.KMatrixA0('a0+', channel=1)
a0n = gluex.resonances.KMatrixA0('a0-', channel=1)
a2 = gluex.resonances.KMatrixA2('a2', channel=1)
s0p = gluex.harmonics.Zlm('Z00+', l=0, m=0, reflectivity='+')
s0n = gluex.harmonics.Zlm('Z00-', 0, 0, '-')
d2p = gluex.harmonics.Zlm('Z22+', 2, 2) # positive reflectivity is the default

# Next, let's put them together into a model
# The API supports addition and multiplication and has additional methods for the real part (`real`) and imaginary part (`imag`).
pos_re_sum = (f0p + a0p) * s0p.real() + (f2 + a2) * d2p.real()
pos_im_sum = (f0p + a0p) * s0p.imag() + (f2 + a2) * d2p.imag()
neg_re_sum = (f0n + a0n) * s0n.real()
neg_im_sum = (f0n + a0n) * s0n.imag()

mod = rt.Model([pos_re_sum, pos_im_sum, neg_re_sum, neg_im_sum])

# There is no need to constrain amplitudes, since each named amplitude is only ever evaluated once and a cached value gets pulled if we run across an amplitude by the same name!
# We should, however, fix some of the values to make the fit less ambiguous. For instance, suppose we are above the threshold for the f_0(500) which is included in the F0 K-Matrix:
mod.fix("f0+", "f0_500 re", 0.0)
mod.fix("f0+", "f0_500 im", 0.0)
mod.fix("f0-", "f0_500 re", 0.0)
mod.fix("f0-", "f0_500 im", 0.0)

# As mentioned, we should also fix at least one complex phase per coherent sum:
mod.fix("f0+", "f0_980 im", 0.0)
mod.fix("f0-", "f0_980 im", 0.0)

# All done! Now let's load our model into a Manager, which helps coordinate the model with datasets.
# First, load up some datasets. rustitude provides an open operation that uses uproot under the hood:
ds = rt.open('data.root')
ds_mc = rt.open('mc.root')

m_data = rt.Manager(mod, ds)
m_mc = rt.Manager(mod, ds_mc)

# We could stop here and evaluate just one dataset at a time:

# res = m_data([1.0, 3.4, 2.8, -3.6, ... ])
# -> [5.3, 0.2, ...], a list of intensities from the amplitude calculation

# Or, what we probably want to do is find the negative log-likelihood for some choice of parameters:

nll = rt.ExtendedLogLikelihood(m_data, m_mc)

res = nll([10.0] * mod.n_free) # automatic CPU parallelism without GIL
print(res) # prints some value for the NLL

mi = rt.minimizer(nll, method='Minuit') # use iminuit to create a Minuit minimizer
mi.migrad() # run the Migrad algorithm
print(mi) # print the fit result

Automatic parallelism over the CPU can be disabled via function calls which support it (for example, nll([10.0] * mod.n_free, parallel=False) would run without parallel processing), and the number of CPUs used can be controlled via the RAYON_NUM_THREADS environment variable, which can be set before the code is run or modified inside the code (for example, os.environ['RAYON_NUM_THREADS] = '5' would ensure only five threads are used past that point in the code). By default, an unset value or the value of '0' will use all available cores.

See the rustitude-gluex package for some of the currently implemented amplitudes (derived from GlueX's halld_sim repo). There are also some helper methods Scalar, CScalar, and PCScalar to create amplitudes which represent a single free parameter, a single complex free parameter, and a single complex free parameter in polar coordinates respectively.

TODOs

In no particular order, here is a list of what (probably) needs to be done before I will stop making any breaking changes:

  • Pure Rust parsing of ROOT files without the uproot backend (I have some moderate success with oxyroot, but there are still a few issues reading larger files)
  • Add plotting methods
  • A way to check if the number of parameters matches the input at compile time would be nice, not sure if it's possible though
  • Give managers a way to apply amplitudes to new datasets, like using the result from a fit to weight some generated Monte-Carlo for plotting the result. This is possible to do through Python, but a convenience method is probably needed
  • Lots of documentation
  • Lots of tests

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

rustitude-0.10.3.tar.gz (8.1 MB view details)

Uploaded Source

Built Distributions

rustitude-0.10.3-cp37-abi3-win_amd64.whl (2.2 MB view details)

Uploaded CPython 3.7+ Windows x86-64

rustitude-0.10.3-cp37-abi3-win32.whl (2.1 MB view details)

Uploaded CPython 3.7+ Windows x86

rustitude-0.10.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (15.5 MB view details)

Uploaded CPython 3.7+ manylinux: glibc 2.17+ x86-64

rustitude-0.10.3-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl (19.7 MB view details)

Uploaded CPython 3.7+ manylinux: glibc 2.17+ s390x

rustitude-0.10.3-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl (14.6 MB view details)

Uploaded CPython 3.7+ manylinux: glibc 2.17+ ppc64le

rustitude-0.10.3-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl (14.5 MB view details)

Uploaded CPython 3.7+ manylinux: glibc 2.17+ ARMv7l

rustitude-0.10.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (14.4 MB view details)

Uploaded CPython 3.7+ manylinux: glibc 2.17+ ARM64

rustitude-0.10.3-cp37-abi3-manylinux_2_12_i686.manylinux2010_i686.whl (14.9 MB view details)

Uploaded CPython 3.7+ manylinux: glibc 2.12+ i686

rustitude-0.10.3-cp37-abi3-macosx_11_0_arm64.whl (2.3 MB view details)

Uploaded CPython 3.7+ macOS 11.0+ ARM64

rustitude-0.10.3-cp37-abi3-macosx_10_12_x86_64.whl (2.6 MB view details)

Uploaded CPython 3.7+ macOS 10.12+ x86-64

File details

Details for the file rustitude-0.10.3.tar.gz.

File metadata

  • Download URL: rustitude-0.10.3.tar.gz
  • Upload date:
  • Size: 8.1 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: maturin/1.7.0

File hashes

Hashes for rustitude-0.10.3.tar.gz
Algorithm Hash digest
SHA256 021a761c5ea499730870cf5667f07f7382538b75c5684b1ddec440a8b06a7c1b
MD5 b83b3219b856b388257980e363f6dc59
BLAKE2b-256 c7c407d954ab25b4e513035ba42dc45f2e500a089519211c27e278b7b8b9ac53

See more details on using hashes here.

File details

Details for the file rustitude-0.10.3-cp37-abi3-win_amd64.whl.

File metadata

File hashes

Hashes for rustitude-0.10.3-cp37-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 ca6ef4bdf216305ae56b6e9653ae9817119fedd0326c3735bda8ba0ee315406e
MD5 2f8072640347a274d314e8ef884b23be
BLAKE2b-256 59e19c77d3ea6582b11c897f6c77c960a212c5ed21d161981017dacf01616438

See more details on using hashes here.

File details

Details for the file rustitude-0.10.3-cp37-abi3-win32.whl.

File metadata

File hashes

Hashes for rustitude-0.10.3-cp37-abi3-win32.whl
Algorithm Hash digest
SHA256 f8ab0d32acbcd09db7095b52078533ee497532df4a780b4bd2031b4265bf4f90
MD5 8cc2dfc7be7bf48d5ca65fdd702344a5
BLAKE2b-256 fe0b8fed8f5a497c23e50b84fc412b2a51f3be2a1b0a85d63fddaac9315009be

See more details on using hashes here.

File details

Details for the file rustitude-0.10.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for rustitude-0.10.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 4801d997428410d65c1e0e7575973d87445bef3af605e77af7fb4db2a9fedba5
MD5 d49592e9ed665cdee6f920eafb55ae14
BLAKE2b-256 f54865b2cc256ad47018284868356a7ebf55a694e54d7a73ad4ca39d889fe883

See more details on using hashes here.

File details

Details for the file rustitude-0.10.3-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl.

File metadata

File hashes

Hashes for rustitude-0.10.3-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl
Algorithm Hash digest
SHA256 0bf9474180e33fc5e5445e9769b5f73cc41efc65bccb040d6016f21bd60a8eae
MD5 158da3b6b75a08fbf4e3ae411fea40dd
BLAKE2b-256 84b64cfd3003486fdc5b90e82e692b7cc1b172907894f6835a788d7b08dfcbbe

See more details on using hashes here.

File details

Details for the file rustitude-0.10.3-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl.

File metadata

File hashes

Hashes for rustitude-0.10.3-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl
Algorithm Hash digest
SHA256 a1eee1362e9b0c571f04689494891624db33f77ecbdfb5ddc4a5e498b1e57390
MD5 bf2e3c604f592633de6d77b101962d37
BLAKE2b-256 1dac2971a5769e9899dbb00adbad2db278cb163d4d8ed9a4e1c60ce0a4147ebc

See more details on using hashes here.

File details

Details for the file rustitude-0.10.3-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl.

File metadata

File hashes

Hashes for rustitude-0.10.3-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl
Algorithm Hash digest
SHA256 919433c996dd0f58fd5c190fed9f5c12290517b7ca5ed229b1b07c0594efa2ec
MD5 62bc7b6e8518cf7133e1f0a78ffb8d11
BLAKE2b-256 e282a5e3f0dbacd6005f02e09032307338fe0a37ab8d1539ec8c74a4562183dd

See more details on using hashes here.

File details

Details for the file rustitude-0.10.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for rustitude-0.10.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 1e8e80a36f41b03f67d9e8b1c004676cc4ee26f5a9b2a7fbf56ff78cdc2e5a9d
MD5 350a3d9145aa48f071fd6f60b0e221bf
BLAKE2b-256 0aec2863bafd323e0047f846da48f9d22af1131176f30bd69cab78d5d394ba88

See more details on using hashes here.

File details

Details for the file rustitude-0.10.3-cp37-abi3-manylinux_2_12_i686.manylinux2010_i686.whl.

File metadata

File hashes

Hashes for rustitude-0.10.3-cp37-abi3-manylinux_2_12_i686.manylinux2010_i686.whl
Algorithm Hash digest
SHA256 7288ecc8ed62ad7d7f73374e908ff856b351b8755902cf0519ff798a9b9c1c31
MD5 7d65b877997f4eb3f61cd3271dd1e5a3
BLAKE2b-256 61c26a6cd9cd088aee925f30584ba3ac95d14d5123940307d2df7ef8c99e6b13

See more details on using hashes here.

File details

Details for the file rustitude-0.10.3-cp37-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for rustitude-0.10.3-cp37-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 9a01fb207f66170c5c010ff222b6f43637a2405152f71901d42bf647507baa7a
MD5 9fe999813016597ae2d50df2ba5af4ce
BLAKE2b-256 adc5a6de86182140f9b63c7d005578cba2a82270b4589067dd05d4f0f4a64796

See more details on using hashes here.

File details

Details for the file rustitude-0.10.3-cp37-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for rustitude-0.10.3-cp37-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 cf69a7943dcf141bfdec6c99f0ed0ae2abbf09a18f58268047c9e9695d51de98
MD5 d74132cfb27c9a49b0924a1304dce67f
BLAKE2b-256 30d85fe6ab8ef7457c7ae9b656c1a385497e36b776542cb3bb82b9a3f3ccfdd6

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page