Skip to content

Custom cut, scale, PDF and bias functions through UMAMI - #264

Merged
theoheimel merged 16 commits into
mainfrom
feat-custom-umami-functions
Oct 18, 2026
Merged

theoheimel merged 16 commits into
mainfrom
feat-custom-umami-functions

Conversation

@theoheimel

@theoheimel theoheimel commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Custom cuts, scales, parton densities and event biases for MG7 runs. Each one is either a UMAMI shared library or a Python file using numpy or torch, called by madspace in batches like a matrix element.

All steps are in. CUDA and HIP builds, and torch functions on GPUs, are untested (no GPU was available).

Design

  • New UMAMI keys (minor version 1).
    • Inputs: PARTICLE_COUNT, PDG_IDS, MOMENTUM_FRACTION, SCALE, PDG_ID, BEAM_INDEX.
    • Outputs: CUT, SCALE, PDF, BIAS, all doubles.
  • Cut: the returned value multiplies the cut mask (0 rejects the event).
  • Scale: one value sets the renormalization and both factorization scales, like MG5's user_dynamical_scale. scale_factor and the fixed-scale options still apply.
  • PDF: returns x·f(x, Q) for one beam and one parton. alpha_s still comes from the run card's PDF set.
  • Bias: works like MG5's bias. Adaptation and unweighting see w·b, the reported cross section uses w, and written events carry w/b.
  • Python functions:
    • UmamiFunctions, a table of std::functions, builds a MatrixElementApi without a shared library, through Context::add_matrix_element.
    • ms.PythonFunction implements UMAMI in Python with selectable inputs and outputs. It passes zero-copy numpy or torch views, and checks the type, dtype, shape and device of the results. torch runs on the GPU stream of the runtime. numpy is CPU only.
    • madspace now releases the GIL while runtimes, generators and MadNIS training run, so worker threads can call Python. The abort check and the DLPack deleters of Python-owned tensors take the GIL themselves.
    • With torch on the CPU, MG7 sets torch to one thread, since madspace already splits the batches.

Checklist

  • UMAMI keys, madspace CustomFunction and CustomPdf, options on Cuts / EnergyScale / PartonDensity / Integrand, Python bindings

  • Bias handling in the event generator (unbiased cross section, unweighted weights divided by the bias)

  • bin/init_custom_func and templates (C, C++, Fortran, CUDA, HIP)

  • Run card [custom_functions] section and launch.py wiring

  • Acceptance tests

  • Documentation page under Usage

  • UmamiFunctions and Context::add_matrix_element

  • GIL release in the madspace bindings

  • ms.PythonFunction and its unit tests

  • .so or .py in [custom_functions], python-numpy / python-torch in bin/init_custom_func, gridpack support

  • Acceptance tests and documentation for Python functions

…as functions

Add UMAMI input keys for particle count, PDG ids, momentum fraction, scale,
PDG id and beam index, and output keys CUT, SCALE, PDF and BIAS. A new
CustomFunction generator calls a loaded UMAMI library on the event momenta.
Cuts, EnergyScale, PartonDensity (via CustomPdf) and Integrand accept these
custom functions.
bin/init_custom_func writes custom_<function>/ with a UMAMI wrapper, a
function body for one phase-space point (C, C++, Fortran, CUDA or HIP), the
UMAMI header and a Makefile that builds lib/custom_<function>.so. Options are
taken from the command line or asked interactively. The run card gets a
[custom_functions] section with the library paths.
The libraries are built from custom_<kind>/ if present, and loaded before the
matrix elements in every context and gridpack. Custom cuts and scales get the
PDG ids of the first flavor, the bias those of the sampled flavor. A custom
PDF replaces the PDF grid (alpha_s still comes from the PDF set) and disables
the systematics.
…eights

