I’m simulating 14.1 MeV neutrons incident on deuterium using Geant4 11.4.2 (patch 2) with G4NDL 4.7.1, tested with both QGSP_BIC_HP and QBBC. For the neutron inelastic interaction corresponding to d(n,2n)p, I observe two neutron secondaries via neutronInelastic, but no proton secondary is ever produced.
I’ve verified the interaction rate itself is correct (matches the tabulated G4NDL cross section, 166 mb at 14.1 MeV), and that production cuts aren’t suppressing the proton. The charge/baryon-number accounting is consistent with a real breakup occurring but only two of the three products being tracked — the observed 2-neutron final state is short by exactly what a proton would carry.
Is the proton being folded into some untracked residual rather than generated as an explicit secondary? Is there a way to configure ParticleHP to produce and transport it explicitly, while preserving the evaluated final-state distributions and event-by-event energy-momentum conservation?
How have you verified that? If that’s true, then the proton should absolutely show up in tracking. If this is a “universal” problem, then you should be able to see it in a single event. If you use /tracking/verbose 2, then you should get the three produced secondaries printed out at the interaction, and then you should see them being tracked.
This same pattern repeats every time neutronInelastic fires — always exactly 2 neutrons, 0 protons. This is distinct from hadElastic, which correctly produces a recoil deuteron (charge/baryon-conserving, as expected for elastic scattering):
The only proton tracks that appear anywhere in a 150,000-event run all trace back to this recoil deuteron subsequently undergoing D+D fusion (dInelastic, separate process, separate parent track) — never from neutronInelastic directly.
This is a complete particle breakup so I wonder if G4BinaryLightIonReaction will ultimately be the better path.
But a quick sanity check might to just have a counter in a custom PreUserTrackingAction that just increments a counter if it ever sees a proton (analogously, looping through secondaries in PostUserTrackingAction to find if protons are ever created). I believe that should remove the production cuts/range cuts as a variable.
Thanks for the suggestion. To clarify a couple of things I should have made clearer in my last post: I’ve already printed every secondary produced in my SteppingAction, using G4Step::GetSecondaryInCurrentStep() on every step (not just at the interaction vertex), so this already inspects secondaries at creation independent of the tracking manager’s own bookkeeping — across dozens of confirmed neutronInelastic events, this consistently shows exactly 2 neutrons and never a proton.
I also switched the primary particle to a proton, keeping the same deuterium target and physics list, and confirmed the reaction now produces genuine 3-body output — d(p,2p)n fires with all three particles tracked (2 protons + 1 neutron), unlike the neutron-incident case.
Digging into the actual G4NDL data files clarified why: Inelastic/CrossSection/1_2_Hydrogen.z gives the total cross section correctly, but the corresponding final-state file, Inelastic/F04/1_2_Hydrogen.z, only declares a single outgoing product (2 neutrons, nProducts=1), with no proton final-state data included at all — compared to the proton-incident case (G4TENDL1.4/Proton/Inelastic/F10/1_2_Hydrogen.z), which explicitly declares nProducts=2 and includes both particles.