Skip to content

CRAM substeps #3908

Open
yrrepy wants to merge 1 commit intoopenmc-dev:developfrom
yrrepy:CRAM_sub-steps
Open

CRAM substeps #3908
yrrepy wants to merge 1 commit intoopenmc-dev:developfrom
yrrepy:CRAM_sub-steps

Conversation

@yrrepy
Copy link
Copy Markdown
Contributor

@yrrepy yrrepy commented Mar 30, 2026

Description

As part of my continuing battle against OpenMC deplete producing negative atom results and oscillations about zero.

This PR adds a substeps parameter to Integrator and SIIntegrator that subdivides each depletion interval into identical sub-intervals, reusing LU factorizations across them. This improves CRAM accuracy for nuclides with large λ·dt products — particularly those depleting toward zero, where single-step CRAM can undershoot to negative atom densities.

The implementation follows:
Isotalo & Pusa (2016), "Improving the Accuracy of the Chebyshev Rational Approximation Method Using Substeps," Nucl. Sci. Eng., 183:1, 65-77. https://doi.org/10.13182/NSE15-67

This addresses the same problem as #3879, but does it through improvements to solver numerical accuracy (step approximation error).
The two are complementary. Clip would be a safety net, and a nice cleanup of low atoms.

Substeps is invoked with:

openmc.deplete.PredictorIntegrator(operator, timesteps, power=power,
      substeps=2)  # subdivide each step into 2 sub-intervals

The default is left as substeps=1 (no substeps).

Personally, in future workflows with both PRs in, I would likely use both substeps=2, clip_min_atom_density=1e-20. As an improvement in numerical accuracy and a full-guard against negative atoms.

Similar script to #3879
demo.py

Here with substeps=2

[openmc.deplete] t=0.0 s, dt=1.0 s, source=100000000000000.0
[openmc.deplete] t=1.0 s, dt=43200.0 s, source=0.0
[openmc.deplete] t=43201.0 s, dt=43200.0 s, source=0.0
[openmc.deplete] t=86401.0 s, dt=43200.0 s, source=0.0
[openmc.deplete] t=129601.0 s, dt=43200.0 s, source=0.0
[openmc.deplete] t=172801.0 s, dt=43200.0 s, source=0.0
[openmc.deplete] t=216001.0 s, dt=43200.0 s, source=0.0
[openmc.deplete] t=259201.0 s, dt=43200.0 s, source=0.0
[openmc.deplete] t=302401.0 s, dt=43200.0 s, source=0.0
[openmc.deplete] t=345601.0 s, dt=43200.0 s, source=0.0
[openmc.deplete] t=388801.0 s, dt=43200.0 s, source=0.0

WITH substeps=2 — improved accuracy near zero:
  step  1 (t=   0h):   5.79e+12
  step  2 (t=  12h):   1.78e-81
  step  3 (t=  24h):  5.47e-175
  step  4 (t=  36h):  1.68e-268
  step  5 (t=  48h):   0.00e+00
  step  6 (t=  60h):   0.00e+00
  step  7 (t=  72h):   0.00e+00
  step  8 (t=  84h):   0.00e+00
  step  9 (t=  96h):   0.00e+00
  step 10 (t= 108h):   0.00e+00
  step 11 (t= 120h):   0.00e+00

Attached in a zip (actually a .xz) are two further synthetic benchmarks that demonstrate the improvements.
substeps_synthetic-benchmarks.zip

They also demonstrate the near zero impact on calculation performance (with an MC inspired FOM).
The error is with respect to the case with fine explicit steps.
One can see from these results, despite improved behaviour, negatives that persist and could be cleaned up with the clip.

Checklist

  • I have performed a self-review of my own code
  • I have run clang-format (version 18) on any C++ source files (if applicable)
  • I have followed the style guidelines for Python source files (if applicable)
  • I have made corresponding changes to the documentation (if applicable)
  • I have added tests that prove my fix is effective or that my feature works (if applicable)

  Splitting a CRAM step into identical substeps and reusing LU
  factorizations improves accuracy for nuclides with large λ·dt
  while adding negligible computational cost.

  This particularly improves accuracy for nuclides approaching zero,
  where a single-step CRAM can undershoot to negative atom densities,
  and then oscillate about zero.

  - IPFCramSolver accepts substeps parameter (default 1)
  - substeps=1 uses original spsolve path (bitwise identical)
  - substeps>1 pre-computes LU via splu, reuses across substeps
  - Integrator/SIIntegrator accept substeps parameter
  - 4 new unit tests: default, bitwise match, two-half-steps,
    CRAM16 self-convergence

  Reference: Isotalo & Pusa, "Improving the Accuracy of the
  Chebyshev Rational Approximation Method Using Substeps,"
  Nucl. Sci. Eng., 183:1, 65-77 (2016).
@yrrepy yrrepy requested a review from paulromano as a code owner March 30, 2026 00:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant