Skip to content

endf adoption #97

@shimwell

Description

@shimwell

Remaining Opportunities to Use endf Package in OpenMC

The endf package (v0.1.12) provides MF-level section parsers that return
structured dicts, replacing the need for manual record-by-record reading.
Each PR below replaces inline get_*_record calls and surrounding parsing
logic with a single parse_mf*() call, then constructs OpenMC objects from
the resulting dict.


PR 1: Replace Evaluation class with endf.Material

Files: openmc/data/endf.py + 10 caller files (no caller changes needed)

Subclass endf.Material with a thin Evaluation adapter providing
backward-compatible section, target, info, projectile, material,
reaction_list, and gnds_name properties. The ~160-line Evaluation class
becomes a ~30-line adapter. All isinstance and check_type checks continue
to work.

Lines saved ~130
Effort Small (1-2 days)
Difficulty Low
Risk Low -- adapter preserves all caller semantics, no caller changes

PR 2: Replace get_evaluations() with endf.get_materials()

Files: openmc/data/endf.py, openmc/data/neutron.py

get_evaluations() is already imported from endf.get_materials. The one
caller in neutron.py (NJOY heating correction, line ~817) accesses
.section[3, mt] on the returned objects. After PR 1 lands the adapter
handles this; alternatively update the caller to .section_text[3, mt] and
delete the wrapper entirely.

Lines saved ~22
Effort Trivial (< 1 day)
Difficulty Low
Risk Low -- single caller, straightforward

PR 3: Use parse_mf1_mt458 in FissionEnergyRelease.from_endf()

Files: openmc/data/fission_energy.py

The 138-line from_endf() manually reads CONT and LIST records for fission
energy components. endf.parse_mf1_mt458() returns a dict with all the
polynomial coefficients and tabulated data pre-parsed.

Lines saved ~55-85
Effort Small (1-2 days)
Difficulty Low-medium
Risk Low -- small focused module, good pilot PR for MF parser adoption

PR 4: Use parse_mf4 in AngleDistribution.from_endf()

Files: openmc/data/angle_distribution.py

The 97-line from_endf() handles four LTT cases (isotropic, Legendre,
tabulated, mixed) by reading HEAD/CONT/TAB2/LIST/TAB1 records.
endf.parse_mf4() returns a structured dict covering all cases.

Lines saved ~40-60
Effort Small-medium (2-3 days)
Difficulty Medium
Risk Medium -- four code paths to validate

PR 5: Use parse_mf5 in energy distribution classes

Files: openmc/data/energy_distribution.py

Seven from_endf() methods (ArbitraryTabulated, GeneralEvaporation,
MaxwellEnergy, Evaporation, WattEnergy, MadlandNix, plus the base dispatcher)
total 187 lines of record-level parsing. endf.parse_mf5() returns parsed
data for all six LF law types.

Lines saved ~70-105
Effort Medium (2-3 days)
Difficulty Medium
Risk Medium -- six law types to validate

PR 6: Use parse_mf6 in angle-energy distribution classes

Files: openmc/data/correlated.py (47 lines), openmc/data/kalbach_mann.py
(95 lines), openmc/data/laboratory.py (37 lines), openmc/data/nbody.py
(20 lines)

Four files with from_endf() methods parsing MF=6 LAW=1,2,3,6 respectively.
endf.parse_mf6() returns structured data for all LAW types. 199 lines of
parsing combined.

Lines saved ~80-120
Effort Medium (3-4 days)
Difficulty Medium
Risk Medium -- touches 4 files but all parse the same MF

PR 7: Use parse_mf7_mt2/parse_mf7_mt4 in ThermalScattering.from_endf()

Files: openmc/data/thermal.py

The ENDF parsing section of from_endf() manually reads MF=7 MT=2,4 data
(coherent/incoherent elastic and inelastic thermal scattering). The endf
package provides parse_mf7_mt2(), parse_mf7_mt4(), and
parse_mf7_mt451(). ~163 lines of parsing.

Lines saved ~65-100
Effort Medium (2-3 days)
Difficulty Medium
Risk Medium -- thermal scattering data is critical for reactor physics

PR 8: Use parse_mf8/parse_mf8_mt457 in decay.py

Files: openmc/data/decay.py

Decay.from_endf() and FissionProductYields.from_endf() manually parse
MF=8 MT=454/457/459 using HEAD/CONT/LIST records. endf.parse_mf8(),
endf.parse_mf8_mt454(), and endf.parse_mf8_mt457() provide pre-parsed
equivalents. ~35 lines of direct parsing plus surrounding processing.

Lines saved ~13-20
Effort Small (1-2 days)
Difficulty Medium
Risk Medium -- decay data feeds depletion chains

PR 9: Use parse_mf23/parse_mf27/parse_mf28 in photon.py

Files: openmc/data/photon.py

Three parsing methods (AtomicRelaxation.from_endf, IncidentPhoton.from_endf,
photon reaction parsing) total ~172 lines of manual HEAD/CONT/LIST/TAB1 reading
for MF=23 (photo-atomic cross sections), MF=27 (form factors), and MF=28
(atomic relaxation).

Lines saved ~70-100
Effort Medium (2-3 days)
Difficulty Medium
Risk Medium

PR 10: Use parse_mf2 in resonance.py

