Repository navigation
madspace: always draw the beam orientation before the mapping - #245
Conversation
2 -> 1 (p p > z, p p > h): a collision Diagram may have one outgoing leg. PhaseSpaceMapping integrates dPhi_1 = 2 pi delta(s_hat - m^2) against the luminosity: s_hat is fixed at the mass and the single random number sets the rapidity. Leptonic beams or a massless final state are refused. run_card.toml [beam] now has ebeam1/ebeam2 and pdf1/pdf2. beam.e_cm and beam.pdf stay readable and settable (both beams), and cards written with e_cm/pdf are translated on read. The phase space is generated in the beams' centre-of-mass frame (s = 4 E1 E2) and boosted by 0.5 ln(E1/E2) into the lab frame before the cuts, so events are written, and eta cuts applied, in the frame the user gave. Asymmetric beams break the Integrand's mirror shortcut (mirroring accepted lab events with unswapped PDFs). With mirror_beams the orientation is drawn before the mapping, the mirror is taken in the beams' frame, and each leg takes the PDF of the beam it comes from. Identical beams keep the old path. The second beam's grid is passed to Integrand and SystematicsCalculator (pdf_grid2 / nominal_pdf2); PDF member variations are dropped when the sets differ, scale variations use both. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The run card now stores pdf1/pdf2, so the `pdf = "..."` substitution matched nothing and the reference runs silently used the default NNPDF4.0 set instead of NNPDF23_lo_as_0130_qed (3jets, dy1j, ttx1j and ttx_decay off by 2-43%). Pin pdf1 and pdf2 (or a legacy pdf line) and fail loudly when nothing was pinned. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The initial-state mirror covers the beam-swapped copy of a subprocess by
rotating the event by pi about x (py, pz -> -py, -pz, kernel_mirror_momenta),
which moves each leg onto the other beam. With mirror_beams the mapping draws
the orientation first, so the rotation happens before the boost into the lab
frame and before the cuts. Without it the Integrand keeps the cheaper path:
generate, cut, then mirror the accepted event. Everything downstream of that
mirror then sees an orientation nothing upstream ever looked at.
Two places were reading the wrong side of it.
The matrix element. Integrand::build_common_part kept feeding the cross
section momenta_acc, the momenta as generated, while the event was written
mirrored. It now takes the mirrored set when there is one, which is what
madevent does (it flips the momentum array that then goes to both the matrix
element and the event record, super_auto_dsig_group_v4.inc "Flip momenta
(rotate around x axis)") and what SystematicsCalculator::matrix_elements
already assumed, since it reads the momenta back out of the event buffer.
This is the same one-line change as "madspace: evaluate the matrix element on
the momenta the event is written with" on the mirror-me-momenta branch,
carried onto the post-cut path that only exists here; on the mirror_beams
path momenta_acc is already the written orientation and nothing changes.
Feeding the unmirrored momenta only ever worked because |M|^2 is invariant
under the rotation -- true for any Lorentz invariant matrix element, false
for a polarised one evaluated in a frame that holds the polarised particle at
rest, where HELAS quantises the spin along the frame z axis (48% on
g q > z{+} q in the Z rest frame, 23 sigma between p p > z{+} j and
p p > z{-} j end to end).
The cuts. Nothing stated that the cuts have to be blind to the mirror, and
nothing enforced it; it happened to hold because pt, |eta|, delta_r, mass and
sqrt_s all are. Observable::mirror_invariant classifies each observable (and
the one it is ordered by -- sorting by a quantity that flips picks a different
particle), Cuts::non_mirror_invariant_cuts names the offenders, and Integrand
refuses the post-cut mirror when any cut is one, next to the asymmetric-beams
check it already had. mg7 does not hit that refusal: launch.py now turns
mirror_beams on for a subprocess whose cuts are not mirror invariant, the same
switch asymmetric beams already trigger, and logs why.
This is not latent. The run_card [cuts] table takes any observable name, and
the template even advertises "eta" -- p p > e+ e- with lepton-eta.min = 0.0:
post-cut mirror 276.3 +- 2.1 pb, 1017 of 2000 events (50.9%) written
with a lepton at eta < 0, i.e. outside the cut
orientation first 233.9 +- 2.1 pb, no event outside the cut
and the second is right: lepton-eta.max = 0.0, which a pp collider's symmetry
forces to be equal, gives 232.9 +- 2.2 pb (0.3 sigma). The old number is 18%
high because it integrates one beam orientation twice, and a single ordering
of a valence-sea pair is forward-skewed.
Nothing on the invariant-cut path moves: default cuts keep the post-cut mirror
(no switch logged) and g _quark > e+ e- _quark gives 59.0349 +- 0.0490 pb
against the 59.0235 +- 0.0287 reference, 0.2 sigma.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Conflicts, all from the madspace documentation PR (#92) meeting this branch's API additions; no logic conflicted: - systematics.hpp, cuts.hpp, observable.hpp, integrand.hpp: main's Doxygen comments next to new members (_nominal_pdf2, the mirror-invariance queries, pdf2_prefix). Both kept; the new members' comments turned into Doxygen and the pdf_grid2 / nominal_pdf2 argument notes moved into the constructors' @PARAM lists. - phasespace.hpp: main's Doxygen for random_dim_for, extended with the 2 -> 1 case and @throws; beam_rapidity / mirror_beams documented as @PARAM of both constructors and mirror_index added to the documented conditions; the new accessors documented. - madspace.cpp: main's pydoc::doc() bindings, with beam_rapidity, mirror_beams, pdf_grid2 and nominal_pdf2 inserted before the docstring, main's external_masses argument name kept, and the new PhaseSpaceMapping and Cuts methods added to main's documented method chains. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Follow-up to the merge of origin/main (12e2e16): - Observable::mirror_invariant(ObservableOption), the static per-option overload, becomes option_mirror_invariant() in observable.cpp's anonymous namespace. Nothing outside that file called it, and as an overload with its own doc comment it gave generate_docstrings two entries for one name (Observable::mirror_invariant / #2), which is why the Python method was bound without a docstring. It now gets pydoc::doc like every other binding. - clang-format on the Integrand error message and black/isort on test_beam_frame.py, which the madspace pre-commit hooks run. pydoc keys checked against docstrings generated with doxygen (ENABLE_DOCS=ON only runs on pushes to main): all 613 doc() calls resolve. madspace suite 1605 passed, 11 skipped, 4 failed (scipy not installed, as before the merge). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two conflicts: - banner.py: both sides added an entry to _LO_SCALAR_MAP (ebeam1/ebeam2 here, scalefact -> beam.scale_factor on main). All three kept. - phasespace.cpp: main's boost_beam/boost_beam_inverse now take the external masses, so the boost of a momentum moving against it can be done in light-cone components and stay on shell. to_lab/from_lab pass lab_masses(_topology, _permutations) to every boost, including the beam-rapidity one; the mirror in between is a rotation and leaves the masses alone. madspace pytest 1657 passed / 11 skipped, run_card 19/19, mg7 launch 47/47, and the asymmetric-beam and per-beam-PDF runs reproduce their pre-merge cross sections (941.2, 1817.4, 44211, 44223 pb). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Conflicts: - mg7/launch.py: keep both new imports (gc from main, math from here), and both blocks added after the cuts -- the mirror-orientation choice from here and main's momentum_hists / weighted_histograms gating. - test_banner.py: main's new output_format default (lhe_npy) together with this branch's per-beam [beam] assertions (ebeam1/ebeam2/pdf1/pdf2, no e_cm). Semantic fix: main's TestForceLHEOutput.test_scan_card_survives edited an `e_cm = 13000.0` line that the per-beam card no longer writes; it now puts the scan on `ebeam1` instead, which tests the same thing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mirrored events carry the flavor tables' leg order on swapped beams, so the first incoming particle could move along -z and orderings like 'u g' never appeared. Swap the incoming records when particle 1 has pz < 0.
Same main commit as the PR #154 merge 96b1e38, merged as a real merge rather than cherry-picked with -m 1, so main keeps its ancestry here. Conflicts, resolved as in 96b1e38: - banner.py [beam]: main gave every parameter a one-line comment= help string; keep ebeam1/ebeam2/pdf1/pdf2 with help strings in that style. - RunCardLO_to_MG7_mapping.md: deleted on main (modified here for the per-beam rows); delete it. Its per-beam rows live in the new docs/source/madevent_to_mg7.rst, and docs/source/run_card.rst documents ebeam1/ebeam2/pdf1/pdf2 (both taken from 96b1e38). - run_card.toml [beam]: one-line comments, as main now uses (from 96b1e38). The madspace conflicts of 96b1e38 (phasespace, cuts, integrand, python bindings, test_beam_frame) do not arise here: bcedaa0 already merged that part of main, and the lab-frame eta windows are handled by lab_fractions instead of widening node_y_max by |y0|.
At the launch question, set ebeam/ebeam1/ebeam2, lpp/lpp1/lpp2, pdlabel/pdlabel1/pdlabel2 and lhaid were "invalid set command" (except the ebeam ones, which reached _LO_SCALAR_MAP) and the run kept the defaults. RunCardMG7.set_lo_beam_param now maps them, a trailing 1/2 acting on that beam and the other names on both: - ebeam*: beam.ebeam1/2, with units and mass expressions. - pdlabel*/lhaid: beam.pdf1/2 through the label and lhaid tables of the run_card.dat conversion; an lhaid outside the table is looked up in the pdfsets.index of the LHAPDF data paths. 'lhapdf' alone changes nothing, 'none' on both beams is beam.leptonic = true. - lpp*: the single beam.leptonic switch (0, +-3, +-4 lepton, +-1 hadron), with a warning when the two beams were given different types. A value mg7 cannot represent is reported and leaves the card alone. from_LO keeps different pdlabel1/pdlabel2 per beam instead of reporting them as dropped. (cherry picked from commit 1120a16)
Merge with main (10faf1d)Only conflict: Eta cuts with asymmetric beams and the mirrorCode: Mapping level (no matrix element): 10 topologies (DY, massive t t~, Z g, e e g g, a a and a g a in the t channel, Z j with a composite child, chili) × y0 ∈ {0, 0.28, −1} × mirror on/off:
Full mg7 runs with |η| < 2.5, compared with a run without the eta cut where the cut is applied to its events afterwards:
|
Conflict: - mg7/launch.py build_lhe_meta: this branch writes the LHAPDF id of each beam (_lhapdf_id(1), _lhapdf_id(2)); main replaced the fixed weight_mode=3 by the one lhe_weight_info derives from the run. Keep both.
oliviermattelaer
left a comment
There was a problem hiding this comment.
Eta cuts with asymmetric beams and the mirror validated, main merged (details in the comment above).
Stacked on #154 (base is its branch, so merge #154 first or retarget to main).
Summary
mirror_beams = true), so boost, cuts and PDFs all see the orientation the event is written with. The post-cut mirror (momenta_mirror_acc,mirror_id_acc, themomenta_meselection) is removed.Integrandthrows if a flavor is mirrored and the mapping was not built withmirror_beams = true.launch.pysetsmirror_beamsfromhas_mirrored_flavors(); asymmetric beams without mirrored flavors only get the y0 boost.Cuts::non_mirror_invariant_cuts,Cuts::mirror_invariant,Observable::mirror_invariant) and its bindings. Signed cuts (eta, y, phi, pz) with symmetric beams now just work instead of throwing."permutation_index","mirror_index") instead of positional indexing, inPhaseSpaceMappingandIntegrand.Fix after merging main
Main's cut-aware rapidity windows assume the beams' frame is the lab frame. With
beam_rapidity != 0that lost ~9% of the cross section (52% accepted vs 81% after the fix) for rambo/propagator modes:x1 e^{y0},x2 e^{-y0}(lab_fractions), with the sign of y0 flipped for mirrored events.New test
test_cut_aware_sampling_loses_nothing_with_asymmetric_beams(with and withoutmirror_beams); I checked it fails with the mirror sign flipped.Behavior changes to be aware of
p p > e+ e-comparison of against main has not been done.test_mirror_beams_cuts_the_written_orientationwas weakened: cut-aware sampling means the cut and uncut mappings no longer map the same random numbers to the same momenta, so it now checks that accepted points pass the cut as written.LHE incoming order
Events were written with the incoming particles in the flavor tables' leg order, so mirrored events had particle 1 moving along -z and orderings such as
u gnever appeared (reported by a collaborator; reproduced on this branch withp p > z j: onlyg u, with pz(1) < 0 in about half of the events).LHECompleter::complete_event_datanow swaps the two incoming records when particle 1 has pz < 0, so particle 1 always comes from beam 1. On the same process both orderings now appear in equal numbers and pz(1) > 0 for every event. The systematics reweighting reads the event buffer and is unaffected. Test:test_first_incoming_particle_moves_along_plus_z. I did not check whethermainshows the same ordering.Testing
test_mg7_launch.py,test_banner.py: 151 passedtest_check_xsec_processes_mg7.py) not run locally; relying on CI.