55"""
66
77from __future__ import annotations
8- from collections .abc import Iterable , Sequence
8+ from collections .abc import Sequence
9+ import shutil
910from tempfile import TemporaryDirectory
11+ from typing import Union , TypeAlias
1012
1113import pandas as pd
1214import numpy as np
2729_valid_rxns .append ('damage-energy' )
2830
2931
32+ # TODO: Replace with type statement when support is Python 3.12+
33+ DomainTypes : TypeAlias = Union [
34+ Sequence [openmc .Material ],
35+ Sequence [openmc .Cell ],
36+ Sequence [openmc .Universe ],
37+ openmc .MeshBase ,
38+ openmc .Filter
39+ ]
40+
41+
3042def get_microxs_and_flux (
31- model : openmc .Model ,
32- domains ,
33- nuclides : Iterable [str ] | None = None ,
34- reactions : Iterable [str ] | None = None ,
35- energies : Iterable [float ] | str | None = None ,
36- chain_file : PathLike | Chain | None = None ,
37- run_kwargs = None
38- ) -> tuple [list [np .ndarray ], list [MicroXS ]]:
39- """Generate a microscopic cross sections and flux from a Model
43+ model : openmc .Model ,
44+ domains : DomainTypes ,
45+ nuclides : Sequence [str ] | None = None ,
46+ reactions : Sequence [str ] | None = None ,
47+ energies : Sequence [float ] | str | None = None ,
48+ reaction_rate_mode : str = 'direct' ,
49+ chain_file : PathLike | Chain | None = None ,
50+ path_statepoint : PathLike | None = None ,
51+ run_kwargs = None
52+ ) -> tuple [list [np .ndarray ], list [MicroXS ]]:
53+ """Generate microscopic cross sections and fluxes for multiple domains.
54+
55+ This function runs a neutron transport solve to obtain the flux and reaction
56+ rates in the specified domains and computes multigroup microscopic cross
57+ sections that can be used in depletion calculations with the
58+ :class:`~openmc.deplete.IndependentOperator` class.
4059
4160 .. versionadded:: 0.14.0
4261
62+ .. versionchanged:: 0.15.3
63+ Added `reaction_rate_mode` and `path_statepoint` arguments.
64+
4365 Parameters
4466 ----------
4567 model : openmc.Model
@@ -53,12 +75,22 @@ def get_microxs_and_flux(
5375 Reactions to get cross sections for. If not specified, all neutron
5476 reactions listed in the depletion chain file are used.
5577 energies : iterable of float or str
56- Energy group boundaries in [eV] or the name of the group structure
78+ Energy group boundaries in [eV] or the name of the group structure.
79+ If left as None energies will default to [0.0, 100e6]
80+ reaction_rate_mode : {"direct", "flux"}, optional
81+ Indicate how reaction rates should be calculated. The "direct" method
82+ tallies reaction rates directly. The "flux" method tallies a multigroup
83+ flux spectrum and then collapses multigroup reaction rates after a
84+ transport solve (with an option to tally some reaction rates directly).
5785 chain_file : PathLike or Chain, optional
5886 Path to the depletion chain XML file or an instance of
5987 openmc.deplete.Chain. Used to determine cross sections for materials not
6088 present in the inital composition. Defaults to
6189 ``openmc.config['chain_file']``.
90+ path_statepoint : path-like, optional
91+ Path to write the statepoint file from the neutron transport solve to.
92+ By default, The statepoint file is written to a temporary directory and
93+ is not kept.
6294 run_kwargs : dict, optional
6395 Keyword arguments passed to :meth:`openmc.Model.run`
6496
@@ -69,7 +101,13 @@ def get_microxs_and_flux(
69101 list of MicroXS
70102 Cross section data in [b] for each domain
71103
104+ See Also
105+ --------
106+ openmc.deplete.IndependentOperator
107+
72108 """
109+ check_value ('reaction_rate_mode' , reaction_rate_mode , {'direct' , 'flux' })
110+
73111 # Save any original tallies on the model
74112 original_tallies = model .tallies
75113
@@ -85,8 +123,8 @@ def get_microxs_and_flux(
85123
86124 # Set up the reaction rate and flux tallies
87125 if energies is None :
88- energy_filter = openmc . EnergyFilter ( [0.0 , 100.0e6 ])
89- elif isinstance (energies , str ):
126+ energies = [0.0 , 100.0e6 ]
127+ if isinstance (energies , str ):
90128 energy_filter = openmc .EnergyFilter .from_group_structure (energies )
91129 else :
92130 energy_filter = openmc .EnergyFilter (energies )
@@ -104,16 +142,18 @@ def get_microxs_and_flux(
104142 else :
105143 raise ValueError (f"Unsupported domain type: { type (domains [0 ])} " )
106144
107- rr_tally = openmc .Tally (name = 'MicroXS RR' )
108- rr_tally .filters = [domain_filter , energy_filter ]
109- rr_tally .nuclides = nuclides
110- rr_tally .multiply_density = False
111- rr_tally .scores = reactions
112-
113145 flux_tally = openmc .Tally (name = 'MicroXS flux' )
114146 flux_tally .filters = [domain_filter , energy_filter ]
115147 flux_tally .scores = ['flux' ]
116- model .tallies = openmc .Tallies ([rr_tally , flux_tally ])
148+ model .tallies = [flux_tally ]
149+
150+ if reaction_rate_mode == 'direct' :
151+ rr_tally = openmc .Tally (name = 'MicroXS RR' )
152+ rr_tally .filters = [domain_filter , energy_filter ]
153+ rr_tally .nuclides = nuclides
154+ rr_tally .multiply_density = False
155+ rr_tally .scores = reactions
156+ model .tallies .append (rr_tally )
117157
118158 if openmc .lib .is_initialized :
119159 openmc .lib .finalize ()
@@ -134,33 +174,55 @@ def get_microxs_and_flux(
134174 statepoint_path = model .run (** run_kwargs )
135175
136176 if comm .rank == 0 :
177+ # Move the statepoint file if it is being saved to a specific path
178+ if path_statepoint is not None :
179+ shutil .move (statepoint_path , path_statepoint )
180+ statepoint_path = path_statepoint
181+
137182 with StatePoint (statepoint_path ) as sp :
138- rr_tally = sp .tallies [rr_tally .id ]
139- rr_tally ._read_results ()
183+ if reaction_rate_mode == 'direct' :
184+ rr_tally = sp .tallies [rr_tally .id ]
185+ rr_tally ._read_results ()
140186 flux_tally = sp .tallies [flux_tally .id ]
141187 flux_tally ._read_results ()
142188
143- rr_tally = comm . bcast ( rr_tally )
189+ # Get flux values and make energy groups last dimension
144190 flux_tally = comm .bcast (flux_tally )
145- # Get reaction rates and flux values
146- reaction_rates = rr_tally .get_reshaped_data () # (domains, groups, nuclides, reactions)
147191 flux = flux_tally .get_reshaped_data () # (domains, groups, 1, 1)
148-
149- # Make energy groups last dimension
150- reaction_rates = np .moveaxis (reaction_rates , 1 , - 1 ) # (domains, nuclides, reactions, groups)
151192 flux = np .moveaxis (flux , 1 , - 1 ) # (domains, 1, 1, groups)
152193
153- # Divide RR by flux to get microscopic cross sections
154- xs = np .empty_like (reaction_rates ) # (domains, nuclides, reactions, groups)
155- d , _ , _ , g = np .nonzero (flux )
156- xs [d , ..., g ] = reaction_rates [d , ..., g ] / flux [d , :, :, g ]
194+ # Create list where each item corresponds to one domain
195+ fluxes = list (flux .squeeze ((1 , 2 )))
196+
197+ if reaction_rate_mode == 'direct' :
198+ # Get reaction rates
199+ rr_tally = comm .bcast (rr_tally )
200+ reaction_rates = rr_tally .get_reshaped_data () # (domains, groups, nuclides, reactions)
201+
202+ # Make energy groups last dimension
203+ reaction_rates = np .moveaxis (reaction_rates , 1 , - 1 ) # (domains, nuclides, reactions, groups)
204+
205+ # Divide RR by flux to get microscopic cross sections. The indexing
206+ # ensures that only non-zero flux values are used, and broadcasting is
207+ # applied to align the shapes of reaction_rates and flux for division.
208+ xs = np .empty_like (reaction_rates ) # (domains, nuclides, reactions, groups)
209+ d , _ , _ , g = np .nonzero (flux )
210+ xs [d , ..., g ] = reaction_rates [d , ..., g ] / flux [d , :, :, g ]
211+
212+ # Create lists where each item corresponds to one domain
213+ micros = [MicroXS (xs_i , nuclides , reactions ) for xs_i in xs ]
214+ else :
215+ micros = [MicroXS .from_multigroup_flux (
216+ energies = energies ,
217+ multigroup_flux = flux_i ,
218+ chain_file = chain_file ,
219+ nuclides = nuclides ,
220+ reactions = reactions
221+ ) for flux_i in fluxes ]
157222
158223 # Reset tallies
159224 model .tallies = original_tallies
160225
161- # Create lists where each item corresponds to one domain
162- fluxes = list (flux .squeeze ((1 , 2 )))
163- micros = [MicroXS (xs_i , nuclides , reactions ) for xs_i in xs ]
164226 return fluxes , micros
165227
166228
@@ -230,7 +292,7 @@ def from_multigroup_flux(
230292 ----------
231293 energies : iterable of float or str
232294 Energy group boundaries in [eV] or the name of the group structure
233- multi_group_flux : iterable of float
295+ multigroup_flux : iterable of float
234296 Energy-dependent multigroup flux values
235297 chain_file : PathLike or Chain, optional
236298 Path to the depletion chain XML file or an instance of
0 commit comments