The integrand weight includes the bias b, so adaptation, unweighting and the
channel allocation see |w| b. The signed running integral, which gives the
reported cross section, accumulates w / b, and the event records carry b so
that the combined events are written with weight w_unw / b.
test_mg7_custom_functions.py compares a custom cut, scale, PDF and bias with
the equivalent built-in settings on p p > t t~, and builds and evaluates every
function in every language with a toolchain. A new Usage page describes
bin/init_custom_func and the function signatures.
UmamiFunctions holds one std::function per UMAMI entry point. The dlopen path
fills it from the library symbols, and Context::add_matrix_element takes it
directly, so matrix elements can be implemented without a shared library.
Worker threads can then call back into Python. The abort check and the DLPack
deleters of Python-owned tensors take the GIL themselves.
ms.PythonFunction calls a Python function on numpy arrays or torch tensors with
selectable UMAMI inputs and outputs, and Context.add_matrix_element adds it to a
context. The inputs are zero-copy views of the madspace buffers. The results are
checked for type, dtype, shape and device before they are copied back. torch
functions run on the stream of the GPU runtime.
_madspace_py_loader imports it, so pybind11-stubgen failed to import the
staged package in the ENABLE_DOCS builds of CI.
[custom_functions] takes a .so library or a .py file. custom_function_loader
loads both, for bin/generate_events and gridpacks. bin/init_custom_func gains
the python-numpy and python-torch languages, which need no build. torch is
limited to one thread on the CPU, where madspace already splits the batches.
@oliviermattelaer

Copy link
Copy Markdown
Contributor

One comment on this:

  • Cut and scale see the event before the mirror flip; the bias and the written event see it after (integrand.cpp:586).
  • Systematics with a custom scale. resolve_dynamical_scales (launch.py:1024) still treats dynamical_scale_choice as the scale the events were generated with, so it silently drops that variation.
  • Gridpack with an absolute path. The file is not copied and data.json keeps the absolute path, with no warning (launch.py:1988). This contradicts the docs.
  • need to check with a GPU (claude reports three potential GPU issue)
    • torch gets tensors labelled device 0 even on cuda:1 (python_function.cpp:49).
    • The GPU wrapper uses fixed 32-particle arrays with no bounds check (wrapper_gpu.cu:56).
    • A CPU .so in a GPU run only fails at the first batch, with “Matrix element has incompatible device”, which doesn’t name the custom function. Check the device when the function is loaded.

One limitation, is that all parameter are hardcoded in the cuts/...
Would be nice to be able (like in MG5aMC) to define parameter such that you include the custom function (like a DJR cut function/...) and then specify the cut value in the run_card (such that we split the function from the cut.

Other points that we should consider is to allow to have different mu_f1, mu_f2 and mu_r

One suggestion would be to add tt~j with custom eta cut to check the mirroring part

@theoheimel

Copy link
Copy Markdown
Contributor Author

@oliviermattelaer Thanks for the feedback!

  • I can address the first point once the mirror-related stuff is merged (it probably solves itself then)
  • I'll check the other issues. I indeed have not tested it on GPU so far
  • Allowing for custom parameters is a good idea! Since the custom functions work through UMAMI, all the infrastructure for setting parameters is there already.

Combine the second-beam PDF (pdf_grid2) with custom PDFs, which get the
other beam index for mirrored events. The custom bias now sees the mapped
momenta, as the mirror is applied inside the mapping.
- cuts return bool in all templates, the wrappers convert to 1 or 0
- torch functions get tensors on the right GPU (Device::device_index)
- check the device of a custom library when it is loaded
- bound check of the particle count in the GPU wrapper
- add a p p > t t~ j test with an asymmetric jet cut for the mirroring
- parameters declared in parameters.h (X-macro, also for Fortran through
  the preprocessor) or PARAMETERS in Python, set in the run card as
  [custom_functions] <kind>_parameters.<name> = value, passed through
  umami_set_parameter; gridpacks keep the values
- a custom scale returns the renormalization scale and the factorization
  scales of both incoming particles (UMAMI_OUT_REN_SCALE,
  UMAMI_OUT_FACT_SCALE_1/2 replace UMAMI_OUT_SCALE)
- PythonFunction supports parameters, per UMAMI instance
- Fortran templates are .F90
@theoheimel
theoheimel marked this pull request as ready for review October 17, 2026 13:47

@oliviermattelaer oliviermattelaer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent

@theoheimel
theoheimel merged commit 1260a12 into main Oct 18, 2026
181 checks passed
@theoheimel
theoheimel deleted the feat-custom-umami-functions branch October 18, 2026 12:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants