You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Tracking issue / design proposal. This proposes adding a transport-free multigroup cross-section method to openmc.Model.convert_to_multigroup — method="transport_free" — for use with the random-ray solver, targeting fusion deep-penetration shielding. It generates MGXS deterministically (no Monte Carlo, no nparticles) by collapsing the configured continuous-energy data against an assumed weighting flux with Bondarenko σ₀ self-shielding. Full design below. (References to DESIGN.md point to a companion standalone-package design kept outside this repo.)
Status: Draft v1 · Form: a new method on openmc.Model.convert_to_multigroup (e.g. method="transport_free"), implemented on a branch of shimwell/openmc. · Companion: the standalone-package design (DESIGN.md) — this doc is the short-term, in-tree test vehicle that shares the same principles.
1. Why a second, in-OpenMC design
The standalone package (DESIGN.md) is the long-term, decoupled goal. But the fastest way to test whether transport-free MGXS is actually good is to implement it inside OpenMC, because almost every ingredient already exists in-tree:
Need
Already in OpenMC
Broadened pointwise σ(E), per temperature
openmc.data.IncidentNeutron.from_hdf5 (reads your ENDF/B-8.1)
Model.convert_to_multigroup (incl. the name handling fixed in PRs #108/#110)
Downstream solver
Model.convert_to_random_ray
Validation references
convert_to_multigroup(method="material_wise" / "stochastic_slab" / "infinite_medium") — one-line swaps on the same model
So the only genuinely new code is the deterministic collapse + σ₀ self-shielding + scatter-matrix assembly. No format converters, no external engine (NJOY/FRENDY), no packaging. This is essentially "Route B from the standalone doc, implemented in-tree.”
It also doubles as a clean proof-of-concept that can later be extracted into the standalone package (or kept in OpenMC if it's good enough to upstream).
2. Goal & scope
A transport-free, deterministic MGXS generator selectable as convert_to_multigroup(method="transport_free", …):
No transport solve, no Monte Carlo, no nparticles — the central UX win (see §6).
Collapses the configured continuous-energy data (ENDF/B-8.1) against an assumed weighting flux with Bondarenko σ₀ self-shielding from the material composition.
Produces a standard MGXSLibrary (mgxs.h5) that feeds convert_to_random_ray.
Neutrons first; photons a stretch goal (§8).
Sits between stochastic_slab (beat it) and material_wise (reference) — same success criterion as the standalone doc.
Shared physics (weighting-flux models, NR/Bondarenko σ₀, the spatial-spectrum limit, group-structure knee, anisotropy/transport-correction, the literature grounding) is documented in DESIGN.md §4 and not repeated here — this doc covers only what's specific to the in-tree implementation.
Non-goals
No external engine, no MATXS/GENDF, no FENDL dependency (see §3).
Not (initially) the full option-3 slowing-down solver or photons — phased (§7).
Not thermal S(α,β) — fast/epithermal fusion spectra.
3. Nuclear data: ENDF/B-8.1, not FENDL
Use the configured cross sections — i.e. ~/nuclear_data/.../cross_sections.xml (ENDF/B-8.1 HDF5), via openmc.config['cross_sections'] / materials.cross_sections, read with openmc.data.IncidentNeutron.from_hdf5.
Why ENDF/B-8.1 over FENDL here:
It's the evaluation you actually use, so the MGXS are consistent with your downstream CE Monte Carlo and any later analysis by construction.
openmc.data reads it natively (broadened pointwise + URR probability tables) — zero new I/O.
FENDL's distinctive value in the standalone doc was the pre-collapsed MG product (Route C) and fusion-tuned evaluations. Here we collapse ourselves, so the pre-collapsed advantage is moot, and ENDF/B-8.1 is a strong general evaluation. No benefit to FENDL for this path → use ENDF/B-8.1.
(If a fusion-specific evaluation is ever wanted, it's just a different cross_sections.xml — the code is evaluation-agnostic.)
4. Design principles (same as the standalone)
Transport-free / deterministic — collapse pointwise data; no sampling.
No opaque convergence dial (hard requirement) — no nparticles. Parameters are auto-derived, discrete well-defaulted physics choices, or carry a deterministic reported convergence metric. (nparticles is the footgun being eliminated.)
σ₀ self-shielding auto-derived from the Material composition.
Weighting flux source-independent by default, source-optional for accuracy — the default w(E)/S(E) is a generic standard spectrum (1/E + Maxwellian + smooth high-energy tail, à la VITAMIN-J/GROUPR), so the source is not a required input and 14 MeV is never hardcoded. With fine groups the within-group shape barely matters (and the deep-penetration-critical keV windows are source-insensitive), so this is genuinely good enough — it is how standard fine-group libraries are built, and it makes the output reusable across source problems. If settings.source is present it is used optionally to sharpen the fast/threshold groups (DD / DT-Muir / TT / mixtures — evaluated on the grid, not sampled). Free accuracy, never required.
Reproducible — deterministic, bit-for-bit, provenance recorded in the library metadata.
Reuse, don't reinvent — lean on openmc.data, openmc.mgxs, MGXSLibrary.
Per-material direct collapse — never combine pre-made nuclide MGs. Multigroup cross sections are flux-weighted averages, not pointwise data, so they do not add cleanly: Σ_{x,g}^mat = Σ_i N_i ⟨σ_{x,i}⟩ weighted by the material's own flux. Mixing per-nuclide MGs (each weighted by a generic flux) is only exact in the fine-group, no-self-shielding limit — self-shielding makes the within-group flux material-specific, and resonance interference is lost entirely. We therefore build each material's macroscopic Σ_x(E) first and collapse it against the material's own (slowing-down) flux, which sidesteps additivity and captures self-shielding + interference exactly. (A reusable per-nuclide + Bondarenko-f-factor library is a valid but strictly-approximate, option-2-capped distributable variant — out of scope for the accurate in-tree generator.)
5. The method (what the new code computes)
Per material, per group g, reaction x, with macroscopic Σ_x(E)=Σ_i N_i σ_{x,i}(E):
Σ_{x,g} = ∫_g Σ_x(E) φ(E) dE / ∫_g φ(E) dE
Weighting flux φ(E) (see DESIGN.md §4.3):
Default — option 3 (0-D slowing-down): solve Σ_t(E)φ(E)=∫Σ_s(E'→E)φ(E')dE'+S(E) on a fine lethargy mesh — the most accurate transport-free flux; weight=None resolves here for neutrons. Heaviest piece to build (needs the scattering kernels; shares machinery with the scatter matrix), so it lands in Phase 3 — until then NR is the interim default.
Cheaper opt-in — option 2 (NR / Bondarenko):φ(E) = w(E)/(Σ_t(E)+σ₀), with w(E) a generic standard weight (optionally source-sharpened — principle 4), σ₀ from composition. Just divide the already-broadened σ_t; captures resonance self-shielding; for fusion structural metals ~as good as option 3. The interim default during Phases 1–2 and a permanent speed option (weight="nr").
Self-shielding:
Resolved range: the HDF5 pointwise data is already Doppler-broadened at temperature → weight the existing arrays by 1/(Σ_t+σ₀). Easy, reliable.
Unresolved range: band-wise Bondarenko using IncidentNeutron.urr[T].table.
σ₀ from Material: σ₀,r = (1/N_r) Σ_{j≠r} N_j σ_{t,j}; short fixed-point iteration. Homogeneous/infinite-medium first; Dancoff/equivalence later.
Scatter matrix Σ_{s,g→g'} (the main new work): built from openmc.data secondary distributions, not MC tallies.
Elastic: analytic group-transfer from kinematics (E'∈[αE,E]) folded with the angular distribution → Legendre moments.
Inelastic / (n,2n) / continuum: from Reaction.products[i].distribution (energy laws, CorrelatedAngleEnergy).
Transport correction / Pn order: start P0, no transport correction (matches the C5G7 random-ray practice — avoids negative cross sections), then add consistent-P / transport-corrected options as a parameter (DESIGN.md §4.6).
Outputs to XSdata:set_total, set_absorption, set_scatter_matrix ([G,G',L+1]); fissionables add set_fission/set_nu_fission/set_chi/set_kappa_fission.
6. API (no nparticles)
model.convert_to_multigroup(
method="transport_free", # the new methodgroups="UKAEA-1102", # any GROUP_STRUCTURES name or edgesmgxs_path="mgxs.h5",
)
# NO weight / scatter_order / correction arguments. The method always does the# most accurate VALID treatment automatically:# • weighting flux : most accurate per particle (neutrons slowing-down, photons analytic)# • scattering : P0 — random ray is a SCALAR-FLUX solver that *requires*# isotropic MGXS (it fatal-errors on anisotropic data; flat_source_domain.cpp# builds the scatter source as sigma_s × scalar_flux, i.e. moment 0 only), with# the transport correction auto-applied + an automatic negativity guard# (fall back per-group where a corrected XS would go negative, and report).model.convert_to_random_ray()
nparticles is not a parameter of this method. If passed (it's a shared kwarg on convert_to_multigroup), it is ignored with a warning — concretely demonstrating the no-footgun principle.
No weight, scatter_order, or correction arguments. All three are removed — the method always does the most accurate valid treatment, so a user can never misset accuracy downward (the nparticles anti-footgun principle, applied to every knob):
weighting → most accurate per particle (neutrons slowing-down, photons analytic); cheaper NR/analytic models survive internally only (phased interim + validation comparison). (Interim: None resolves to NR until the Phase-3 slowing-down solver lands — §7.)
scatter order → forced to P0: random ray is a scalar-flux solver and requires isotropic MGXS, so higher orders are unusable and anisotropic data fatal-errors. No choice to expose.
transport correction → auto-applied (TC-P0) with an automatic negativity guard: apply the correction; where a corrected cross section would go negative, fall back per-group to the safe value and report it. ("Most accurate" here is capped by the P0 solver — true anisotropy needs a Pn random-ray solver, out of scope.)
Optional deterministic convergence report: a helper that regenerates at two group structures (e.g. 709→1102) and reports the max ΔΣ — replaces "did my MC converge?" with a reproducible number.
Collapse total/absorption/(n,γ)/fission/ν-fission/χ/κ-fission over openmc.data with the NR flux; σ₀ from composition; URR via probability tables.
Validation (no transport run needed): generate material_wise / infinite_medium libraries on the same model and compare the group XS values (the in-tree references make this a few lines). This tests the whole collapse + data + MGXSLibrary path and the self-shielding, fast.
Deliverable: deterministic, noise-free vector MGXS that match infinite_medium and beat stochastic_slab's noise.
Phase 2 — scatter matrices → full library → random ray.
Elastic (analytic) first, then inelastic/continuum from secondary distributions; P0 no-TC.
Now the library is runnable → convert_to_random_ray → compare downstream results (flux/reaction-rate/dose vs depth) against CE Monte Carlo and against the material_wise/stochastic_slab libraries. This is the "is it any good?" experiment (DESIGN.md §11), made trivial because all references live in the same code.
Phase 3 — accuracy upgrades & stretch.
Option-3 slowing-down weighting (the 0-D solver).
Transport correction within P0 (TC-P0 + automatic negativity guard; TC-MHT-style "anisotropy into P0"). True Pn is impossible without extending the RR solver — out of scope.
Group-structure fineness sweep (resolve Fe windows; find the knee).
Photons (stretch — §8).
8. Photons — stretch goal, and why (D1S)
Photon MGXS are deferred for this in-tree effort, for a workflow-specific reason, not a technical one:
The intended photon use is photon weight windows for shutdown-dose-rate work via D1S (Direct-1-Step).
In D1S the decay photons are produced during the neutron transport (prompt-like emission from neutron-induced activation, via modified photon-production data scaled by time factors). So the photon source is emergent — it is not known before the simulation.
Random ray (deterministic) needs the photon source as an input to solve the photon field / generate weight windows. With D1S there is no standalone, pre-defined photon source to hand it → photon-MGXS-for-random-ray doesn't fit the primary workflow.
(Contrast R2S, which does produce an explicit decay-photon source from a separate activation step — there, photon MGXS would be directly usable. So photons are deferred, not abandoned: useful whenever the photon source is known a priori, e.g. R2S, fixed gamma sources, or prompt capture-gamma transport with known production.)
When photons are tackled, they're easier physics (smooth XS, no resonances → option-1 weighting; Compton via Klein-Nishina × S(x,Z), coherent via form factor — all in openmc.data.IncidentPhoton) — the blocker is the use case, not the implementation.
9. Implementation location & shape
New module, e.g. openmc/mgxs/transport_free.py (or openmc/data/group_collapse.py), holding the collapse + σ₀ + scatter-matrix code (pure Python over openmc.data/numpy).
Keep the new code engine-free and MC-free so it stays deterministic and dependency-light (numpy + openmc.data).
Designed so the core collapse functions are extractable into the standalone package later (same data model in spirit).
10. Validation (leveraging in-tree references)
The in-tree position makes the §11 experiment from DESIGN.md nearly free:
formethodin ["transport_free", "stochastic_slab", "material_wise"]:
m=model.clone()
m.convert_to_multigroup(method=method, groups="UKAEA-1102", mgxs_path=f"{method}.h5")
# compare group XS (Phase 1) and downstream random-ray flux/dose vs CE (Phase 2)
Level 1 (correctness):transport_free (∞-dilution) vs infinite_medium — same physics, deterministic vs MC → must match → verifies the collapse math.
Level 2 (usefulness): vs stochastic_slab and material_wise, and vs a CE reference, on a steel/tungsten shield with depth-resolved metrics. Acceptance: error < stochastic_slab, between it and material_wise.
11. Risks
Risk
Note
Scatter matrix from openmc.data is the bulk of the work
elastic is analytic & tractable; inelastic/continuum need careful integration of secondary distributions. The accuracy-critical piece (DESIGN.md §4.6).
Negative XS from transport correction in random ray
start P0 no-TC.
Spatial-spectrum error (inherent)
won't beat material_wise; finer groups + (later) zoned weighting.
Option-3 slowing-down solver effort
deferred to Phase 3; option 2 carries Phases 1–2.
URR probability-table handling
resolved range covers most; URR a refinement.
12. Relationship to the standalone package
Decision: this in-tree branch is the active build; the standalone package is deferred ("future extraction"). Rationale (from the side discussions): openmc.data already supplies the entire data layer (broadened σ(E), secondary distributions, URR probability tables, photon data, group structures, MGXSLibrary), so the only genuinely new code is the processing (collapse + σ₀ evaluator + slowing-down solver + scatter-matrix-from-distributions builder) — and that processing is reader-independent. Building in-tree therefore costs the least and locks in nothing.
endf-python is off the critical path. Its shimwell fork additions (incident-photon, removal_xs, …) are independently useful (upstream if desired) but are not a dependency here. The fork only becomes relevant if a decoupled, openmc-free distributable tool is later wanted — at which point it is that tool's reader, not this work.
Liftability rule: the collapse/σ₀/scatter core is written reader-agnostic behind a thin data-model boundary, so it can be extracted into the standalone package later (swap openmc.data → endf-python fork) with no rewrite. Same principles, different packaging.
References
Shared literature and citations: see DESIGN.md §17. OpenMC-specific API surfaces referenced above are in openmc/data/ (neutron.py, photon.py, reaction.py, urr.py, resonance.py), openmc/mgxs/ (groups.py, GROUP_STRUCTURES), and openmc/mgxs_library.py (XSdata, MGXSLibrary).
Transport-Free Multigroup Cross Sections — In-OpenMC Implementation
Status: Draft v1 · Form: a new method on
openmc.Model.convert_to_multigroup(e.g.method="transport_free"), implemented on a branch ofshimwell/openmc. · Companion: the standalone-package design (DESIGN.md) — this doc is the short-term, in-tree test vehicle that shares the same principles.1. Why a second, in-OpenMC design
The standalone package (
DESIGN.md) is the long-term, decoupled goal. But the fastest way to test whether transport-free MGXS is actually good is to implement it inside OpenMC, because almost every ingredient already exists in-tree:openmc.data.IncidentNeutron.from_hdf5(reads your ENDF/B-8.1)IncidentNeutron.urr[T].tableReaction.products[i].distribution(CorrelatedAngleEnergy, …)openmc.mgxs.GROUP_STRUCTURES(UKAEA-1102,CCFE-709,VITAMIN-J-175…)openmc.XSdata/openmc.MGXSLibrary→mgxs.h5Model.convert_to_multigroup(incl. the name handling fixed in PRs #108/#110)Model.convert_to_random_rayconvert_to_multigroup(method="material_wise" / "stochastic_slab" / "infinite_medium")— one-line swaps on the same modelSo the only genuinely new code is the deterministic collapse + σ₀ self-shielding + scatter-matrix assembly. No format converters, no external engine (NJOY/FRENDY), no packaging. This is essentially "Route B from the standalone doc, implemented in-tree.”
It also doubles as a clean proof-of-concept that can later be extracted into the standalone package (or kept in OpenMC if it's good enough to upstream).
2. Goal & scope
A transport-free, deterministic MGXS generator selectable as
convert_to_multigroup(method="transport_free", …):nparticles— the central UX win (see §6).MGXSLibrary(mgxs.h5) that feedsconvert_to_random_ray.stochastic_slab(beat it) andmaterial_wise(reference) — same success criterion as the standalone doc.Shared physics (weighting-flux models, NR/Bondarenko σ₀, the spatial-spectrum limit, group-structure knee, anisotropy/transport-correction, the literature grounding) is documented in
DESIGN.md §4and not repeated here — this doc covers only what's specific to the in-tree implementation.Non-goals
3. Nuclear data: ENDF/B-8.1, not FENDL
Use the configured cross sections — i.e.
~/nuclear_data/.../cross_sections.xml(ENDF/B-8.1 HDF5), viaopenmc.config['cross_sections']/materials.cross_sections, read withopenmc.data.IncidentNeutron.from_hdf5.Why ENDF/B-8.1 over FENDL here:
openmc.datareads it natively (broadened pointwise + URR probability tables) — zero new I/O.cross_sections.xml— the code is evaluation-agnostic.)4. Design principles (same as the standalone)
nparticles. Parameters are auto-derived, discrete well-defaulted physics choices, or carry a deterministic reported convergence metric. (nparticlesis the footgun being eliminated.)Materialcomposition.w(E)/S(E)is a generic standard spectrum (1/E + Maxwellian + smooth high-energy tail, à la VITAMIN-J/GROUPR), so the source is not a required input and 14 MeV is never hardcoded. With fine groups the within-group shape barely matters (and the deep-penetration-critical keV windows are source-insensitive), so this is genuinely good enough — it is how standard fine-group libraries are built, and it makes the output reusable across source problems. Ifsettings.sourceis present it is used optionally to sharpen the fast/threshold groups (DD / DT-Muir / TT / mixtures — evaluated on the grid, not sampled). Free accuracy, never required.openmc.data,openmc.mgxs,MGXSLibrary.Σ_{x,g}^mat = Σ_i N_i ⟨σ_{x,i}⟩weighted by the material's own flux. Mixing per-nuclide MGs (each weighted by a generic flux) is only exact in the fine-group, no-self-shielding limit — self-shielding makes the within-group flux material-specific, and resonance interference is lost entirely. We therefore build each material's macroscopicΣ_x(E)first and collapse it against the material's own (slowing-down) flux, which sidesteps additivity and captures self-shielding + interference exactly. (A reusable per-nuclide + Bondarenko-f-factor library is a valid but strictly-approximate, option-2-capped distributable variant — out of scope for the accurate in-tree generator.)5. The method (what the new code computes)
Per material, per group
g, reactionx, with macroscopicΣ_x(E)=Σ_i N_i σ_{x,i}(E):Weighting flux
φ(E)(seeDESIGN.md §4.3):Σ_t(E)φ(E)=∫Σ_s(E'→E)φ(E')dE'+S(E)on a fine lethargy mesh — the most accurate transport-free flux;weight=Noneresolves here for neutrons. Heaviest piece to build (needs the scattering kernels; shares machinery with the scatter matrix), so it lands in Phase 3 — until then NR is the interim default.φ(E) = w(E)/(Σ_t(E)+σ₀), withw(E)a generic standard weight (optionally source-sharpened — principle 4),σ₀from composition. Just divide the already-broadened σ_t; captures resonance self-shielding; for fusion structural metals ~as good as option 3. The interim default during Phases 1–2 and a permanent speed option (weight="nr").Self-shielding:
1/(Σ_t+σ₀). Easy, reliable.IncidentNeutron.urr[T].table.Material:σ₀,r = (1/N_r) Σ_{j≠r} N_j σ_{t,j}; short fixed-point iteration. Homogeneous/infinite-medium first; Dancoff/equivalence later.Scatter matrix
Σ_{s,g→g'}(the main new work): built fromopenmc.datasecondary distributions, not MC tallies.E'∈[αE,E]) folded with the angular distribution → Legendre moments.Reaction.products[i].distribution(energy laws,CorrelatedAngleEnergy).DESIGN.md §4.6).Outputs to
XSdata:set_total,set_absorption,set_scatter_matrix([G,G',L+1]); fissionables addset_fission/set_nu_fission/set_chi/set_kappa_fission.6. API (no
nparticles)nparticlesis not a parameter of this method. If passed (it's a shared kwarg onconvert_to_multigroup), it is ignored with a warning — concretely demonstrating the no-footgun principle.weight,scatter_order, orcorrectionarguments. All three are removed — the method always does the most accurate valid treatment, so a user can never misset accuracy downward (thenparticlesanti-footgun principle, applied to every knob):Noneresolves to NR until the Phase-3 slowing-down solver lands — §7.)7. Phased implementation (the test plan)
Phase 1 — vector XS + σ₀ self-shielding (option 2). Validate by XS values.
openmc.datawith the NR flux; σ₀ from composition; URR via probability tables.material_wise/infinite_mediumlibraries on the same model and compare the group XS values (the in-tree references make this a few lines). This tests the whole collapse + data +MGXSLibrarypath and the self-shielding, fast.infinite_mediumand beatstochastic_slab's noise.Phase 2 — scatter matrices → full library → random ray.
convert_to_random_ray→ compare downstream results (flux/reaction-rate/dose vs depth) against CE Monte Carlo and against thematerial_wise/stochastic_slablibraries. This is the "is it any good?" experiment (DESIGN.md §11), made trivial because all references live in the same code.Phase 3 — accuracy upgrades & stretch.
8. Photons — stretch goal, and why (D1S)
Photon MGXS are deferred for this in-tree effort, for a workflow-specific reason, not a technical one:
When photons are tackled, they're easier physics (smooth XS, no resonances → option-1 weighting; Compton via Klein-Nishina × S(x,Z), coherent via form factor — all in
openmc.data.IncidentPhoton) — the blocker is the use case, not the implementation.9. Implementation location & shape
openmc/mgxs/transport_free.py(oropenmc/data/group_collapse.py), holding the collapse + σ₀ + scatter-matrix code (pure Python overopenmc.data/numpy).Model.convert_to_multigroupalongside_generate_material_wise_mgxsetc. — reuse the existing tail (cross_sections assignment, macroscopic conversion, name handling from Raise on duplicate material names in convert_to_multigroup #108/Preserve user material names in convert_to_multigroup #110,energy_mode='multi-group').openmc.data).10. Validation (leveraging in-tree references)
The in-tree position makes the §11 experiment from
DESIGN.mdnearly free:transport_free(∞-dilution) vsinfinite_medium— same physics, deterministic vs MC → must match → verifies the collapse math.stochastic_slabandmaterial_wise, and vs a CE reference, on a steel/tungsten shield with depth-resolved metrics. Acceptance: error <stochastic_slab, between it andmaterial_wise.11. Risks
openmc.datais the bulk of the workDESIGN.md §4.6).material_wise; finer groups + (later) zoned weighting.12. Relationship to the standalone package
openmc.dataalready supplies the entire data layer (broadened σ(E), secondary distributions, URR probability tables, photon data, group structures,MGXSLibrary), so the only genuinely new code is the processing (collapse + σ₀ evaluator + slowing-down solver + scatter-matrix-from-distributions builder) — and that processing is reader-independent. Building in-tree therefore costs the least and locks in nothing.shimwellfork additions (incident-photon,removal_xs, …) are independently useful (upstream if desired) but are not a dependency here. The fork only becomes relevant if a decoupled, openmc-free distributable tool is later wanted — at which point it is that tool's reader, not this work.openmc.data→ endf-python fork) with no rewrite. Same principles, different packaging.References
Shared literature and citations: see
DESIGN.md §17. OpenMC-specific API surfaces referenced above are inopenmc/data/(neutron.py, photon.py, reaction.py, urr.py, resonance.py),openmc/mgxs/(groups.py,GROUP_STRUCTURES), andopenmc/mgxs_library.py(XSdata,MGXSLibrary).