Skip to content

mg7: default histograms, plots with bands, and user defaults for the launch switches - #190

Closed
oliviermattelaer wants to merge 7 commits into
fix-output-formatsfrom
mg7-histograms-and-switch-defaults
Closed

oliviermattelaer wants to merge 7 commits into
fix-output-formatsfrom
mg7-histograms-and-switch-defaults

Conversation

@oliviermattelaer

Copy link
Copy Markdown
Contributor

Stacked on #188 (base fix-output-formats), because the first commit edits what that PR does. Please merge #188 first; the diff here is only the six commits on top of it.

1. Launch switches: user defaults, and the LHE auto-revert restored

#188 turned the silent output_format = 'lhe' override into an error. Put back: asking for a shower in the launch question is enough, a user should not have to know that it constrains output_format.

The switch that made that override visible (analysis defaulting to ExRoot whenever exrootanalysis_path is set — its default value even when nothing is installed) stays off, and gets a proper home instead: input/default_switch.txt, the switch counterpart of input/default_run_card_lo.dat.

analysis = MadAnalysis5
shower   = Pythia8

Read by ControlSwitch, so one file serves the LO madevent, NLO aMC@NLO and mg7 questions; each ignores the keys it does not have. Entries go through the usual validation and consistency resolution — an unavailable program is reported and skipped, and an NLO fixed_order = ON still drags the shower default off. Verified on a real LO and a real NLO output.

Along the way, three things that were quietly broken:

  • the run_card default files were read from MG5DIR/internal/, a directory that does not exist — input/default_run_card_{lo,nlo}.dat and input/default_run_card_mg7.toml were therefore never applied (the only test of the feature monkeypatches the path);
  • only default_run_card_lo.dat was checked before materialising all three, so an installation predating the mg7 one never received it;
  • AskRun.get_allowed_reweight returned None on its first call.

2. [histograms]: a default set of plots per process

An empty section nobody filled in. The observable names are built from the final state, so RunCardMG7.create_default_for_process now writes them at output time: the pt and eta of every final-state particle, the invariant mass of every pair, the partonic sqrt(s), and the distribution of the event weight.

[histograms]
t_1-pt.min = 0.0      t_1-pt.max = 500.0    t_1-pt.bin_count = 50
jet-eta.min = -5.0    jet-eta.max = 5.0     ...
t_1-t_2-pair_mass ... sqrt_s ... weight

A particle goes under a predefined group (jet, lepton, ...) when the run time cannot tell its members apart: MadGraph merges a group's flavours into one subprocess and only the merged representative reaches the observables, so p p > l+ l- is lepton, not e and mu (which would silently select nothing). Anything else gets a [multiparticles] group of its own (t = [6, -6]). Multiplicity is the smallest count over subprocesses — jet_2 in a subprocess with one jet is a hard error in madspace's build_indices, not an empty histogram.

Ranges are order-of-magnitude guesses from the collider energy, or from the decaying particle's mass for a 1 → N width. On p p > t t~ j at 13.6 TeV every histogram holds the cross section with under 1.2% in the overflow bin.

weight is the one key that is not an observable of the momenta. madspace gained EventHistogramSpec::from_weight for it: the event weight over the reference weight (the cross section), so the axis does not depend on the process — an unweighted sample is a spike at 1, a partially unweighted one shows its spread.

In the launch question, set histograms OFF removes them all and set histograms default puts the generated ones back.

3. Plots, with the bands, in Events/<run>/plots

Every run now draws one PDF per observable and says where:

combining done, 00:00:00.10 wall, 00:00:00.15 cpu
7 plot(s) stored in Events/run_02/plots

Read straight from the event-sample histograms rather than through an HwU file, which is what buys the bands: the scale envelope and the PDF uncertainty are already computed per bin by madspace, while histograms.py has to rebuild the PDF band from the member weights and needs the LHAPDF python module to do it — not installed everywhere, and broken on python 3.14. Each plot is dσ/dx with its statistical errors, the bands behind it, and a second panel with the bands relative to the nominal. The y axis goes logarithmic when the spectrum falls by more than a factor 50.

