Conversation
GuySten
force-pushed
the
claude/sharp-tesla-00fexy
branch
from
September 14, 2026 13:28
3c20c4f to
895c455
Compare
GuySten
changed the base branch from
develop
to
claude/assert-xs-particle-type
September 14, 2026 13:28
GuySten
force-pushed
the
claude/sharp-tesla-00fexy
branch
from
September 14, 2026 14:00
895c455 to
0167f97
Compare
GuySten
force-pushed
the
claude/sharp-tesla-00fexy
branch
from
September 14, 2026 14:16
0167f97 to
38e98f2
Compare
GuySten
changed the base branch from
claude/assert-xs-particle-type
to
claude/report-data-errors
September 14, 2026 14:16
GuySten
force-pushed
the
claude/report-data-errors
branch
from
September 14, 2026 14:21
93cb39e to
e873494
Compare
GuySten
force-pushed
the
claude/sharp-tesla-00fexy
branch
from
September 14, 2026 14:21
38e98f2 to
0f1b967
Compare
GuySten
force-pushed
the
claude/report-data-errors
branch
from
September 14, 2026 14:26
e873494 to
3a09103
Compare
GuySten
force-pushed
the
claude/sharp-tesla-00fexy
branch
from
September 14, 2026 14:27
0f1b967 to
4e72bdb
Compare
GuySten
force-pushed
the
claude/report-data-errors
branch
from
September 14, 2026 14:28
3a09103 to
49a86af
Compare
GuySten
force-pushed
the
claude/sharp-tesla-00fexy
branch
from
September 14, 2026 14:28
4e72bdb to
6364fdb
Compare
GuySten
force-pushed
the
claude/report-data-errors
branch
from
September 14, 2026 14:32
49a86af to
9471cd4
Compare
GuySten
force-pushed
the
claude/sharp-tesla-00fexy
branch
from
September 14, 2026 14:32
6364fdb to
f12ae89
Compare
GuySten
changed the base branch from
claude/report-data-errors
to
claude/assert-xs-particle-type
September 14, 2026 14:33
GuySten
force-pushed
the
claude/assert-xs-particle-type
branch
from
September 14, 2026 14:39
cfff90e to
84896c1
Compare
GuySten
force-pushed
the
claude/sharp-tesla-00fexy
branch
from
September 14, 2026 14:39
f12ae89 to
396b346
Compare
5 tasks
GuySten
force-pushed
the
claude/assert-xs-particle-type
branch
from
September 14, 2026 16:33
84896c1 to
36872ed
Compare
GuySten
force-pushed
the
claude/sharp-tesla-00fexy
branch
from
September 14, 2026 16:33
396b346 to
7e7250f
Compare
GuySten
force-pushed
the
claude/assert-xs-particle-type
branch
from
September 14, 2026 16:47
36872ed to
c507e5f
Compare
GuySten
force-pushed
the
claude/sharp-tesla-00fexy
branch
from
September 14, 2026 16:47
7e7250f to
7a43a73
Compare
GuySten
force-pushed
the
claude/assert-xs-particle-type
branch
from
September 14, 2026 16:51
c507e5f to
bdc0fb4
Compare
GuySten
force-pushed
the
claude/sharp-tesla-00fexy
branch
from
September 14, 2026 16:51
7a43a73 to
f854342
Compare
GuySten
force-pushed
the
claude/assert-xs-particle-type
branch
from
September 14, 2026 16:56
bdc0fb4 to
4b24cf1
Compare
GuySten
force-pushed
the
claude/sharp-tesla-00fexy
branch
from
September 14, 2026 16:56
f854342 to
a34d085
Compare
GuySten
force-pushed
the
claude/assert-xs-particle-type
branch
from
September 14, 2026 17:01
4b24cf1 to
047ad5c
Compare
GuySten
force-pushed
the
claude/sharp-tesla-00fexy
branch
from
September 14, 2026 17:02
a34d085 to
f77afaf
Compare
GuySten
force-pushed
the
claude/assert-xs-particle-type
branch
from
September 14, 2026 17:08
047ad5c to
e83a7ec
Compare
GuySten
force-pushed
the
claude/sharp-tesla-00fexy
branch
from
September 14, 2026 17:08
f77afaf to
9e0e18c
Compare
Photon and electron transport are driven entirely by per-element data, but OpenMC required a type="neutron" entry in cross_sections.xml for every nuclide in every material and then read the corresponding HDF5 file, whether or not neutrons were ever transported. A photon-only calculation therefore needed a neutron data library it never used, and Element.expand() could not expand an element against a library holding only photon data. Add a neutron_transport setting (default true) that turns this off: * Material::init_nuclide_index no longer requires a neutron library entry, and openmc_load_nuclide creates the nuclide without opening an HDF5 file. * A new Nuclide constructor builds a nuclide from its name alone, taking Z, A and the metastable state from the name and the atomic weight ratio from the tabulated AME2020 atomic masses rather than from the data library. Nuclide names that cannot be resolved this way, such as elemental data like C0, are reported as errors. * The neutron energy grid setup, the neutron temperature bounds and the temperature interpolation check are skipped, since no neutron data exists to define them. * Element.expand() ignores non-neutron library entries and falls back to natural abundances when a library has no neutron data at all, so Material.add_element works against a photon-only library. Mirroring the way a photon source turns photon transport on, a source that emits neutrons turns neutron transport back on, so a model is never left transporting particles for which no data was read. Combinations that would otherwise silently do nothing are rejected: eigenvalue mode, multigroup mode, thermal scattering data, resonance scattering, decay photon sources, and turning neutron transport off without photon transport on. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JxR1sQWWnALXj39rX12v7c
Test the feature against a cross section library built by stripping everything but the photon data out of the configured one, so a run that touched neutron data in any way could not succeed. The tests cover the photon-only run itself (including that densities are normalized from the tabulated atomic masses), that the same library is still rejected without opting in, that a neutron source turns neutron transport back on, and that thermal scattering data and eigenvalue mode are rejected. Also complete the source-driven enabling: the default source used when a model specifies none is built through the programmatic IndependentSource constructor rather than the XML one, so it was not turning neutron transport back on and would have left neutrons being sampled with no data to transport them with. Moving the call into that constructor covers every IndependentSource, which in turn makes the "no particles would be transported" check unreachable -- a source always enables transport of its own particle type -- so drop it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JxR1sQWWnALXj39rX12v7c
The default source created when a model specifies none is built through the programmatic IndependentSource constructor, which takes no particle type, so enabling transport there was a hard-coded neutron_transport = true expressed as a call on a value that is always the default. Set it where the default source is created instead, so the reason is visible at the point it applies and the constructor carries no global side effect. Behavior is unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JxR1sQWWnALXj39rX12v7c
…idden A fixed source calculation with neutron transport turned off and no source specified fell back to the default neutron source, turned neutron transport back on, and then failed with "Could not find nuclide ... in the nuclear data library" -- which points at the data rather than at the real mistake. Report the missing source instead. Other run modes keep the existing fallback, since they do not sample the external source. Sources turning transport back on is deliberate, but doing so silently leaves no sign that the setting was overridden, so warn when a source has turned neutron transport back on after the user asked for it to be off. Plotting is excluded because it reads no data, so the setting has no bearing on it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JxR1sQWWnALXj39rX12v7c
Only eigenvalue and fixed source calculations sample the external source, so the default source that stands in when a model specifies none has no bearing on the others. Turning neutron transport back on for all of them meant a stochastic volume calculation still demanded neutron data it never used; it samples no source and transports nothing, needing only the names and densities of the nuclides present, so let it run with no data at all. Plotting reads no data either way. A particle restart does transport, but takes its particle from the restart file rather than from a source, and its type is only known once that file has been read -- long after the decision of which data to load. A restarted neutron therefore reached transport with no energy grid to look up and segfaulted; report it instead. The random ray solver needs no separate handling: it requires multi-group mode, which already rejects turning neutron transport off. A test covers that path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JxR1sQWWnALXj39rX12v7c
Testing the restart guard meant writing a restart file by hand, since OpenMC only produces them for particles that get lost. A fabricated data file is not worth having in the suite. The guard itself stays. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JxR1sQWWnALXj39rX12v7c
Restarting was the one behavior left without a test after the hand-written restart file was dropped. Losing a particle is what makes OpenMC write these files, so let it: the material does not reach the outer boundary, leaving a gap that every photon leaving it is lost in. Restarting from the resulting file exercises the restart path against a run that read no neutron data. How many files get written depends on the thread count, since the write limit is checked before the lost particle count is incremented, so the test takes any one of them rather than asserting on the number. Verified with 1, 2 and 4 threads. This covers a photon restart, which needs no neutron data. The guard that reports a restarted neutron when neutron transport is off remains covered only by manual testing, since producing a neutron restart file would require the neutron data the test is built to do without. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JxR1sQWWnALXj39rX12v7c
"Decay photon sources are produced by neutron activation" was misleading: R2S also drives photon transport from a decay source, and its photon step is one of the clearest uses of running without neutron data. What cannot work is D1S, where a single coupled neutron-photon calculation replaces the photons produced in neutron reactions with decay photons, so with no neutron reactions it would produce no photons at all. Name that method in the error and point R2S users elsewhere, and document the distinction. Also add the missing underscore to the decay sources label, which made it a comment rather than a target. Nothing referenced it until now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JxR1sQWWnALXj39rX12v7c
An independent review turned up two segfaults and an uninitialized member that this branch had made reachable. A nuclide-filtered tally with multiply_density off takes a branch that fills the neutron micro cross section cache whenever the tallied nuclide is absent from the scoring region, with no check on the particle type. For a photon that was already wasted work computing a neutron cross section at a photon energy; with no neutron data it dereferences an empty energy grid. A cell filter over a void region is enough to reach it. Guard both estimators on the particle type. Sources OpenMC cannot inspect up front do not go through enable_transport, so a compiled source emitting neutrons reached transport with no data. Check the particle type in Particle::from_source. Every thread arrives at once, so the error is serialized; racing through std::exit turned a reported error into a segfault. Nuclide::reaction_index_ is only filled by create_derived(), which neither the data-free constructor nor the volume calculation early return reaches, leaving 902 garbage entries that pass the >= 0 test callers use before indexing an empty reactions_. openmc.lib.nuclides[...].collapse_rate() aborted on it. Fill it in both constructors, which also fixes the pre-existing volume mode case. Also: validate the nuclide name before registering anything globally and report failures through the C API rather than calling std::exit, so a bad nuclide no longer kills the host interpreter; reject multipole data, which became reachable once photon transport stopped being required; move the neutron_transport reset next to photon_transport's; and say in the docs that elemental evaluations such as C0 cannot be used, which materials carried over from a neutron calculation may well contain. Tests: assert atomic weight ratios against literals from a neutron data library rather than re-deriving them from the table under test, cover reaction_index_ and rejected names, and add a regression test for the tally crash. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JxR1sQWWnALXj39rX12v7c
Turning neutron transport back on when a source emits neutrons mirrored the way a photon source turns photon transport on, but the two are not the same operation. Photon transport is off by default, so a photon source settles something the user never specified; neutron transport is only ever off because it was asked for, so doing the same there overrides an explicit choice. It also could not be applied uniformly -- a compiled source cannot be inspected before the data decision is made -- which left three different outcomes depending on where the neutrons came from. Reject such a source instead, so every path reports the same thing, and at the source rather than as missing data further along. The override warning goes with it. Elemental evaluations such as C0, which several libraries provide in place of the individual isotopes, are now supported rather than rejected: their mass is the natural-abundance-weighted atomic weight, which is what such an evaluation reports as its atomic weight ratio. The table is computed from the AME2020 masses and natural abundances already in the repository. A material carried over from a calculation that used a neutron library may well hold one of these, so rejecting them would have blocked the R2S photon step this setting exists for. Also reject an NCrystal configuration, which configures neutron scattering in the same way S(a,b) data does. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JxR1sQWWnALXj39rX12v7c
A second independent review found that ParticleType parses all-digit names with std::stoi, which throws out_of_range rather than invalid_argument on overflow. That is a logic_error either way, but only invalid_argument was caught, so a material naming something like 99999999999 terminated on an uncaught exception with no message, and killed the host interpreter through the C API. Catch the common base. The same parser also accepts aliases such as "alpha", "h1" and "proton", and bare PDG numbers, none of which a data library would name a nuclide. Accepting them here meant a model would run only while neutron data was being skipped, and would write names into summary.h5 and the volume files that the Python API cannot parse back. Require the canonical spelling, reporting what it should be. Also: record neutron_transport in the statepoint, so post-processing can tell whether the atomic weight ratios came from a library or from AME2020; correct the claim that an elemental evaluation's atomic weight ratio is the abundance- weighted mass, which can differ by a part in a thousand; and note that a volume calculation still reads photon data if the model carries a photon source. openmc.data.atomic_mass now computes elemental masses for every element with naturally occurring isotopes rather than a fixed list of six, so the Python API can read back materials the C++ side accepts. Values for those six are unchanged; the C++ table agrees with Python across all 84 elements to 4e-9. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JxR1sQWWnALXj39rX12v7c
GuySten
force-pushed
the
claude/assert-xs-particle-type
branch
from
September 14, 2026 17:57
e83a7ec to
e94fcbd
Compare
GuySten
force-pushed
the
claude/sharp-tesla-00fexy
branch
2 times, most recently
from
September 15, 2026 11:04
5208518 to
051d7f3
Compare
Element.expand() decides which isotopes exist by reading cross_sections.xml, and refuses outright when the library lists none for the element. The fallback added earlier on this branch only covered a library holding no neutron data at all, which is the photon-only case but not the one users actually hit. ENDF/B-VII.1 carries photoatomic data for every element through Z=100, but its neutron sublibrary has no evaluation for neon, ytterbium, osmium or platinum. None of those four can appear in a photon calculation at all -- not as a bulk material, and not as the platinum encapsulation or marker band of a brachytherapy source, which is where a photon model is most likely to meet it. It fails at Material.add_element, before OpenMC starts: ValueError: Unable to expand element Pt because the cross section library provided does not contain any of the natural isotopes for that element. Decide per element as well as per library. A library with no neutron data at all still says nothing about any element, as before; now neither does one whose neutron data skips this element while its photon data covers it. Either way natural abundance is the whole of what the library knows, so expand by it. An element with neither kind of data is still an error, unchanged, as are the partial cases the surrounding code already handles -- an elemental evaluation such as C0 standing in for C12 and C13, and O18 folded into O16. A neutron calculation naming one of these four now fails when the data is actually wanted, reporting the missing nuclide, rather than at expansion. That is the same error one step later, and it is the price of the model being usable at all when neutrons are not transported. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JxR1sQWWnALXj39rX12v7c
GuySten
force-pushed
the
claude/sharp-tesla-00fexy
branch
from
September 15, 2026 11:16
f17f294 to
6bad8f0
Compare
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
Photon and electron transport are driven entirely by per-element data, so a calculation in which no neutrons appear does not need neutron cross sections. OpenMC nevertheless requires a
type="neutron"entry incross_sections.xmlfor every nuclide in every material and then reads the corresponding HDF5 file, whether or not a neutron is ever transported.That is not only wasteful. ENDF/B-VII.1 carries photoatomic data for every element through Z=100, but its neutron sublibrary has no evaluation for neon, ytterbium, osmium or platinum — so none of those four can appear in a photon calculation at all, even though every cross section such a calculation needs is sitting in the library. It fails in Python, at
Material.add_element, before OpenMC starts:Platinum is the one most likely to be missed: it is the encapsulation and the radiopaque marker of brachytherapy sources, and Yb-169 is itself a brachytherapy source isotope.
The photon transport step of an R2S calculation is the other motivating case: its source comes from the decay of activated materials, so it transports no neutrons of its own.
With that,
cross_sections.xmlonly has to list the photon data for the elements in the model.How nuclide properties are obtained without a library
A new
Nuclideconstructor builds a nuclide from its name alone. Z, A and the metastable state are parsed from the name; the atomic weight ratio comes from the AME2020 masses already in the tree (added in openmc-dev#4129) rather than from a data file.Elemental evaluations such as
C0, which some libraries provide in place of the individual isotopes, are supported through a newATOMIC_WEIGHTtable of natural-abundance-weighted atomic masses. ENDF/B-VII.1 is one such library — it carriesC0rather thanC12andC13— so a material carried over from a calculation that used it may well contain one.openmc.data.atomic_masspreviously knew elemental masses for only six hardcoded elements; it now computes them for every element with naturally occurring isotopes, so the Python API can read back what the C++ side accepts.Only canonical GNDS spellings are accepted.
ParticleTypealso parses aliases likealpha,h1and bare PDG numbers, and accepting those would make a model run only while neutron data was being skipped.Densities may differ in the last few digits from a run that reads neutron data, since AME2020 and a library's tabulated atomic weight ratios are not bit-identical. For elemental evaluations the difference can reach a part in a thousand. The statepoint records
neutron_transportso post-processing can tell which of the two a run's masses came from.Expanding an element with no neutron data
Element.expand()decides which isotopes exist by readingcross_sections.xml, and refused outright when the library listed none for the element — the error above. But photon data is tabulated per element, so a library with no neutron data for an element is simply silent about its isotopes, and natural abundance is the whole of what it knows.It now falls back to natural abundance in exactly two cases: a library with no neutron data at all, as a photon-only calculation uses, and one whose photon data covers an element its neutron data skips, which is the ENDF/B-VII.1 case above. An element with neither kind of data is still an error, as are the partial cases the surrounding code already handled —
C0standing in forC12andC13,O18folded intoO16.A neutron calculation naming one of those four elements now fails when the data is actually wanted, reporting the missing nuclide, rather than at expansion — the same error one step later, and the price of the model being usable at all when neutrons are not transported.
Which particles get transported
A photon source turns photon transport on, because that setting is off by default and a source saying it emits photons settles something never specified.
Neutron transport has no counterpart. It is only ever off because it was asked for, so turning it back on would override an explicit choice, silently change which particles a calculation transports, and surface later as missing data rather than as the source that caused it. It also cannot be applied uniformly — a compiled source cannot be inspected before the data decision is made. So a source that emits neutrons is an error instead:
A model specifying no source at all falls back to OpenMC's default source, which emits neutrons, and is reported the same way rather than as missing nuclide data further along.
Sources whose particles are not knowable up front are checked when the particle appears: a compiled source emitting a neutron, and a particle restart file holding one, both report it rather than transporting with no data.
Run modes
Only eigenvalue and fixed source calculations sample the external source, so sources are not checked elsewhere. A stochastic volume calculation samples no source and transports nothing, so it reports nuclide inventories without reading any neutron data — and with no data at all if the model carries no photon source. Plotting reads none either.
What is skipped for want of neutron data
Cell::set_temperature's interpolation bounds check, which has no temperature-dependent data to bound against.Rejected combinations
Each of these would otherwise do nothing silently:
<sab><cfg>use_decay_photons(D1S)Fixes found in review
Two independent reviews of this branch found problems it had made reachable:
multiply_densityoff called the neutron cross section routine for whatever particle was being scored — a segfault with no neutron data. Fixed in Keep the micro cross section caches addressed correctly #64, where it belongs; the regression test stays here, since this branch is what makes the crash reachable.std::exit.Nuclide::reaction_index_is only filled bycreate_derived(), which neither the new constructor nor the volume-mode early return reaches, leaving 902 garbage entries that pass the>= 0test callers use before indexing an emptyreactions_. Filled in both constructors, which also fixes the pre-existing volume mode case.std::exitrather than an error code, killing the host interpreter.openmc_load_nuclidenow reports it throughset_errmsgandOPENMC_E_DATA. (The same pattern inTally::set_nuclidesandread_ce_cross_sectionsis independent of this branch and is fixed separately in Report unresolvable nuclide names instead of terminating, and fix the tally docs #66.)Testing
tests/unit_tests/test_neutron_transport_off.pyruns against a library built by stripping everything but the photon entries out of the configured one, so a run that touched neutron data in any way could not succeed. It covers the photon-only run itself (including that densities are normalized from the tabulated masses), that the same library is still rejected without opting in, rejection of neutron sources and of each combination above, elemental evaluations, volume mode, the particle restart path, and the tally case that used to crash.tests/unit_tests/test_element.pycovers expansion against a syntheticcross_sections.xml: a photon-only library expands by natural abundance, so does a library whose neutron data skips the element while its photon data covers it (parametrized over Pt, Os, Yb and Ne), an element with neither kind of data is still refused, and non-neutron entries never count as isotope availability. Those build their own XML rather than needing a data library.tests/cpp_unit_tests/test_nuclide.cppcovers the data-free constructor: identity and mass from the name, metastables,H1taking the atomic rather than the bare proton mass, elemental evaluations,reaction_index_, and rejected names. Atomic weight ratios are asserted against values from a neutron data library rather than re-derived from the table under test.Verified against the real ENDF/B-VII.1 HDF5 library: the four gap elements were confirmed by enumerating its 443 neutron and 100 photoatomic files against
NATURAL_ABUNDANCE,Element('Pt').expand()reproduces the error above without this change, and a platinum photon model runs to completion with it, readingPtphotoatomic data and no neutron data at all.Not covered: comparing a physics result with and without neutron data. That needs both runs to be possible for the same model, which they are not for these four elements.
Checklist
🤖 Generated with Claude Code
https://claude.ai/code/session_01JxR1sQWWnALXj39rX12v7c