Projects

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 out there. laddu is written in Rust with bindings to Python via PyO3 and maturin and is the spiritual successor to 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.

laddu grew out of my frustration with the way amplitude analyses were being done within the GlueX collaboration. Everyone had these messy configuration files which would need to be duplicated and modified, usually by one-off scripts, to produce fit results which would then have to be collected by yet another set of scripts. I got tired of the constant file management, I was spending more time debugging config files than actually doing physics! Since my original foray into Rust in March 2024, I have learned a lot about what is required to distribute a project like this via Python. There are tons of small optimizations that can be made, and I'd imagine there are still quite a few to go. This project has taught me everything from memory management to the intricacies of floating-point numbers to quite a lot about parallel processing. I believe the project is still in an exploratory state, but it is certainly usable enough to do some actual research now. Since I'm actively using it while I develop it, I quickly discover new sharp corners and quality-of-life features to implement, and there's always little chores to do like documentation and testing.

ganesh (/ɡəˈneɪʃ/), named after the Hindu god of wisdom, provides several common minimization algorithms as well as a straightforward, trait-based interface to create your own extensions. This crate is intended to be as simple as possible. The user needs to implement the Function trait on some struct which will take a vector of parameters and return a single-valued Result ($f(\mathbb{R}^n)\to\mathbb{R}$). Users can optionally provide a gradient function to speed up some algorithms, but a default central finite-difference implementation is provided so that all algorithms will work out of the box.

Part of the difficulty of this project is the lack of well-documented implementations of some of the more useful algorithms, like the BFGS family. While I am not sure, I believe this might be the first pure Rust implementation of L-BFGS-B. If you look through other optimization crates, Python packages, or even C/C++ libraries, a common theme you'll find is that they mostly just bind the original FORTRAN code written for this algorithm. I wanted to write the algorithm from scratch for two reasons. First, it's a great way to learn the language, and it's very convenient to not have to worry about dependencies in external languages like FORTRAN. Second, I had a lot of trouble finding bounded optimizers for Rust. argmin, the most-downloaded optimization crate, currently doesn't implement any constrained optimizers, although external solvers like egobox and cobyla can be used to accomplish this. nlopt has a Rust wrapper that includes these algorithms (written in C), but I quit using it after one too many C-related exceptions that were all-but-impossible to track down or solve with Rust code.

rustitude is intended to be a one-stop-shop for amplitude analysis. At GlueX, we typically rely on AmpTools or PyPWA for performing partial-wave analyses (PWAs), moment analyses, and the like. These each come with pros and cons. For AmpTools, the major pros are that its fast (C++), can use MPI and run on GPUs, and has an assortment of pre-written amplitudes that allow users to just start writing analysis code. The major con, in my opinion, is that you typically don't write C++ to use AmpTools, you instead write a config file with a completely new syntax. In practice, since these config files aren't code, you end up writing quite a few of them for very similar purposes. Since it isn't code, it's very prone to typos which won't be detected till runtime and you can accidentally overwrite things by including other configs within configs. Furthermore, the output file must then be read by some custom C++ code that will typically vary for each kind of fit you might do. I usually see people write C++ code to put the fit results in a format that is easier to read in Python or messy ROOT code to directly make histograms. On the other hand PyPWA, as the name suggests, is written in Python, so it's already leagues slower than AmpTools, despite optimizations that can be made with JIT compilers like numba. The pros here are that Python is a very simple language which can be used to quickly prototype and test new code and easily visualize the results. You can even run it interactively! However, the documentation is a bit lacking, the implementation of amplitudes is not straightforward, and it hasn't been updated in over a year.

These are mostly minor annoyances. AmpTools works great, and I've used it for my own thesis work, but I've always wanted to try my hand at writing something to do the task in a different way. rustitude is the first iteration of this, and was my first project using Rust with PyO3 bindings to Python. I knew at the outset I wanted the primary interface for users to be Python, but for speed purposes there needed to be a compiled backend. I began the project with a few other goals, the first of which was to make it extremely simple to write new amplitudes without knowing much Rust. Next, I wanted the framework to precalculate and cache as much as possible ahead of time. For example, there are a couple of AmpTools amplitudes which are based on spherical harmonics. For every spherical harmonic, the spherical angles are generally going to depend on data from the events and not from any free parameters in a model, so it really isn't necessary to calculate them every time the main function is evaluated. AmpTools does some caching, but because it can only do it on a per-amplitude basis (you can't cache different values for a $Y_{00}$ and a $Y_{10}$ amplitude without writing separate amplitude files for each, for instance), there is still a lot of room for optimization.

This project is currently operational and accomplishes most of these goals to my satisfaction. However, it is still a bit slower than I'd like and very memory-hungry. AmpTools uses a lot of nice pointer manipulation, and MPI allows the calculation to be divided across multiple nodes of an HPC cluster, which also spreads out the memory usage. In rustitude, ROOT files are primarily read into memory in their entirety, and calculations are limited to one node (although multiprocessing is set up to efficiently use as many cores as desired). There are also some ergonomics that I'm not happy about, and if you look in the git history, you can see that it went through several rewrites before getting to this form. I used this project to learn Rust, and now that I understand it a bit more, there are a lot of things that will be difficult to improve without major refactors. I've since started working on a successor project (see laddu above) that will hopefully address these issues.