[run] make_plots, true by default, turns it off. matplotlib is imported lazily and only inside plots.py: a run without it says so once and carries on.

4. MADatLO.HwU (opt-in)

For comparison with NLO: the same distributions in the format aMC@NLO writes as MADatNLO.HwU, so madgraph/various/histograms.py plots and compares them with no conversion step. [run] write_hwu, false by default — it is a second copy of numbers info.json already carries.

The variation columns are labelled muR=1.00 muF=2.00 / PDF= 331901 (and dyn=3 muR=...), which is what makes histograms.py read them as an envelope and a band; the envelope it rebuilds from them is bin-by-bin the one madspace computed. The group a weight belongs to decides its label, not the variation's own fields — a PDF set's central member carries mur = muf = 1 and would otherwise be written as a scale variation, hiding it from the band.

Two conventions applied on the way out, both of them where a converter goes wrong: HwU bins hold dσ/dx, so every column is divided by the bin width; and HwU has no under/overflow bins, so the first and last entries of the madspace arrays are dropped, as an aMC@NLO analysis does.

5. Which python runs a run, and a quiet stdout

bin/generate_events trusted #! /usr/bin/env python3. A run needs the packages of the environment MadGraph7 was started from — matplotlib, the LHAPDF bindings, madspace — and the python3 the PATH resolves to often has none of them; on one setup here the plots were silently skipped for exactly that reason. The launcher now pins sys.executable and, because a shebang is only a default, re-executes itself through that interpreter when it finds it is running somewhere else.

The path is deliberately not resolved through its symlinks: in a virtual environment sys.executable points at the environment, and the base interpreter it links to does not see its site-packages — taking the realpath handed the run a python without matplotlib. A vanished interpreter is ignored rather than fatal, and the gridpack, which is the portable artefact, keeps its /usr/bin/env python3.

matplotlib narrates every font it embeds in a PDF at DEBUG level — 28549 lines for one run here, once anything lets DEBUG through the root logger. Its loggers are pinned to WARNING before the import, since matplotlib logs its own start-up while being imported.

Testing

  • Full unit suite: 2130 tests, 1 failure — the pre-existing test_DensityMatrixObservables22 (No module named 'scipy').
  • madspace: 1584 passed, 11 skipped for a missing PDF set. A madspace rebuild is needed for the weight histogram (python madspace/install.py --source).
  • New: 12 for the switch defaults, 13 for the histogram generator, 10 for the HwU writer (which read the result back with madgraph.various.histograms rather than checking the text against itself), 12 for the plots, 7 for the launcher re-exec, 7 in test_mg7_launch.py for the switches.
  • End to end on real outputs: LO e+ e- > mu+ mu-, NLO p p > e+ e- [QCD], mg7 p p > t t~ and p p > t t~ j.

🤖 Generated with Claude Code

oliviermattelaer and others added 6 commits September 20, 2026 20:27
Restore the automatic switch to output_format = 'lhe' when a post-processing
tool is selected on an mg7 run: the npy formats carry no event record those
tools can read, but asking for a shower in the launch question should be
enough -- the user does not have to know that it constrains output_format.

Add input/default_switch.txt (materialised from the shipped, fully commented
input/.default_switch.txt), the switch counterpart of
input/default_run_card_lo.dat: "analysis = MadAnalysis5" there is the file
version of typing "analysis=MadAnalysis5" at every launch. It is read by
ControlSwitch, so the same file serves the LO madevent, NLO aMC@NLO and mg7
questions; each ignores the keys it does not have. Entries go through the
usual validation and consistency resolution, so an unavailable program is
reported and skipped rather than silently accepted, and an NLO
"fixed_order = ON" still drags the shower default off.

