HNL hadronic and electroweak decays#110
Conversation
Squashed diff for paths: - projects/interactions/private/HNLDecay.cxx - projects/interactions/public/SIREN/interactions/HNLDecay.h - projects/interactions/private/pybindings/HNLDecay.h - projects/interactions/private/ElectroweakDecay.cxx - projects/interactions/public/SIREN/interactions/ElectroweakDecay.h - projects/interactions/private/HNLDipoleDecay.cxx - projects/interactions/public/SIREN/interactions/HNLDipoleDecay.h - projects/interactions/private/pybindings/HNLDipoleDecay.h - projects/interactions/private/pybindings/interactions.cxx Source commits on dev/HNL_DIS_clean that contributed: 197bf14 (Nicholas Kamp): fixing build errors from the merge 1bbd678 (Nicholas Kamp): fix memory leak 2219939 (nickkamp1): first step at EW decay 285285f (Nicholas Kamp): kinematics fix to 2body decays 3625e81 (Nicholas Kamp): fix build errors, add 3 nu decay 39c5407 (nickkamp1): implement sampling for secondary decay 3d95c4e (nickkamp1): more EW decay logic, add prototype surface detector 5031575 (Nicholas Kamp): initial suite of changes to re-implement the dipole DIS from spline class in the SIREN parlance 5e2b658 (Nicholas Kamp): start MH sampling template 6f19de6 (nickkamp1): start with diff xs 72e02b4 (Nicholas Kamp): N4Bar decay fix 7c8a8f1 (Nicholas Kamp): continue implementing diff dec width, start w sampling 8283177 (Nicholas Kamp): start implementing 3 body sampling logic 88c13f0 (Nicholas Kamp): decay widths now match literature 949125f (Nicholas Kamp): overhaul the three body decay sampling logic.. some build errors remain 96f16cc (Nicholas Kamp): build fixes, total decay width fixes b6eba95 (Nicholas Kamp): total decay width for EW decay b895e21 (Nicholas Kamp): add lots of sampling code c3b7765 (Nicholas Kamp): fix build warnings, add more dipole hnl splines dc0281b (Nicholas Kamp): Total decay widths implemented, add ND280 detector ea13b30 (nickkamp1): Rename some classes, flush out the two body decay class f6f8b46 (Nicholas Kamp): get the decay sampling working feddaca (Nicholas Kamp): three body decays now run!! Co-authored-by: nickkamp1 <nwkamp@mit.edu>
9730149 to
6308e14
Compare
ff12237 to
26c739b
Compare
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds hadronic and electroweak decay support for HNL-related interactions, including new decay classes and Python bindings, as part of the stacked refactor of dev/HNL_DIS.
Changes:
- Introduces
ElectroweakDecayandHNLDipoleDecayclasses (headers + implementations) with decay-width calculations and final-state sampling. - Extends
HNLDecayto include hadronic/meson channels, 3-body decays, and QCD corrections. - Updates pybind11 module registration to expose the new/updated decay types to Python.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| projects/interactions/public/SIREN/interactions/HNLDipoleDecay.h | Public API + cereal serialization for the new dipole decay. |
| projects/interactions/private/HNLDipoleDecay.cxx | Implements dipole decay widths, signatures, and sampling. |
| projects/interactions/public/SIREN/interactions/HNLDecay.h | Extends HNLDecay API (hadronic/EW helpers, updated sampling signature). |
| projects/interactions/private/HNLDecay.cxx | Implements hadronic/EW/3-body decay widths and sampling logic. |
| projects/interactions/public/SIREN/interactions/ElectroweakDecay.h | Public API + cereal serialization for electroweak boson decays. |
| projects/interactions/private/ElectroweakDecay.cxx | Implements W/Z decay widths, signatures, and sampling. |
| projects/interactions/private/pybindings/interactions.cxx | Registers new interactions/decays in the Python module. |
| projects/interactions/private/pybindings/HNLDipoleDecay.h | Adds Python bindings for HNLDipoleDecay. |
| projects/interactions/private/pybindings/HNLDecay.h | Adds Python bindings for HNLDecay. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| double _dipole_coupling; | ||
| ChiralNature _nature; | ||
|
|
||
| archive(::cereal::make_nvp("PrimaryTypes", _primary_types)); | ||
| archive(::cereal::make_nvp("HNLMass", _hnl_mass)); | ||
| archive(::cereal::make_nvp("DipoleCoupling", _dipole_coupling)); | ||
| archive(::cereal::make_nvp("ChiralNature", _nature)); | ||
| construct(_hnl_mass, _dipole_coupling, _nature, _primary_types); |
There was a problem hiding this comment.
The cereal schema is inconsistent: save() archives DipoleCoupling as std::vector<double> and ChiralNature as an int, but load_and_construct() reads DipoleCoupling into a double and ChiralNature into an enum. This will fail to deserialize. Align types by loading a std::vector<double> for DipoleCoupling (or change save() to store a single double if that’s the intended schema) and load ChiralNature via an int followed by a static_cast<ChiralNature>.
| double _dipole_coupling; | |
| ChiralNature _nature; | |
| archive(::cereal::make_nvp("PrimaryTypes", _primary_types)); | |
| archive(::cereal::make_nvp("HNLMass", _hnl_mass)); | |
| archive(::cereal::make_nvp("DipoleCoupling", _dipole_coupling)); | |
| archive(::cereal::make_nvp("ChiralNature", _nature)); | |
| construct(_hnl_mass, _dipole_coupling, _nature, _primary_types); | |
| std::vector<double> _dipole_coupling; | |
| int _nature_int; | |
| archive(::cereal::make_nvp("PrimaryTypes", _primary_types)); | |
| archive(::cereal::make_nvp("HNLMass", _hnl_mass)); | |
| archive(::cereal::make_nvp("DipoleCoupling", _dipole_coupling)); | |
| archive(::cereal::make_nvp("ChiralNature", _nature_int)); | |
| construct(_hnl_mass, _dipole_coupling, static_cast<ChiralNature>(_nature_int), _primary_types); |
| void load_and_construct(Archive & archive, cereal::construct<ElectroweakDecay> & construct, std::uint32_t version) { | ||
| if(version == 0) { | ||
| std::set<siren::dataclasses::ParticleType> _primary_types; | ||
| construct(_primary_types); | ||
| archive(::cereal::make_nvp("Decay", cereal::virtual_base_class<Decay>(construct.ptr()))); | ||
| } else { | ||
| throw std::runtime_error("ElectroweakDecay only supports version <= 0!"); | ||
| } | ||
| } |
There was a problem hiding this comment.
Deserialization drops PrimaryTypes: _primary_types is never loaded from the archive, and construct(_primary_types) is called with an empty set. This makes serialized objects lose configuration. Load PrimaryTypes before constructing (and keep the save()/load_and_construct() fields symmetric).
| int iup = 0; | ||
| for(auto ubar : UpAntiQuarks) { | ||
| for(auto d : DownQuarks) { | ||
| if (record.signature.secondary_types[0]==ubar && record.signature.secondary_types[1]==d) { | ||
| return V_CKM.at(std::make_pair(UpQuarks[iup],d)) * GammaW; | ||
| } | ||
| } | ||
| ++iup; | ||
| } |
There was a problem hiding this comment.
The W→qq' partial width factor is incorrect: the amplitude should scale with |V\_CKM|, but the width scales with |V\_CKM|^2 and includes a color factor of 3 relative to leptonic channels. Returning V_CKM * GammaW underestimates/overestimates and can even go negative if signs appear. Use 3 * std::pow(V_CKM, 2) * GammaW (or equivalent) and ensure consistency with how GammaW is defined.
| if (proxyRecord.signature.secondary_types[0] == dataclasses::ParticleType::EMinus || | ||
| proxyRecord.signature.secondary_types[0] == dataclasses::ParticleType::MuMinus || | ||
| proxyRecord.signature.secondary_types[0] == dataclasses::ParticleType::TauMinus || | ||
| proxyRecord.signature.secondary_types[0] == dataclasses::ParticleType::EPlus || | ||
| proxyRecord.signature.secondary_types[0] == dataclasses::ParticleType::MuPlus || | ||
| proxyRecord.signature.secondary_types[0] == dataclasses::ParticleType::TauPlus) { | ||
| for(auto meson : PlusChargedMesons) { | ||
| proxyRecord.signature.secondary_types[1] = meson; | ||
| GammaMeson += TotalDecayWidthForFinalState(proxyRecord); | ||
| } | ||
| } | ||
| else if (proxyRecord.signature.secondary_types[0] == dataclasses::ParticleType::EPlus || | ||
| proxyRecord.signature.secondary_types[0] == dataclasses::ParticleType::MuPlus || | ||
| proxyRecord.signature.secondary_types[0] == dataclasses::ParticleType::TauPlus) { |
There was a problem hiding this comment.
The charge-selection logic is broken: the first if includes both negative and positive leptons, so the else if branch for positive leptons is unreachable. As a result, this always sums PlusChargedMesons even when the lepton is positively charged. Split the first condition to only match negative leptons (EMinus/MuMinus/TauMinus), and keep the second branch for positive leptons to sum MinusChargedMesons.
| rk::P4 pHNL(geom3::Vector3(record.primary_momentum[1], record.primary_momentum[2], record.primary_momentum[3]), record.primary_mass); | ||
| rk::Boost boost_to_lab = pHNL.labBoost(); | ||
| rk::P4 k3_HNLRest(p3_HNLRest*geom3::Vector3(SinTheta3_HNLRest*cos(Phi3_HNLRest),SinTheta3_HNLRest*sin(Phi3_HNLRest),CosTheta3_HNLRest),m_alpha); | ||
| rk::P4 k4_HNLRest(p4_HNLRest*geom3::Vector3(SinTheta4_HNLRest*cos(Phi4_HNLRest),SinTheta4_HNLRest*sin(Phi4_HNLRest),CosTheta4_HNLRest),m_alpha); |
There was a problem hiding this comment.
The k4 four-vector is constructed with m_alpha instead of m_beta, which will give the wrong invariant mass/energy for the second charged lepton in the 3-body final state. Use m_beta for k4_HNLRest.
| rk::P4 k4_HNLRest(p4_HNLRest*geom3::Vector3(SinTheta4_HNLRest*cos(Phi4_HNLRest),SinTheta4_HNLRest*sin(Phi4_HNLRest),CosTheta4_HNLRest),m_alpha); | |
| rk::P4 k4_HNLRest(p4_HNLRest*geom3::Vector3(SinTheta4_HNLRest*cos(Phi4_HNLRest),SinTheta4_HNLRest*sin(Phi4_HNLRest),CosTheta4_HNLRest),m_beta); |
| friend cereal::access; | ||
| protected: | ||
| HNLDecay() {}; | ||
| HNLDecay() {crundec = new CRunDec();}; |
There was a problem hiding this comment.
Manual ownership of CRunDec* without a visible destructor/copy-control risks leaks and double-frees (e.g., copies of HNLDecay will copy the raw pointer). Prefer storing CRunDec by value or using std::unique_ptr<CRunDec> and implementing/defining appropriate copy/move behavior (or deleting copy operations).
| ChiralNature nature; | ||
| const std::set<siren::dataclasses::ParticleType> primary_types = {siren::dataclasses::ParticleType::NuF4, siren::dataclasses::ParticleType::NuF4Bar}; | ||
| const std::set<siren::dataclasses::ParticleType> primary_types = {siren::dataclasses::ParticleType::N4, siren::dataclasses::ParticleType::N4Bar}; | ||
| CRunDec * crundec; |
There was a problem hiding this comment.
Manual ownership of CRunDec* without a visible destructor/copy-control risks leaks and double-frees (e.g., copies of HNLDecay will copy the raw pointer). Prefer storing CRunDec by value or using std::unique_ptr<CRunDec> and implementing/defining appropriate copy/move behavior (or deleting copy operations).
| HNLDecay(double hnl_mass, std::vector<double> mixing, ChiralNature nature) : hnl_mass(hnl_mass), mixing(mixing), nature(nature) {crundec = new CRunDec();}; | ||
| HNLDecay(double hnl_mass, std::vector<double> mixing, ChiralNature nature, std::set<siren::dataclasses::ParticleType> const & primary_types) : hnl_mass(hnl_mass), mixing(mixing), nature(nature), primary_types(primary_types) {crundec = new CRunDec();}; | ||
| HNLDecay(double hnl_mass, double mixing, ChiralNature nature) : hnl_mass(hnl_mass), mixing(std::vector<double>{0,0,mixing}), nature(nature) {crundec = new CRunDec();}; | ||
| HNLDecay(double hnl_mass, double mixing, ChiralNature nature, std::set<siren::dataclasses::ParticleType> const & primary_types) : hnl_mass(hnl_mass), mixing(std::vector<double>{0,0,mixing}), nature(nature), primary_types(primary_types) {crundec = new CRunDec();}; |
There was a problem hiding this comment.
Manual ownership of CRunDec* without a visible destructor/copy-control risks leaks and double-frees (e.g., copies of HNLDecay will copy the raw pointer). Prefer storing CRunDec by value or using std::unique_ptr<CRunDec> and implementing/defining appropriate copy/move behavior (or deleting copy operations).
| else { | ||
| std::cout << "HNL decay signature not recongized! Exiting\n"; | ||
| exit(0); | ||
| } |
There was a problem hiding this comment.
Calling exit(0) inside library code makes failures non-recoverable for callers (including Python bindings) and bypasses normal error handling/cleanup. Replace exit(0) with a thrown exception (e.g., std::runtime_error) or the project’s error mechanism so the caller can handle invalid signatures.
| geom3::UnitVector3 pHNL_dir = pHNL_mom.direction(); | ||
| geom3::Rotation3 x_to_pHNL_rot = geom3::rotationBetween(x_dir, pHNL_dir); | ||
|
|
||
| double phi = random->Uniform(0, 2.0 * M_PI); |
There was a problem hiding this comment.
M_PI is not guaranteed to be defined by <cmath> across all platforms/toolchains unless specific macros are set. Since this project already has siren::utilities::Constants::pi, prefer using that constant (or std::numbers::pi if available) for portability.
| double phi = random->Uniform(0, 2.0 * M_PI); | |
| double phi = random->Uniform(0, 2.0 * siren::utilities::Constants::pi); |
6308e14 to
cb29e52
Compare
26c739b to
4aa1586
Compare
cb29e52 to
dffa635
Compare
4aa1586 to
770d5fc
Compare
dffa635 to
ed52cca
Compare
770d5fc to
88fa558
Compare
ed52cca to
539374b
Compare
88fa558 to
73a93fd
Compare
|
Closing to reopen from the commit author's account so they can be added as a reviewer. Branch unchanged; will be re-linked from the new PR shortly. |
Stack: PR 12 of 14
This PR is part of a 14-PR stack decomposing
dev/HNL_DISinto reviewable chunks.feat/hnl-decays-2bodyfeat/hnl-decays-hadronic-ewMerge order:
feat/hnl-decays-2bodyshould land before this PR.Squashed contents
Squashed diff for paths:
Source commits on dev/HNL_DIS_clean that contributed:
197bf14 (Nicholas Kamp): fixing build errors from the merge
1bbd678 (Nicholas Kamp): fix memory leak
2219939 (nickkamp1): first step at EW decay
285285f (Nicholas Kamp): kinematics fix to 2body decays
3625e81 (Nicholas Kamp): fix build errors, add 3 nu decay
39c5407 (nickkamp1): implement sampling for secondary decay
3d95c4e (nickkamp1): more EW decay logic, add prototype surface detector
5031575 (Nicholas Kamp): initial suite of changes to re-implement the dipole DIS from spline class in the SIREN parlance
5e2b658 (Nicholas Kamp): start MH sampling template
6f19de6 (nickkamp1): start with diff xs
72e02b4 (Nicholas Kamp): N4Bar decay fix
7c8a8f1 (Nicholas Kamp): continue implementing diff dec width, start w sampling
8283177 (Nicholas Kamp): start implementing 3 body sampling logic
88c13f0 (Nicholas Kamp): decay widths now match literature
949125f (Nicholas Kamp): overhaul the three body decay sampling logic.. some build errors remain
96f16cc (Nicholas Kamp): build fixes, total decay width fixes
b6eba95 (Nicholas Kamp): total decay width for EW decay
b895e21 (Nicholas Kamp): add lots of sampling code
c3b7765 (Nicholas Kamp): fix build warnings, add more dipole hnl splines
dc0281b (Nicholas Kamp): Total decay widths implemented, add ND280 detector
ea13b30 (nickkamp1): Rename some classes, flush out the two body decay class
f6f8b46 (Nicholas Kamp): get the decay sampling working
feddaca (Nicholas Kamp): three body decays now run!!
Notes
dev/HNL_DIS_clean..fitsdata files have been removed from the branch and are packaged separately.