Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
f8c1fa5
MadSpin: per-phase timing, plus a p p > t t~ benchmark driver
oliviermattelaer Aug 11, 2026
0251788
madspace: support a single incoming particle (1 -> n decays)
oliviermattelaer Aug 11, 2026
ee372e0
mg7 export: describe a decay's single initial leg instead of a beam pair
oliviermattelaer Aug 11, 2026
05bd960
mg7 launcher: run a decay directory (no beams, no PDF)
oliviermattelaer Aug 11, 2026
0b13099
mg7: a 1 -> n decay must default to no cuts
oliviermattelaer Aug 11, 2026
f96f3b6
MadSpin: generate the decay-event pools with mg7 instead of madevent
oliviermattelaer Aug 11, 2026
a1afe9d
mg7: build the matrix elements in parallel, and split the decay timings
oliviermattelaer Aug 11, 2026
2ee053f
MadSpin: read the decay pools as numpy, not LHE text
oliviermattelaer Aug 11, 2026
543c4dc
Stop gzipping at level 9, and stop repacking MadSpin's input
oliviermattelaer Aug 11, 2026
8faeecc
MadSpin: stop re-reading the param card and the process directory per…
oliviermattelaer Aug 11, 2026
5791693
lhe_parser: boost a whole event without allocating per particle
oliviermattelaer Aug 11, 2026
bc3707a
lhe_parser: hoist the boost divisions, drop the boost-vector copy
oliviermattelaer Aug 11, 2026
e2e6f4c
MadSpin: take numpy out of the two smallest density-matrix operations
oliviermattelaer Aug 11, 2026
bf9c1c6
f2py wrapper: add a batched density entry point
oliviermattelaer Aug 11, 2026
3b62c59
MadSpin: batch the max-weight scan's decay densities
oliviermattelaer Aug 11, 2026
7afb215
Revert the mg7 build parallelisation: it races, and #67 supersedes it
Aug 17, 2026
afbbf0a
Merge branch 'main' into claude/madspin-performance-optimization-4a418a
oliviermattelaer Aug 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 35 additions & 3 deletions MadSpin/decay.py
Original file line number Diff line number Diff line change
Expand Up @@ -5427,6 +5427,9 @@ class DensityMatrix:
# Cache diagonal masks by basis_id (depends only on helicities)
_diag_cache = {}

# Same, as integer positions, for trace()
_diag_pos_cache = {}

# Cache tensor-product helicity tables by basis_id
_tp_hel_cache = {}

Expand Down Expand Up @@ -5629,6 +5632,21 @@ def _get_diag_mask_cached(self):
DensityMatrix._diag_cache[self._basis_id] = mask
return mask

def _get_diag_positions_cached(self):
"""Positions of the diagonal entries, as a plain tuple of ints.

trace() sums a handful of entries -- two, for a single decaying
fermion -- and numpy costs about 0.9 us to do that however few there
are, nearly all of it dispatch. Indexing the cached positions directly
is 8x faster at that size. Cached per basis_id alongside the mask.
"""
cached = DensityMatrix._diag_pos_cache.get(self._basis_id)
if cached is not None:
return cached
positions = tuple(int(i) for i in np.flatnonzero(self._diag_mask))
DensityMatrix._diag_pos_cache[self._basis_id] = positions
return positions

# -------------------------------------------------------------------------
# Cached permutation for alignment by helicity labels
# -------------------------------------------------------------------------
Expand Down Expand Up @@ -5678,15 +5696,19 @@ def scalar_multiplication(self, other):
# Fastest correct path for map-built matrices
if (self.map_density_matrix_ind is not None and
self.map_density_matrix_ind is other.map_density_matrix_ind):
return np.sum(self.values * other.values)
# np.dot rather than np.sum(a*b): identical for complex (dot does
# not conjugate) but one call instead of two, and it skips the
# temporary the multiply would allocate. 0.23 us against 0.90 us
# on the 16-entry matrices this sees.
return np.dot(self.values, other.values)

# Align by cached ordering for each basis
self._ensure_sorted_view()
other._ensure_sorted_view()

a = self._sort_order
b = other._sort_order
return np.sum(self.values[a] * other.values[b])
return np.dot(self.values[a], other.values[b])

def tensor_product(self, other):
"""
Expand Down Expand Up @@ -5736,7 +5758,17 @@ def trace(self):
"""
Order-independent trace.
"""
return np.sum(self.values[self._diag_mask])
# Sum the diagonal entries by position. See
# _get_diag_positions_cached: at these sizes numpy's dispatch dwarfs
# the addition itself.
values = self.values
positions = self._get_diag_positions_cached()
if not positions:
return np.complex64(0)
total = values[positions[0]]
for i in positions[1:]:
total = total + values[i]
return total


def print_full_matrix(self, precision=6):
Expand Down
Loading
Loading