Along the way:
 - the run_card default files were read from MG5DIR/internal/, a directory
   that does not exist: input/default_run_card_{lo,nlo}.dat and
   input/default_run_card_mg7.toml were therefore never applied.
 - only default_run_card_lo.dat was checked before materialising all three,
   so an installation predating the mg7 one never got it.
 - AskRun.get_allowed_reweight returned None on its first call.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
[histograms] was an empty section nobody filled in. The observables it takes
are named after the final state, so they can only be written once the process
is known: RunCardMG7.create_default_for_process now fills it with the pt and
eta of every final-state particle, the invariant mass of every pair of them,
the partonic sqrt(s) and the distribution of the event weight.

The particles are named after the [multiparticles] groups, with the "_1",
"_2", ... suffix picking the hardest, second hardest, ... of a group:
`p p > t t~ j` gets t_1-pt, t_2-pt, jet-pt, the three etas, the three pair
masses and sqrt_s. A particle is histogrammed under a predefined group (jet,
lepton, ...) when the run time cannot tell its members apart -- MadGraph
merges the flavours of a group into a single subprocess and only the merged
representative reaches the observables, so `p p > l+ l-` is "lepton", not "e"
and "mu" (the latter would silently select nothing). A particle that matches
none of that, like the top, gets a [multiparticles] group of its own. The
multiplicity is the smallest over the subprocesses, since asking for the n-th
hardest particle of a group is an error in a subprocess that has fewer.

The ranges are order-of-magnitude guesses from the collider energy (or the
mass of the decaying particle for a 1 -> N width), rounded to 1/2/5 x 10^k.
On p p > t t~ j at 13.6 TeV every histogram holds the cross section with
under 1.2% in the overflow bin.

"weight" is the one [histograms] key that is not an observable of the
momenta. madspace gained EventHistogramSpec::from_weight for it: the event
weight divided by the reference weight (the cross section, i.e. the mean
weight), so the axis does not depend on the process -- a fully unweighted
sample is a spike at 1 and a partially unweighted one shows its spread.

In the launch question, "set histograms OFF" removes them all and
"set histograms default" puts the generated ones back from
Cards/run_card_default.toml.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The histograms only existed as JSON inside Events/<run>/info.json, which
nothing else in MadGraph reads. HwU is the format the rest of the code speaks
-- an aMC@NLO run writes MADatNLO.HwU and madgraph/various/histograms.py
plots and compares those -- so an mg7 run now writes the same thing next to
its events, and an LO distribution can be overlaid on an NLO one with no
conversion step in between.

Everything info.json carries goes into the file: the nominal distribution, one
column per systematics variation, and the scale/PDF bands. The variation
columns are labelled "muR=1.00 muF=2.00" / "PDF= 331901" (and
"dyn=3 muR=..." for a dynamical scale), which is what makes histograms.py read
them as an envelope and a band rather than as anonymous curves -- the envelope
it rebuilds from them is bin-by-bin the one madspace computed. The group a
weight belongs to decides its label, not the variation's own fields: the
central member of a PDF set carries mur = muf = 1 and would otherwise be
written as a scale variation, hiding it from the band.

Two conventions of the format are applied on the way out, both of them where a
converter goes wrong: HwU bins hold dsigma/dx, so every column is divided by
the bin width (sum(central * width) is then the cross section), and HwU has no
under/overflow bins, so the first and last entries of the madspace arrays are
dropped, as an aMC@NLO analysis does.

The conversion is a pure function of the two dicts madspace writes, in
hwu_output.py, which imports nothing. The tests read the result back with
madgraph.various.histograms rather than checking the text against itself.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
MADatLO.HwU is a second copy, in another format, of numbers Events/<run>/
info.json already carries, so it is now opt-in rather than written by every
run: [run] write_hwu in the run_card, false by default, or "set write_hwu
True" in the launch question.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…plots