Files: openmc/data/resonance.py

The largest block of manual parsing in OpenMC: 534 lines across six
from_endf() methods covering SLBW, MLBW, Reich-Moore, R-Matrix Limited,
and unresolved resonance formalisms. endf.parse_mf2() returns a structured
dict for all of these. The OpenMC side would still construct its rich resonance
objects but from pre-parsed dicts instead of raw records.

Lines saved ~200-320
Effort Large (5-7 days)
Difficulty High
Risk High -- resonance processing is complex with many formalisms; extensive testing needed

PR 11: Use parse_mf12/parse_mf13/parse_mf14/parse_mf15 in reaction product parsing

Files: openmc/data/reaction.py, openmc/data/neutron.py

The Reaction.from_endf() method (92 lines) orchestrates parsing of MF=3,4,5,6
and also MF=12/13 (photon production multiplicities/cross sections). After
PRs 4-6 land for the sub-parsers, this orchestration layer can be simplified
to use pre-parsed endf.Material.section_data dicts directly.

Lines saved ~30-50
Effort Medium (2-3 days)
Difficulty Medium-high
Risk Medium -- depends on PRs 4-6

PR 12: Use parse_mf33/parse_mf34 in resonance_covariance.py

Files: openmc/data/resonance_covariance.py

316 lines of manual parsing across three from_endf() methods for MF=32
MT=151 resonance parameter covariances. Note: the endf package has no
parse_mf32
-- it only has parse_mf33 and parse_mf34 for cross-section
and angular covariances. MF=32 (resonance covariances) must remain manual
unless endf-python adds support.

The MF=33/34 parsers could still help if any MF=33/34 data is read in this
file, but currently the file only reads MF=32.

Lines saved ~0 (blocked -- no parse_mf32 in endf package)
Effort N/A unless endf-python adds MF=32 support
Difficulty N/A
Risk N/A

PR 13: Use endf.Material.interpret() for high-level IncidentNeutron.from_endf()

Files: openmc/data/neutron.py

The capstone integration: instead of OpenMC's from_endf() manually iterating
over sections and dispatching to per-MF parsers, use
endf.Material.interpret() to get an endf.IncidentNeutron with fully parsed
reactions, then convert to OpenMC's richer objects. This subsumes remaining
Phase 3 work and could eliminate the orchestration layer entirely.

Lines saved Hard to estimate independently; subsumes PRs 3-11
Effort Large (1-2 weeks)
Difficulty High
Risk High -- requires mapping endf-python's entire data model to OpenMC's validated domain objects

Summary Table

PR Description Lines Saved Effort Difficulty Risk
1 Evaluation -> Material adapter ~130 Small Low Low
2 get_evaluations -> get_materials ~22 Trivial Low Low
3 MF=1 MT=458 (fission energy) ~55-85 Small Low-med Low
4 MF=4 (angular distributions) ~40-60 Small-med Medium Medium
5 MF=5 (energy distributions) ~70-105 Medium Medium Medium
6 MF=6 (angle-energy, 4 files) ~80-120 Medium Medium Medium
7 MF=7 (thermal scattering) ~65-100 Medium Medium Medium
8 MF=8 (decay data) ~13-20 Small Medium Medium
9 MF=23/27/28 (photon) ~70-100 Medium Medium Medium
10 MF=2 (resonances) ~200-320 Large High High
11 MF=12-15 (reaction products) ~30-50 Medium Med-high Medium
12 MF=32 (resonance covariance) 0 Blocked N/A N/A
13 IncidentNeutron.interpret() TBD Large High High
Total (PRs 1-11) ~775-1110

Suggested Merge Order

Low-hanging fruit:
  PR 1  (Evaluation adapter)                     ~130 lines
  PR 2  (get_evaluations)                         ~22 lines
  PR 3  (MF=1 -- good pilot for MF parsers)    55-85 lines

MF parser adoption (independent, any order):
  PR 4  (MF=4)                                  40-60 lines
  PR 5  (MF=5)                                 70-105 lines
  PR 6  (MF=6)                                 80-120 lines
  PR 7  (MF=7)                                 65-100 lines
  PR 8  (MF=8)                                  13-20 lines
  PR 9  (MF=23/27/28)                          70-100 lines

Heavy lifts (after MF parsers land):
  PR 10 (MF=2 resonances)                     200-320 lines
  PR 11 (MF=12-15 reaction products)            30-50 lines
  PR 13 (IncidentNeutron.interpret capstone)       TBD

PRs 1-2 are independent of each other. PRs 3-9 are independent of each other
but benefit from PR 1 landing first. PR 10 is the highest-value single PR but
also the riskiest. PR 13 is a capstone that subsumes the orchestration saved
by earlier PRs.

What OpenMC Should Keep

endf-python is a lightweight reader. OpenMC's higher-level infrastructure
should remain:

  • Validation -- property setters with checkvalue
  • HDF5 persistence -- to_hdf5() / from_hdf5() methods
  • Resonance processing -- reconstruction, Doppler broadening
  • Resonance covariance -- MF=32 (no endf-python equivalent)
  • Rich function types -- Polynomial, Sum, Regions1D, ResonancesWithBackground
  • Transport logic -- redundant reactions, derived products, temperature handling

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions