Allow tracklength estimator for neutron heating (#3915)

Co-authored-by: Jon Shimwell <jon@proximafusion.com>
Co-authored-by: Paul Romano <paul.k.romano@gmail.com>
This commit is contained in:
Jonathan Shimwell 2026-05-30 08:10:05 +02:00 committed by GitHub
parent 2dd5322fee
commit 219d82726f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 62 additions and 2 deletions

View file

@ -644,8 +644,24 @@ void Tally::set_scores(const vector<std::string>& scores)
break;
case HEATING:
if (settings::photon_transport)
if (settings::photon_transport) {
// Photon heating requires a collision estimator (analog energy
// balance). However, if the tally only scores neutrons, we can keep the
// tracklength estimator since neutron heating uses kerma coefficients
// that support tracklength scoring.
bool neutron_only = false;
for (auto i_filt : filters_) {
auto pf =
dynamic_cast<ParticleFilter*>(model::tally_filters[i_filt].get());
if (pf && pf->particles().size() == 1 &&
pf->particles()[0].is_neutron()) {
neutron_only = true;
break;
}
}
if (!neutron_only)
estimator_ = TallyEstimator::COLLISION;
}
break;
case SCORE_PULSE_HEIGHT: {

View file

@ -17,3 +17,47 @@ def test_tally_init_args():
assert tally.filters == [filter]
assert tally.nuclides == ['U235']
assert tally.estimator == 'tracklength'
def test_heating_estimator_with_photon_transport(run_in_tmpdir):
"""Test that a neutron-only heating tally uses the tracklength estimator
even when photon transport is enabled.
Without a neutron particle filter, photon heating requires the collision
estimator (analog energy balance). But neutron heating has kerma
coefficients that support tracklength scoring, so a neutron-only tally
should keep the tracklength estimator.
"""
mat = openmc.Material()
mat.add_nuclide('Si28', 1.0)
mat.set_density('g/cm3', 2.3)
sph = openmc.Sphere(r=10.0, boundary_type='vacuum')
cell = openmc.Cell(fill=mat, region=-sph)
model = openmc.Model()
model.geometry = openmc.Geometry([cell])
model.settings.run_mode = 'fixed source'
model.settings.particles = 100
model.settings.batches = 1
model.settings.photon_transport = True
model.settings.source = openmc.IndependentSource(
space=openmc.stats.Point()
)
neutron_filter = openmc.ParticleFilter(['neutron'])
# Neutron-only heating — should get tracklength estimator
t_neutron = openmc.Tally(name='heating_neutron')
t_neutron.filters = [neutron_filter]
t_neutron.scores = ['heating']
# No particle filter — should get collision estimator
t_all = openmc.Tally(name='heating_all')
t_all.scores = ['heating']
model.tallies = [t_neutron, t_all]
sp_filename = model.run()
with openmc.StatePoint(sp_filename) as sp:
assert sp.tallies[t_neutron.id].estimator == 'tracklength'
assert sp.tallies[t_all.id].estimator == 'collision'