Repository navigation
Conversation
With a neutron time cutoff, create_fission_sites() rejects a delayed fission site whose emission time exceeds the cutoff, since the neutron would be killed at birth. The rejected site was still counted in n_bank and wgt_bank, because the loop counter that becomes n_bank is also incremented when a site is rejected, but it got no nu_bank entry and no n_delayed_bank count. Analog fission tallies then read past the end of nu_bank in score_fission_eout() and in the analog decay-rate scores, scored the rejected delayed neutrons as prompt neutrons, since the prompt fraction is taken from n_bank and n_delayed_bank, and left fission() true with an empty nu_bank when every site of a collision was rejected. The continuous-energy and the multigroup versions of create_fission_sites() both had the problem. A rejected delayed neutron is now recorded like any other fission neutron, in nu_bank and in the delayed group counts, but it is still not banked: it takes no progeny ID and enters neither the fission bank nor the secondary bank. n_bank and wgt_bank are taken from the number of neutrons recorded, so n_bank equals the size of nu_bank whenever fission() is set. Analog nu-fission, prompt-nu-fission, delayed-nu-fission and decay-rate scores then count every fission neutron produced, as the track-length and collision estimators do, whether or not a time cutoff is set. Nothing changes without a time cutoff, and the random number stream is the same. Add tests/unit_tests/test_time_cutoff_fission.py. In the first generation of a multigroup and of a continuous-energy eigenvalue run, every analog fission tally must be the same with and without a time cutoff, and a tally with an EnergyoutFilter must sum to the same tally without the filter. In a multigroup fixed-source problem in which every fission neutron is delayed, no prompt-nu-fission may be scored.
Extend tests/unit_tests/test_time_cutoff_fission.py with checks that do not depend on comparing two runs: - With a multigroup library in which every collision produces exactly one delayed fission neutron, emitted long after the time cutoff, the analog nu-fission and delayed-nu-fission estimates must equal the collision estimates collision by collision. No fission neutron may enter the simulation (ParticleProductionFilter, track-length flux), and collision tracking must show that the colliding particle keeps the delayed group of its source site and takes no progeny ID. - In a continuous-energy fixed-source problem, the flux must be positive and the analog nu-fission, prompt-nu-fission and delayed-nu-fission estimates must agree with the track-length estimates. - When the fission bank fills up in the first generation (multigroup, eight neutrons per collision; continuous energy, Cf-249), the analog tallies must count exactly the sites in the bank, plus, in the multigroup run with a time cutoff, the delayed neutrons it rejected. - With uniform fission site weighting, the analog tallies with an EnergyoutFilter must sum to the tallies without it, in multigroup and continuous-energy problems. Also set inactive = 0 explicitly in the all-delayed fixed-source test.
Wrap the six @pytest.mark.parametrize('event_based', ...) lines of
tests/unit_tests/test_time_cutoff_fission.py that exceed 79 characters.
In the multigroup full fission bank test with a time cutoff, also check
that the delayed neutrons sampled after the site that failed to enter
the bank are not counted: the analog delayed-nu-fission estimate must
stay well below the collision estimate, which counts all of them.
Once a site fails to enter the full fission bank, the loop over the fission sites of the collision ends, so the neutrons of the remaining sites are never sampled. Say so in the comment of the multigroup check.
ondrejch
force-pushed
the
pr/fix-time-cutoff-fission-sites
branch
from
October 7, 2026 20:56
4ee56dd to
da4eae0
Compare
5 tasks done
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
With a neutron time cutoff,
create_fission_sites()rejects a delayed fission site whose emission time is past the cutoff. This happens in the continuous-energy version (src/physics.cpp) and in the multigroup version (src/physics_mg.cpp). The loop counter that becomesn_bankis incremented by thecontinueas well. The rejected site is therefore counted inn_bankandwgt_bank, but it gets nonu_bankentry and non_delayed_bankcount. As described in #4179, the analog fission tallies then:nu_bank, inscore_fission_eout()and in the analogdecay-ratescore;fission()true with an emptynu_bankwhen every site of a collision was rejected.The rejection was added with the delayed neutron emission time (#2898, merged as part of #2992), so it was first released in v0.15.3. Before that, a delayed site had its parent's time and was never past the cutoff, so every fission neutron produced was banked and counted. The rejection itself has to stay. Since #3526,
Particle::event_advance()limits a flight to(t_cutoff - time) * speed. A neutron banked past the cutoff would therefore be moved a negative distance and score a negative track length before it is killed.The change
A rejected delayed neutron is now recorded like any other fission neutron, in
nu_bankand in the delayed-group counts. It is still not banked:ifp();p.delayed_group().The loop index no longer doubles as the counter.
n_bankandwgt_bankare taken fromnu_bank.size(), so they agree withnu_bankandn_delayed_bankwheneverfission()is set.The full-bank path is unchanged. The site that cannot be banked and all later ones are dropped, with the existing warning. Neutrons rejected before that point are recorded, so
n_bankis the same as on develop.The rejection consumes no random numbers and nothing is resampled, so the random number stream does not change.
tally_scoring.cpp, file formats, MPI types andSourceSiteare not touched. Inparticle_data.h, the comments onn_bank,wgt_bankandn_delayed_banknow say what they count. One sentence undertime_neutroninsettings.rst, and a matching one in theSettings.cutoffdocstring, states the rule.For a collision with a rejected site:
nu-fissionprompt-nu-fissiondelayed-nu-fission, with or withoutDelayedGroupFilterEnergyoutFilternu_bankdecay-ratenu_bankfission()gate, IFP scores, absorption event MTSeveral things are also unchanged:
sort_bank()check;ParticleProductionFiltercounts the secondaries that are banked. In fixed-source runs with a cutoff it therefore stays below analognu-fissionby the rejected neutrons, as on develop. The default cutoff is infinite, so without atime_neutroncutoff no site is rejected and nothing changes (see Testing).Counting the rejected neutrons, and the alternative
The alternative, (a), counts only the kept sites.
n_bank,wgt_bankandn_delayed_bankwould come from the banked sites, andfission()would be false when all sites were rejected. With the structure of this PR, (a) is the same patch without therecord_fission_neutron(site)call before thecontinue. I chose to count the rejected neutrons, for these reasons:nu-fission,prompt-nu-fission,delayed-nu-fissionanddecay-rateusenu(E) * fission, which includes delayed neutrons whatever the cutoff. With this PR the analog estimates estimate the same quantities (see Testing). Under (a), the analog delayed scores would be scaled by the probability that the neutron is emitted before the cutoff. The same score would then mean different things for different estimators, and OpenMC switches a tally to the analog estimator when it has anEnergyoutFilter.TimeFilterbin well before the cutoff would still depend on where the cutoff is, because its delayed neutrons are emitted later.delayed-nu-fissionkeeps the meaning it had.nu-fission,fission(), the IFP scores, and the absorption event MT (seen by collision tracks,ReactionFilterand the MT scores). Under (a), all of them would change for collisions whose sites were all rejected.create_delayed_neutrons = False), it does so inNuclide::nu(), and every estimator agrees. The time cutoff is a transport truncation, not a physics switch.The case for (a):
tally_scoring.cppdescribe the analog estimate as "the number of particles that were banked in the fission bank". That matches (a) literally. This PR leavestally_scoring.cppunchanged.ParticleProductionFilter, and with the full-bank path, which drops the sites it cannot bank without counting them.If reviewers prefer (a), the code change is only the two
record_fission_neutron(site)calls in the rejection branches. The tests change more: every case that counts the rejected neutrons. With that variant, 16 of the 18 cases fail; only the two continuous-energy full-bank cases, which run without a cutoff, still pass. For example, the eigenvalue test would have to require equalprompt-nu-fission, and smallerdelayed-nu-fissionanddecay-rate, with the cutoff.Related PRs and issues
n_sites_storedonly after a site is banked. That is (a) counting, and on its own it would make the continuous-energy counts consistent that way.physics_mg.cpp.record_fission_neutron(site)call before theircontinue), or they are not counted at all.settings::ifp_on()toifp_on()). This PR does not touch that line.Testing
The new
tests/unit_tests/test_time_cutoff_fission.pyhas three test configurations. Each runs in history and event mode, so there are six cases:test_time_cutoff_eigenvalue[mg|ce]runs the first generation of an eigenvalue problem with and without a cutoff of 1e-2 s.slab_mgwith a 2-group library with delayed fractions 0.1 and 0.2 (1000 particles); for CE, a bare U-235 sphere (10000 particles).k_generationand the flux.EnergyoutFilter, and with aDelayedGroupFilter(includingdecay-rate).EnergyoutFiltertally must sum to the unfiltered one. In MG,decay-ratemust equal lambda timesdelayed-nu-fission.test_time_cutoff_all_delayedis an MG fixed-source problem in which every fission neutron is delayed.prompt-nu-fissionmust be exactly 0, anddelayed-nu-fissionmust equalnu-fission, with and without anEnergyoutFilter.On develop all six cases fail:
prompt-nu-fission1.034 with the cutoff versus 0.734 without, anddelayed-nu-fission0 versus 0.3.score_fission_eout().With this PR all six pass, in 0.75 s at 2 threads.
A second commit adds six more configurations to the same module, again in both modes, so twelve more cases. They do not compare two runs:
test_time_cutoff_one_neutron_per_collisionuses an MG fixed-source library in which every collision produces exactly one delayed neutron, emitted long after the cutoff.nu-fissionanddelayed-nu-fissionmust equal the collision estimates, andprompt-nu-fissionmust be 0.ParticleProductionFilterscores nothing, and the track-length flux must be positive.test_time_cutoff_fixed_source_ceuses a CE fixed-source U-235 sphere. The track-length flux must be positive, and each analog estimate must be within 50 % of the track-length one.test_time_cutoff_full_fission_bank[mg|ce]fills the fission bank in the first generation: MG with eight neutrons per collision, CE with a reflected Cf-249 cube.nu-fissionmust equal the number of banked sites, three per source particle. In every run, prompt plus delayed must equalnu-fission.prompt-nu-fissionmust equal the banked sites, anddelayed-nu-fission, the rejected neutrons, must be positive.delayed-nu-fissionmust stay below 0.85 times the collision estimate, because the neutrons the collision would have produced after the site that failed to enter the bank are neither sampled nor counted.test_time_cutoff_ufs[mg|ce]uses uniform fission site weighting. TheEnergyoutFiltertallies must sum to the unfiltered ones. In MG,decay-ratemust also equal lambda timesdelayed-nu-fission.With all three commits, all 18 cases pass with this PR, in 2.0 s at 2 threads. They also pass with
-D_GLIBCXX_ASSERTIONSand with 2 MPI ranks. On develop, 16 fail. The two that pass there are the CE full-bank cases, which run without a cutoff.Other checks were run on Ubuntu 24.04.5 with GCC 13.3.0, built RelWithDebInfo with
OPENMC_ENABLE_STRICT_FP=ON, with 2 OpenMP threads unless noted and the NNDC data:Assertions. Built with
-D_GLIBCXX_ASSERTIONS, develop aborts atnu_bank(i=0)inscore_fission_eout()in all six cases. With this PR all six pass, with no assertion.Invariants. A temporary instrumentation patch (not included) checked these after every
create_fission_sites()call:n_bank == nu_bank.size();n_delayed_bankof each group equals the number ofnu_bankentries of that group;wgt_bankequals the sum of thenu_bankweights;fission() == (n_bank > 0);With this PR there was no violation, in history and event mode, in the test models and in the fixed-source models below (seed 1). There was none either in two extra models with several neutrons per collision; in one of them the fission bank fills up and more than 11000 calls find it full. On develop,
n_bank == nu_bank.size()fails in every call with a rejected site: 300 of the 1034 calls that produced fission neutrons in the MG eigenvalue case with the cutoff, and 55 of 9027 in CE. In every run whose fission bank does not fill, the numbers of calls and of sampled and rejected sites are identical with and without this PR. With a full bank at 2 threads, they vary from run to run on either build.Analog versus track-length, in fixed source with the cutoff:
slab_mgwith the library with delayed fractions 0.1 and 0.2) and CE (a bare U-235 sphere, r = 8 cm, with a 2 MeV point source at t = 0). Each ran 10 seeds of 10 batches x 1000 particles.nu-fission,prompt-nu-fissionanddelayed-nu-fission. Then a t-test of mean(r) = 0, with Šidák control of the six tests at a family level of 0.01 (|t| <= 4.42).delayed-nu-fissionis 99.97 % (MG) and 99.68 % (CE) below the track-length estimate, andprompt-nu-fissionis 42.8 % (MG) and 0.61 % (CE) above it.nu-fissionagrees with the track-length estimate on both builds.Without a time cutoff:
pytest --updateovertests/regression_testsregenerates everyresults_true.datandinputs_true.datbyte for byte the same on develop and with this PR.surface_source_write/case-a01, which forces two threads and whose bank holds the same sites in a different order.tests/unit_tests, both builds give the same passes and failures, apart from the new module. The failures on both come from the test environment: no installed OpenMC forcpp_driverand the dlopen tests, noopenpyxl, and noOPENMC_ENDF_DATA.Event mode. The ten
mg_*regression tests pass with--event.MPI (OpenMPI 4.1.6, 2 ranks of 1 thread). The checks of the MG eigenvalue test hold in history and event mode.
mg_basic,mg_basic_delayed,mg_talliesandmg_fixed_source_ww_fission_shared_secondarypass with--mpi --mpi-np 2.Other suites.
ctestpasses for the serial and the MPI builds.test_time_filter.py,test_filter_delayedgroup.py,test_settings.py,test_mg_inverse_velocity.pyandtest_mesh.pypass. clang-format 18.1.8 reports no changes.Fixes #4179
AI Assistance
Code, tests, analysis and this text:
Review of the change:
Checklist