A run had no picture of anything: info.json held the distributions and
MADatLO.HwU could be fed to histograms.py by hand, but nothing was ever drawn.
Every run now writes one PDF per observable into Events/<run>/plots and says
where they went ("11 plot(s) stored in Events/run_01/plots").

The drawing reads the event-sample histograms straight from madspace rather
than going through the HwU file, which buys the bands: the scale envelope and
the PDF uncertainty are already computed per bin there, while histograms.py
has to rebuild the PDF band from the member weights and needs the LHAPDF
python module to do it -- not installed everywhere, and broken on python 3.14.
Each plot is the differential cross section with its statistical errors, the
bands behind it, and a second panel with the bands relative to the nominal.
The y axis goes logarithmic when the spectrum falls by more than a factor 50,
which is what tells a pt spectrum from an eta one.

The conventions are the ones the other two consumers of these numbers already
use, so the three agree: dsigma/dx on the y axis, and no under/overflow bins
(HwU has none, and MadBoard's browser plots slice them off too).

[run] make_plots, true by default, turns it off. matplotlib is imported
lazily and only inside plots.py: a run without it says so once and carries on,
every number being in info.json either way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… quiet

Two things a run got wrong on its own stdout.

bin/generate_events trusted "#! /usr/bin/env python3". A run needs the
packages of the environment MadGraph7 was started from -- matplotlib for the
plots, the LHAPDF bindings, madspace -- and the python3 the PATH resolves to
often has none of them: on this setup the plots were silently skipped for
exactly that reason. The launcher now pins sys.executable as its shebang and,
because a shebang is only a default (bypassed by "python3 bin/generate_events",
ignored by the kernel when the path is too long), re-executes itself through
that interpreter whenever it finds it is running somewhere else. Both paths
draw the plots now. The path is deliberately not resolved through its
symlinks: in a virtual environment sys.executable points at the environment,
and the base interpreter it links to does not see its site-packages -- taking
the realpath handed the run a python without matplotlib. A vanished
interpreter is ignored rather than fatal, and the gridpack, which is the
portable artefact, keeps its "/usr/bin/env python3".

matplotlib narrates every font it embeds in a PDF at DEBUG level, which was a
dozen lines per plot -- 28549 of them for one run here -- as soon as anything
let DEBUG records through the root logger. Its loggers are now pinned to
WARNING, before the import rather than after: matplotlib logs its own start-up
while it is being imported, and a logger silenced afterwards is silenced four
lines too late. An explicit level above WARNING is left alone.

The launcher text moved into mg7_launcher_source() so that the re-exec can be
tested by running it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… to it

The test pasted its one sqrt_s histogram right after the [histograms] header,
which was fine while that section was always empty. The output now writes
process-specific default histograms there, sqrt_s among them, so the edited
card defined sqrt_s.min twice: a TOML error ("Cannot overwrite a value") on
which generate_events died at once. It died with exit code 0, so the helper's
return-code check never printed the log and the test reported "no mg7 run
directory" instead.

The test also relies on its histogram being the only one (it reads
event_histograms[0] and expects 10 bins), so the section's content is now
replaced as a whole, which keeps that true whatever the defaults generate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
oliviermattelaer added a commit that referenced this pull request Sep 23, 2026
…branch

Takes #190's default [histograms], plots/HwU output and input/default_switch.txt.
Both branches restore the automatic switch to LHE; keep this branch's
force_lhe_output_if_needed (raw-TOML edit that survives scan cards, driven by
selected_tools() and the [postprocessing] steps), with #190's rationale in
its docstring.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@oliviermattelaer

Copy link
Copy Markdown
Contributor Author

Superseded by #195, which merges this branch in (default histograms, plots/HwU output, input/default_switch.txt, restored LHE auto-switch). On top of it, #195 adds [run] weighted_histograms (default off) and postprocessing_histograms (default on), and fixes the scan-summary means, which counted the under/overflow bins as in-range bins.

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.

1 participant