Skip to content

Commit 608a1c3

Browse files
authored
Fix several issues related to independent operator depletion (#3977)
1 parent 09ee830 commit 608a1c3

3 files changed

Lines changed: 112 additions & 15 deletions

File tree

openmc/deplete/independent_operator.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,8 +339,12 @@ def get_material_rates(self, mat_index, nuc_index, react_index):
339339

340340
for i_nuc in nuc_index:
341341
nuc = self.nuc_ind_map[i_nuc]
342+
if nuc not in xs._index_nuc:
343+
continue
342344
for i_rx in react_index:
343345
rx = self.rx_ind_map[i_rx]
346+
if rx not in xs._index_rx:
347+
continue
344348

345349
# Determine reaction rate by multiplying xs in [b] by flux
346350
# in [n-cm/src] to give [(reactions/src)*b-cm/atom]

openmc/deplete/microxs.py

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,12 @@ def get_microxs_and_flux(
8484
reactions listed in the depletion chain file are used.
8585
energies : iterable of float or str
8686
Energy group boundaries in [eV] or the name of the group structure.
87-
If left as None energies will default to [0.0, 100e6]
87+
If left as None, no energy filter is applied to the flux tally. When
88+
`reaction_rate_mode` is "direct", these boundaries define the output
89+
flux and microscopic cross section energy group structure. When
90+
`reaction_rate_mode` is "flux", these boundaries define the multigroup
91+
flux tally used to collapse continuous-energy cross sections; returned
92+
fluxes and microscopic cross sections are one-group.
8893
reaction_rate_mode : {"direct", "flux"}, optional
8994
The "direct" method tallies reaction rates directly (per energy
9095
group). The "flux" method tallies a multigroup flux spectrum and then
@@ -110,7 +115,9 @@ def get_microxs_and_flux(
110115
reaction_rate_opts : dict, optional
111116
When `reaction_rate_mode="flux"`, allows selecting a subset of
112117
nuclide/reaction pairs to be computed via direct reaction-rate tallies
113-
(per energy group). Supported keys: "nuclides", "reactions".
118+
over one energy bin spanning the full `energies` range. Supported keys:
119+
"nuclides", "reactions". If "reactions" are specified without
120+
"nuclides", all selected nuclides are used.
114121
115122
Returns
116123
-------
@@ -139,10 +146,14 @@ def get_microxs_and_flux(
139146
nuclides = [nuc.name for nuc in chain.nuclides
140147
if nuc.name in nuclides_with_data]
141148

142-
# Set up the reaction rate and flux tallies
149+
# Set up the reaction rate and flux tallies. When energies are omitted, no
150+
# energy filter is needed for the transport calculation. A one-group energy
151+
# range is still needed later if flux collapse is requested.
152+
collapse_energies = energies
143153
if energies is None:
144-
energies = [0.0, 100.0e6]
145-
if isinstance(energies, str):
154+
energy_filter = None
155+
collapse_energies = [0.0, 100.0e6]
156+
elif isinstance(energies, str):
146157
energy_filter = openmc.EnergyFilter.from_group_structure(energies)
147158
else:
148159
energy_filter = openmc.EnergyFilter(energies)
@@ -172,8 +183,11 @@ def get_microxs_and_flux(
172183
rr_reactions = list(reactions)
173184
elif reaction_rate_mode == 'flux' and reaction_rate_opts:
174185
opts = reaction_rate_opts or {}
175-
rr_nuclides = list(opts.get('nuclides', []))
176186
rr_reactions = list(opts.get('reactions', []))
187+
if rr_reactions:
188+
rr_nuclides = list(opts.get('nuclides', nuclides))
189+
else:
190+
rr_nuclides = list(opts.get('nuclides', []))
177191
# Keep only requested pairs within overall sets
178192
if rr_nuclides:
179193
rr_nuclides = [n for n in rr_nuclides if n in set(nuclides)]
@@ -182,7 +196,7 @@ def get_microxs_and_flux(
182196

183197
# Use 1-group energy filter for RR in flux mode
184198
has_rr = bool(rr_nuclides and rr_reactions)
185-
if has_rr and reaction_rate_mode == 'flux':
199+
if has_rr and reaction_rate_mode == 'flux' and energy_filter is not None:
186200
rr_energy_filter = openmc.EnergyFilter(
187201
[energy_filter.values[0], energy_filter.values[-1]])
188202
else:
@@ -194,14 +208,18 @@ def get_microxs_and_flux(
194208
model.tallies = []
195209
for i, domain_filter in enumerate(domain_filters):
196210
flux_tally = openmc.Tally(name=f'MicroXS flux {i}')
197-
flux_tally.filters = [domain_filter, energy_filter]
211+
flux_tally.filters = [domain_filter]
212+
if energy_filter is not None:
213+
flux_tally.filters.append(energy_filter)
198214
flux_tally.scores = ['flux']
199215
model.tallies.append(flux_tally)
200216
flux_tallies.append(flux_tally)
201217

202218
if has_rr:
203219
rr_tally = openmc.Tally(name=f'MicroXS RR {i}')
204-
rr_tally.filters = [domain_filter, rr_energy_filter]
220+
rr_tally.filters = [domain_filter]
221+
if rr_energy_filter is not None:
222+
rr_tally.filters.append(rr_energy_filter)
205223
rr_tally.nuclides = rr_nuclides
206224
rr_tally.multiply_density = False
207225
rr_tally.scores = rr_reactions
@@ -255,8 +273,12 @@ def get_microxs_and_flux(
255273
all_flux_arrays = []
256274
for flux_tally in flux_tallies:
257275
# Get flux values and make energy groups last dimension
258-
flux = flux_tally.get_reshaped_data() # (domains, groups, 1, 1)
259-
flux = np.moveaxis(flux, 1, -1) # (domains, 1, 1, groups)
276+
flux = flux_tally.get_reshaped_data()
277+
if energy_filter is None:
278+
flux = flux[..., np.newaxis] # (domains, 1, 1, groups)
279+
else:
280+
# (domains, groups, 1, 1) -> (domains, 1, 1, groups)
281+
flux = np.moveaxis(flux, 1, -1)
260282
all_flux_arrays.append(flux)
261283
fluxes.extend(flux.squeeze((1, 2)))
262284

@@ -266,8 +288,15 @@ def get_microxs_and_flux(
266288
for flux_arr, rr_tally in zip(all_flux_arrays, rr_tallies):
267289
flux = flux_arr
268290
# Get reaction rates and make energy groups last dimension
269-
reaction_rates = rr_tally.get_reshaped_data() # (domains, groups, nuclides, reactions)
270-
reaction_rates = np.moveaxis(reaction_rates, 1, -1) # (domains, nuclides, reactions, groups)
291+
reaction_rates = rr_tally.get_reshaped_data()
292+
if rr_energy_filter is None:
293+
# (domains, nuclides, reactions) ->
294+
# (domains, nuclides, reactions, groups)
295+
reaction_rates = reaction_rates[..., np.newaxis]
296+
else:
297+
# (domains, groups, nuclides, reactions) ->
298+
# (domains, nuclides, reactions, groups)
299+
reaction_rates = np.moveaxis(reaction_rates, 1, -1)
271300

272301
# If RR is 1-group, sum flux over groups
273302
if reaction_rate_mode == "flux":
@@ -279,16 +308,20 @@ def get_microxs_and_flux(
279308
direct_micros.extend(
280309
MicroXS(xs_i, rr_nuclides, rr_reactions) for xs_i in xs)
281310

282-
# If using flux mode, compute flux-collapsed microscopic XS
283311
if reaction_rate_mode == 'flux':
312+
# Compute flux-collapsed microscopic XS
284313
flux_micros = [MicroXS.from_multigroup_flux(
285-
energies=energies,
314+
energies=collapse_energies,
286315
multigroup_flux=flux_i,
287316
chain_file=chain_file,
288317
nuclides=nuclides,
289318
reactions=reactions
290319
) for flux_i in fluxes]
291320

321+
# We need to return one-group fluxes to match the microscopic cross
322+
# sections, which are always one-group by virtue of the collapse
323+
fluxes = [flux.sum(keepdims=True) for flux in fluxes]
324+
292325
# Decide which micros to use and merge if needed
293326
if reaction_rate_mode == 'flux' and rr_tallies:
294327
micros = [m1.merge(m2) for m1, m2 in zip(flux_micros, direct_micros)]

tests/unit_tests/test_deplete_microxs.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,66 @@ def capture_run(**kwargs):
179179
assert ef.values[0] == pytest.approx(energies[0])
180180
assert ef.values[-1] == pytest.approx(energies[-1])
181181

182+
183+
def _simple_model():
184+
model = openmc.Model()
185+
mat = openmc.Material(components={'H1': 1.0, 'H2': 1.0},
186+
density=5.0, density_units='g/cm3')
187+
sphere = openmc.Sphere(r=10.0, boundary_type='vacuum')
188+
cell = openmc.Cell(region=-sphere, fill=mat)
189+
model.geometry = openmc.Geometry([cell])
190+
model.settings.particles = 100
191+
model.settings.batches = 5
192+
model.settings.run_mode = 'fixed source'
193+
return model, mat
194+
195+
196+
def test_hybrid_tally_defaults_to_all_nuclides(run_in_tmpdir):
197+
energies = [0., 0.625, 2.0e7]
198+
kwargs = {
199+
'nuclides': ['H1', 'H2'],
200+
'reactions': ['(n,2n)', '(n,gamma)'],
201+
'energies': energies,
202+
'reaction_rate_mode': 'flux',
203+
'chain_file': CHAIN_FILE,
204+
}
205+
206+
model, mat = _simple_model()
207+
default_fluxes, default_micros = get_microxs_and_flux(
208+
model, [mat], reaction_rate_opts={'reactions': ['(n,2n)']}, **kwargs
209+
)
210+
211+
model, mat = _simple_model()
212+
explicit_fluxes, explicit_micros = get_microxs_and_flux(
213+
model, [mat],
214+
reaction_rate_opts={
215+
'nuclides': ['H1', 'H2'],
216+
'reactions': ['(n,2n)']
217+
},
218+
**kwargs
219+
)
220+
221+
np.testing.assert_allclose(default_fluxes[0], explicit_fluxes[0])
222+
np.testing.assert_allclose(default_micros[0].data, explicit_micros[0].data)
223+
assert default_micros[0].nuclides == explicit_micros[0].nuclides
224+
assert default_micros[0].reactions == explicit_micros[0].reactions
225+
226+
227+
def test_flux_mode_returns_one_group_flux(run_in_tmpdir):
228+
model, mat = _simple_model()
229+
fluxes, micros = get_microxs_and_flux(
230+
model, [mat],
231+
nuclides=['H1'],
232+
reactions=['(n,2n)'],
233+
energies=[0., 0.625, 2.0e7],
234+
reaction_rate_mode='flux',
235+
chain_file=CHAIN_FILE,
236+
)
237+
238+
assert fluxes[0].shape == (1,)
239+
assert micros[0].data.shape == (1, 1, 1)
240+
assert fluxes[0][0] > 0.0
241+
182242
# ---------------------------------------------------------------------------
183243
# Tests for MicroXS.merge()
184244
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)