From f6943c4e78d716f8ee17bf637112b9f02594137a Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 20:03:10 +0000 Subject: [PATCH 1/2] Annihilate positrons below the positron energy cutoff process_charged_secondary returned early for charged particles below their energy cutoff before reaching the positron annihilation step. With a nonzero positron cutoff, positrons from pair production below the cutoff therefore never produced their two 511 keV annihilation photons, losing that energy from the photon field (source positrons, handled in sample_positron_reaction, always annihilated). The cutoff now only suppresses thick-target bremsstrahlung, and positrons always annihilate. Behavior with the default cutoff of zero is unchanged. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_014RN7JroEkrbkLKVHbMDap9 --- src/physics.cpp | 8 +++-- tests/unit_tests/test_energy_cutoff.py | 43 ++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/physics.cpp b/src/physics.cpp index 71dae94b590..342d4144685 100644 --- a/src/physics.cpp +++ b/src/physics.cpp @@ -471,13 +471,17 @@ void process_charged_secondary( Particle& p, Direction u, double E, ParticleType type) { int idx = type.transport_index(); - if (idx == C_NONE || E < settings::energy_cutoff[idx]) + if (idx == C_NONE) return; - if (settings::electron_treatment == ElectronTreatment::TTB) { + // Below the energy cutoff, the kinetic energy is deposited locally without + // producing bremsstrahlung + if (E >= settings::energy_cutoff[idx] && + settings::electron_treatment == ElectronTreatment::TTB) { thick_target_bremsstrahlung(p, type, u, E); } + // A positron annihilates regardless of its kinetic energy if (type == ParticleType::positron()) { Direction photon_u = isotropic_direction(p.current_seed()); p.create_secondary( diff --git a/tests/unit_tests/test_energy_cutoff.py b/tests/unit_tests/test_energy_cutoff.py index 45333f2e1dc..ee342dc3bef 100644 --- a/tests/unit_tests/test_energy_cutoff.py +++ b/tests/unit_tests/test_energy_cutoff.py @@ -61,3 +61,46 @@ def test_energy_cutoff(run_in_tmpdir): # Despite killing particles below the cutoff, the total heating should be # equal to the source energy assert heating[0] == pytest.approx(source_energy) + + +def test_positron_cutoff_annihilation(run_in_tmpdir): + """Positrons below the energy cutoff must still annihilate""" + + def run(positron_cutoff): + model = openmc.Model() + m = openmc.Material() + m.add_nuclide('Pb208', 1.0) + m.set_density('g/cm3', 11.35) + sph = openmc.Sphere(r=100.0, boundary_type='reflective') + model.geometry = openmc.Geometry([openmc.Cell(fill=m, region=-sph)]) + + model.settings.run_mode = 'fixed source' + model.settings.source = openmc.IndependentSource( + particle='photon', + energy=openmc.stats.Discrete([10.0e6], [1.0]), + ) + model.settings.particles = 100 + model.settings.batches = 2 + # With local energy deposition, charged particles produce no + # bremsstrahlung, so the positron cutoff should have no effect + model.settings.electron_treatment = 'led' + if positron_cutoff is not None: + model.settings.cutoff = {'energy_positron': positron_cutoff} + + tally = openmc.Tally() + tally.filters = [ + openmc.EnergyFilter([0.0, 0.5e6, 0.52e6, 10.0e6]), + openmc.ParticleFilter(['photon']) + ] + tally.scores = ['flux'] + model.tallies = [tally] + model.run(apply_tally_results=True) + return tally.mean.ravel() + + # Every positron is below a cutoff above the source energy + flux_ref = run(None) + flux_cutoff = run(20.0e6) + + # Annihilation photons contribute to the bin containing 511 keV + assert flux_ref[1] > 0.0 + assert flux_cutoff == pytest.approx(flux_ref) From 9338b11273d130cc361f754e4ba11d52ce19b5ae Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 20:16:16 +0000 Subject: [PATCH 2/2] Document that positrons below the energy cutoff still annihilate Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_014RN7JroEkrbkLKVHbMDap9 --- docs/source/io_formats/settings.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/source/io_formats/settings.rst b/docs/source/io_formats/settings.rst index 01021e8d22d..dcb63d0fc10 100644 --- a/docs/source/io_formats/settings.rst +++ b/docs/source/io_formats/settings.rst @@ -201,7 +201,9 @@ time. *Default*: 0.0 :energy_positron: - The energy under which positrons will be killed. + The energy under which positrons will be killed. A killed positron + deposits its kinetic energy locally but still produces a pair of + annihilation photons. *Default*: 0.0