Skip to content

Add transport-free multigroup XS method: convert_to_multigroup(method='transport_free') #112

Description

@jon-proximafusion

Tracking issue / design proposal. This proposes adding a transport-free multigroup cross-section method to openmc.Model.convert_to_multigroupmethod="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.)


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 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)
Unresolved-resonance probability tables IncidentNeutron.urr[T].table
Secondary energy/angle distributions (for scatter matrices) Reaction.products[i].distribution (CorrelatedAngleEnergy, …)
Fine fusion group structures openmc.mgxs.GROUP_STRUCTURES (UKAEA-1102, CCFE-709, VITAMIN-J-175…)
MGXS output format openmc.XSdata / openmc.MGXSLibrarymgxs.h5
Entry point + material→macroscopic conversion 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)

  1. Transport-free / deterministic — collapse pointwise data; no sampling.
  2. 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.)
  3. σ₀ self-shielding auto-derived from the Material composition.
  4. 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.
  5. Reproducible — deterministic, bit-for-bit, provenance recorded in the library metadata.
  6. Reuse, don't reinvent — lean on openmc.data, openmc.mgxs, MGXSLibrary.
  7. 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 method
    groups="UKAEA-1102",              # any GROUP_STRUCTURES name or edges
    mgxs_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 correctionauto-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.

7. Phased implementation (the test plan)

Phase 1 — vector XS + σ₀ self-shielding (option 2). Validate by XS values.

  • 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).
  • Wire a new branch into Model.convert_to_multigroup alongside _generate_material_wise_mgxs etc. — 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').
  • 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:

for method in ["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).

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