Skip to content

Conversation

paulromano
Copy link
Contributor

@paulromano paulromano commented Jul 18, 2025

Description

Thanks to many recent updates in OpenMC, subvoxel mesh-based R2S calculations are now possible. However, the workflow requires quite a bit of insider knowledge and is not something that is easy for a user to carry out. Enter the R2SManager class. This class gives users a straightforward way to carry out either mesh-based or cell-based R2S calculations by simply providing a Model, a list of domains (for cell-based) or mesh (for mesh-based), and then calling the run method (which takes timesteps, source rates, etc.). This makes it just as easy to run an R2S calculation as it currently is to run a D1S calculation:

...
model = openmc.Model(...)

# Define overlaid mesh for activation
mesh = openmc.RegularMesh()
...

# Create R2S manager
manager = openmc.deplete.R2SManager(model, mesh)

# Define dose tally for photon model
dose_tally = openmc.Tally()
manager.photon_model.tallies = [dose_tally]

# Execute R2S calculation
timesteps = [(6.0, 'd'), (10, 'h')]
source_rates = [1e12, 0.0]
manager.run(
    timesteps,
    source_rates,
    cooling_times=[-1],
    dose_tallies=[dose_tally]
)

The run method orchestrates calls to three underlying methods:

  • step1_neutron_transport
  • step2_activation
  • step3_photon_transport

So if a user wants to separately execute one of the steps, they have the flexibility to do so. Each step will populate different entries of the results dictionary (present as an attribute on R2SManager). Finally, to keep the output files organized, a single directory holds all the results, with subdirectories for each step:

r2s_<timestamp>/
├── activation
│   ├── depletion_results.h5
│   └── materials.xml
├── neutron_transport
│   ├── fluxes.npy
│   ├── mesh_material_volumes.npz
│   ├── micros.h5
│   ├── model.xml
│   └── statepoint.h5
└── photon_transport
    ├── mesh_material_volumes.npz (optional)
    ├── tally_ids.json
    └── time_2
        ├── model.xml
        ├── statepoint.10.h5
        ├── summary.h5
        └── tallies.out

Notable features

  • Users can pass arguments through to get_microxs_and_flux, which enables them to play with different reaction rate modes and energy group structures
  • Separate Settings can be specified for the photon transport calculation using the photon_settings argument to run()
  • Right now, cell-based calculations require the user to specify volumes and bounding boxes for each activated cell, but I'm working on a separate PR that will enable these bounding boxes to be computed automatically

Checklist

  • I have performed a self-review of my own code
  • I have run clang-format (version 15) 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)

@shimwell
Copy link
Member

Nice I shall take a better look. I just wanted to mention one of the reasons to use R2S over D1S is that it allows movement of geometry. I am not sure how to accommodate such a feature but would be interesting if the OpenMC R2S implementation can support movement of geometry perhaps via a list of models instead of a single model?

@eepeterson
Copy link
Contributor

Thanks for the awesome addition here! My initial comments on the draft are below:

  1. I'd prefer to see all the arguments that are passed to run to be passed to the constructor of the R2SManager instead since the R2S workflow is not fully defined without many of those things. Then, the run method arguments could parallel those of the Model class a little more.
  2. It would be good for the design to be agnostic to whether the Independent or Coupled operator is being used so we can easily check the comparison, particularly in the case of high burnup materials over long time periods in actual power plant designs. Within this context I think you could combine the neutron transport and activation directories since that's how it would get executed by default with the CoupledOperator I believe.
  3. The cooling_times argument could be slightly misleading to users since based on its name it could be understood as having an impact on the irradiation schedule (read the docs and it doesn't, but nevertheless). I think this could be more accurately called photon_transport_indices or something. It could also be an optional argument that defaults to the final timestep or None for all timesteps.

I'll take a deeper look at some of the subvoxel stuff and provide some more feedback there, but these are just the immediate high level things.

@paulromano
Copy link
Contributor Author

Thanks for the comments @eepeterson!

I'd prefer to see all the arguments that are passed to run to be passed to the constructor of the R2SManager instead since the R2S workflow is not fully defined without many of those things. Then, the run method arguments could parallel those of the Model class a little more.

Yeah, I thought about this too when I was putting the class together. I chose the current design because 1) the arguments are really run-time options that are propagated to one of the steps as opposed to state that needs to persist through the full workflow and 2) along similar lines it makes it more explicit what information is needed to execute the steps. The arguments to __init__ are things that are needed for all steps (the model and the domains) and are hence stored as attributes on the class.

It would be good for the design to be agnostic to whether the Independent or Coupled operator is being used so we can easily check the comparison, particularly in the case of high burnup materials over long time periods in actual power plant designs. Within this context I think you could combine the neutron transport and activation directories since that's how it would get executed by default with the CoupledOperator I believe.

For now I didn't tackle anything related to CoupledOperator because it's not possible to use it for a mesh-based calculation. I agree that having the design agnostic to is a good idea though. Even if we were to support CoupledOperator for cell-based calculations in the future, it would be easy enough to redirect the output files into the same structure that is currently used here.

The cooling_times argument could be slightly misleading ...

Agreed! I'll update this.

@egor1abs
Copy link
Contributor

egor1abs commented Jul 21, 2025

Thanks for this awesome PR! I plan to test this functionality for my tasks related to ITER neutronics.
But I have a question. If R2SManager is used, is it possible to change the calculation model for neutron and then photon transport with exactly same mesh? So I need to calculate the neutron flux in a large model, and then use these fluxes for a smaller model, because this way will take significantly less time. Or maybe it makes sense at the stage of calculating photon transport to optionally specify the cells in which it is necessary to calculate the transport?

@paulromano
Copy link
Contributor Author

A few notable updates on this branch to bring to your attention:

  1. I wasn't happy with storing a separate CSV file for each MicroXS object. To avoid this, I added to_hdf5/from_hdf5 methods on MicroXS as well as write_microxs_hdf5 and read_microxs_hdf5 functions that put multiple objects in a single file, so now the R2S calculation will result in a single micros.h5 file. It defaults to using LZF compression which I've found to be fast and nearly as good as lower gzip compression levels. This should help minimize disk space usage for large-scale mesh-based calculations.
  2. Rather than having photon_settings and photon_tallies, there is now a single photon_model argument on the constructor that allows a user to specify a separate photon model. By default, it will create a shallow copy of the model, which means you can change the settings/tallies in the photon model separately from the neutron model while preserving the same geometry. To modify the geometry for the photon calculation, it should be deepcopied.
  3. After thinking for a long time about how to handle material changes between the neutron/photon steps, I came up with what I think is a reasonable solution: for a mesh-based calc, if the photon model is different from the neutron model, it will run a mesh-material-volume calculation to identify which materials appear under each mesh element. Then, when creating the decay photon source, it can cross check the photon MeshMaterialVolumes object to see if a material that was present in the neutron model is also present in the photon model; if not, it skips that source. For a cell-based calc, it checks whether the cell is present in the photon model and if it has the same material assigned. This was the final piece that we needed to properly do the FNG dose benchmark, which swaps out some material assignments in the photon model.

@egor1abs The above changes at least partly address the use case you are asking about. For a cell-based calculation, if the cell in the neutron model that was activated is not present in the photon model, it will be skipped. For a mesh-based calculation, no cell information is used when creating the decay photon source so it will only look at what materials appear in each mesh element.

@paulromano paulromano marked this pull request as ready for review September 7, 2025 20:04
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.

4 participants