2024-10-29 18:05:40 +01:00
|
|
|
from __future__ import annotations
|
2016-11-21 20:30:54 -05:00
|
|
|
from itertools import chain
|
2020-04-06 15:16:09 -05:00
|
|
|
from numbers import Integral, Real
|
2024-10-29 18:05:40 +01:00
|
|
|
from typing import Dict, Iterable, List
|
2016-11-11 21:58:31 -05:00
|
|
|
|
|
|
|
|
import numpy as np
|
|
|
|
|
|
|
|
|
|
import openmc.checkvalue as cv
|
|
|
|
|
import openmc.data
|
|
|
|
|
|
2016-11-13 21:48:18 -05:00
|
|
|
# Supported keywords for continuous-energy cross section plotting
|
2024-06-19 15:48:34 +01:00
|
|
|
PLOT_TYPES = {'total', 'scatter', 'elastic', 'inelastic', 'fission',
|
2016-11-11 21:58:31 -05:00
|
|
|
'absorption', 'capture', 'nu-fission', 'nu-scatter', 'unity',
|
2024-06-19 15:48:34 +01:00
|
|
|
'slowing-down power', 'damage'}
|
2016-11-19 06:21:39 -05:00
|
|
|
|
2019-12-05 22:22:43 -06:00
|
|
|
# Supported keywords for multi-group cross section plotting
|
2024-06-19 15:48:34 +01:00
|
|
|
PLOT_TYPES_MGXS = {'total', 'absorption', 'scatter', 'fission',
|
2016-11-20 16:11:04 -05:00
|
|
|
'kappa-fission', 'nu-fission', 'prompt-nu-fission',
|
|
|
|
|
'deleyed-nu-fission', 'chi', 'chi-prompt', 'chi-delayed',
|
2024-06-19 15:48:34 +01:00
|
|
|
'inverse-velocity', 'beta', 'decay-rate', 'unity'}
|
2016-11-20 16:11:04 -05:00
|
|
|
# Create a dictionary which can be used to convert PLOT_TYPES_MGXS to the
|
|
|
|
|
# openmc.XSdata attribute name needed to access the data
|
2016-11-29 19:29:41 -05:00
|
|
|
_PLOT_MGXS_ATTR = {line: line.replace(' ', '_').replace('-', '_')
|
2016-12-11 13:10:07 -05:00
|
|
|
for line in PLOT_TYPES_MGXS}
|
2016-11-29 19:29:41 -05:00
|
|
|
_PLOT_MGXS_ATTR['scatter'] = 'scatter_matrix'
|
2016-11-11 21:58:31 -05:00
|
|
|
|
|
|
|
|
# Special MT values
|
|
|
|
|
UNITY_MT = -1
|
|
|
|
|
XI_MT = -2
|
|
|
|
|
|
|
|
|
|
# MTs to combine to generate associated plot_types
|
2016-11-18 19:55:24 -05:00
|
|
|
_INELASTIC = [mt for mt in openmc.data.SUM_RULES[3] if mt != 27]
|
2020-04-01 12:46:17 -05:00
|
|
|
PLOT_TYPES_MT = {
|
|
|
|
|
'total': openmc.data.SUM_RULES[1],
|
|
|
|
|
'scatter': [2] + _INELASTIC,
|
|
|
|
|
'elastic': [2],
|
|
|
|
|
'inelastic': _INELASTIC,
|
|
|
|
|
'fission': [18],
|
|
|
|
|
'absorption': [27],
|
|
|
|
|
'capture': [101],
|
|
|
|
|
'nu-fission': [18],
|
|
|
|
|
'nu-scatter': [2] + _INELASTIC,
|
|
|
|
|
'unity': [UNITY_MT],
|
2020-05-19 11:59:47 -04:00
|
|
|
'slowing-down power': [2] + [XI_MT],
|
2020-04-01 12:46:17 -05:00
|
|
|
'damage': [444]
|
|
|
|
|
}
|
2016-11-11 21:58:31 -05:00
|
|
|
|
|
|
|
|
# Types of plots to plot linearly in y
|
|
|
|
|
PLOT_TYPES_LINEAR = {'nu-fission / fission', 'nu-scatter / scatter',
|
|
|
|
|
'nu-fission / absorption', 'fission / absorption'}
|
|
|
|
|
|
2016-11-30 19:55:59 -05:00
|
|
|
# Minimum and maximum energies for plotting (units of eV)
|
2016-12-11 13:10:07 -05:00
|
|
|
_MIN_E = 1.e-5
|
|
|
|
|
_MAX_E = 20.e6
|
2016-11-30 19:55:59 -05:00
|
|
|
|
2016-11-11 21:58:31 -05:00
|
|
|
|
2023-03-21 21:51:40 +00:00
|
|
|
ELEMENT_NAMES = list(openmc.data.ELEMENT_SYMBOL.values())[1:]
|
|
|
|
|
|
|
|
|
|
|
2023-04-28 04:26:21 +01:00
|
|
|
def _get_legend_label(this, type):
|
|
|
|
|
"""Gets a label for the element or nuclide or material and reaction plotted"""
|
|
|
|
|
if isinstance(this, str):
|
2024-02-17 18:21:52 +00:00
|
|
|
if type in openmc.data.DADZ:
|
2024-06-10 18:18:04 +01:00
|
|
|
if this in ELEMENT_NAMES:
|
|
|
|
|
return f'{this} {type}'
|
|
|
|
|
else: # this is a nuclide so the legend can contain more information
|
|
|
|
|
z, a, m = openmc.data.zam(this)
|
|
|
|
|
da, dz = openmc.data.DADZ[type]
|
|
|
|
|
gnds_name = openmc.data.gnds_name(z + dz, a + da, m)
|
|
|
|
|
# makes a string with nuclide reaction and new nuclide
|
|
|
|
|
# For example "Be9 (n,2n) Be8"
|
|
|
|
|
return f'{this} {type} {gnds_name}'
|
2023-04-28 04:26:21 +01:00
|
|
|
return f'{this} {type}'
|
2023-05-03 09:52:40 -05:00
|
|
|
elif this.name == '':
|
2023-04-28 04:26:21 +01:00
|
|
|
return f'Material {this.id} {type}'
|
|
|
|
|
else:
|
|
|
|
|
return f'{this.name} {type}'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _get_yaxis_label(reactions, divisor_types):
|
|
|
|
|
"""Gets a y axis label for the type of data plotted"""
|
|
|
|
|
|
2024-04-05 10:37:46 +01:00
|
|
|
heat_values = {"heating", "heating-local", "damage-energy"}
|
|
|
|
|
|
|
|
|
|
# if all the types are heating a different stem and unit is needed
|
|
|
|
|
if all(set(value).issubset(heat_values) for value in reactions.values()):
|
|
|
|
|
stem = "Heating"
|
|
|
|
|
elif all(isinstance(item, str) for item in reactions.keys()):
|
|
|
|
|
for nuc_reactions in reactions.values():
|
|
|
|
|
for reaction in nuc_reactions:
|
|
|
|
|
if reaction in heat_values:
|
|
|
|
|
raise TypeError(
|
|
|
|
|
"Mixture of heating and Microscopic reactions. "
|
|
|
|
|
"Invalid type for plotting"
|
|
|
|
|
)
|
|
|
|
|
stem = "Microscopic"
|
2023-04-28 04:26:21 +01:00
|
|
|
elif all(isinstance(item, openmc.Material) for item in reactions.keys()):
|
|
|
|
|
stem = 'Macroscopic'
|
|
|
|
|
else:
|
|
|
|
|
msg = "Mixture of openmc.Material and elements/nuclides. Invalid type for plotting"
|
|
|
|
|
raise TypeError(msg)
|
|
|
|
|
|
2024-04-05 10:37:46 +01:00
|
|
|
if divisor_types:
|
|
|
|
|
mid, units = "Data", ""
|
|
|
|
|
else:
|
|
|
|
|
mid = "Cross Section"
|
|
|
|
|
units = {
|
|
|
|
|
"Macroscopic": "[1/cm]",
|
|
|
|
|
"Microscopic": "[b]",
|
|
|
|
|
"Heating": "[eV-barn]",
|
|
|
|
|
}[stem]
|
2023-04-28 04:26:21 +01:00
|
|
|
|
2024-04-05 10:37:46 +01:00
|
|
|
return f'{stem} {mid} {units}'
|
2023-04-28 04:26:21 +01:00
|
|
|
|
|
|
|
|
def _get_title(reactions):
|
|
|
|
|
"""Gets a title for the type of data plotted"""
|
|
|
|
|
if len(reactions) == 1:
|
|
|
|
|
this, = reactions
|
|
|
|
|
name = this.name if isinstance(this, openmc.Material) else this
|
|
|
|
|
return f'Cross Section Plot For {name}'
|
|
|
|
|
else:
|
|
|
|
|
return 'Cross Section Plot'
|
|
|
|
|
|
|
|
|
|
|
2024-10-29 18:05:40 +01:00
|
|
|
def plot_xs(
|
2024-11-06 05:59:25 -06:00
|
|
|
reactions: Dict[str | openmc.Material, List[str]],
|
2024-10-29 18:05:40 +01:00
|
|
|
divisor_types: Iterable[str] | None = None,
|
|
|
|
|
temperature: float = 294.0,
|
|
|
|
|
axis: "plt.Axes" | None = None,
|
|
|
|
|
sab_name: str | None = None,
|
|
|
|
|
ce_cross_sections: str | None = None,
|
|
|
|
|
mg_cross_sections: str | None = None,
|
|
|
|
|
enrichment: float | None = None,
|
|
|
|
|
plot_CE: bool = True,
|
|
|
|
|
orders: Iterable[int] | None = None,
|
|
|
|
|
divisor_orders: Iterable[int] | None = None,
|
|
|
|
|
energy_axis_units: str = "eV",
|
|
|
|
|
**kwargs,
|
2024-11-06 05:59:25 -06:00
|
|
|
) -> "plt.Figure" | None:
|
2016-12-02 18:57:09 -05:00
|
|
|
"""Creates a figure of continuous-energy cross sections for this item.
|
2016-11-11 21:58:31 -05:00
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
|
----------
|
2023-04-28 04:26:21 +01:00
|
|
|
reactions : dict
|
|
|
|
|
keys can be either a nuclide or element in string form or an
|
2024-10-29 18:05:40 +01:00
|
|
|
openmc.Material object. Values are a list of the types of
|
|
|
|
|
cross sections to include in the plot.
|
2016-11-11 21:58:31 -05:00
|
|
|
divisor_types : Iterable of values of PLOT_TYPES, optional
|
|
|
|
|
Cross section types which will divide those produced by types
|
|
|
|
|
before plotting. A type of 'unity' can be used to effectively not
|
|
|
|
|
divide some types.
|
|
|
|
|
temperature : float, optional
|
|
|
|
|
Temperature in Kelvin to plot. If not specified, a default
|
|
|
|
|
temperature of 294K will be plotted. Note that the nearest
|
|
|
|
|
temperature in the library for each nuclide will be used as opposed
|
|
|
|
|
to using any interpolation.
|
|
|
|
|
axis : matplotlib.axes, optional
|
|
|
|
|
A previously generated axis to use for plotting. If not specified,
|
|
|
|
|
a new axis and figure will be generated.
|
|
|
|
|
sab_name : str, optional
|
2023-03-21 21:51:40 +00:00
|
|
|
Name of S(a,b) library to apply to MT=2 data when applicable.
|
2016-11-20 16:11:04 -05:00
|
|
|
ce_cross_sections : str, optional
|
2016-11-11 21:58:31 -05:00
|
|
|
Location of cross_sections.xml file. Default is None.
|
2016-11-20 16:11:04 -05:00
|
|
|
mg_cross_sections : str, optional
|
2016-11-14 20:46:24 -05:00
|
|
|
Location of MGXS HDF5 Library file. Default is None.
|
|
|
|
|
enrichment : float, optional
|
|
|
|
|
Enrichment for U235 in weight percent. For example, input 4.95 for
|
2023-03-21 21:51:40 +00:00
|
|
|
4.95 weight percent enriched U. Default is None.
|
2016-11-30 20:16:05 -05:00
|
|
|
plot_CE : bool, optional
|
2016-11-20 16:11:04 -05:00
|
|
|
Denotes whether or not continuous-energy will be plotted. Defaults to
|
|
|
|
|
plotting the continuous-energy data.
|
2016-11-30 20:16:05 -05:00
|
|
|
orders : Iterable of Integral, optional
|
2016-11-20 16:11:04 -05:00
|
|
|
The scattering order or delayed group index to use for the
|
|
|
|
|
corresponding entry in types. Defaults to the 0th order for scattering
|
|
|
|
|
and the total delayed neutron data. This only applies to plots of
|
|
|
|
|
multi-group data.
|
2016-11-30 20:16:05 -05:00
|
|
|
divisor_orders : Iterable of Integral, optional
|
2016-11-20 16:11:04 -05:00
|
|
|
Same as orders, but for divisor_types
|
2023-03-22 13:32:38 +00:00
|
|
|
**kwargs :
|
2016-11-14 20:46:24 -05:00
|
|
|
All keyword arguments are passed to
|
|
|
|
|
:func:`matplotlib.pyplot.figure`.
|
2024-02-29 01:31:54 +00:00
|
|
|
energy_axis_units : {'eV', 'keV', 'MeV'}
|
|
|
|
|
Units used on the plot energy axis
|
|
|
|
|
|
2024-06-21 20:28:56 -05:00
|
|
|
.. versionadded:: 0.15.0
|
2016-11-14 20:46:24 -05:00
|
|
|
|
|
|
|
|
Returns
|
|
|
|
|
-------
|
|
|
|
|
fig : matplotlib.figure.Figure
|
|
|
|
|
If axis is None, then a Matplotlib Figure of the generated
|
|
|
|
|
cross section will be returned. Otherwise, a value of
|
|
|
|
|
None will be returned as the figure and axes have already been
|
|
|
|
|
generated.
|
|
|
|
|
|
|
|
|
|
"""
|
2018-03-02 12:09:27 -06:00
|
|
|
import matplotlib.pyplot as plt
|
|
|
|
|
|
2016-11-20 16:11:04 -05:00
|
|
|
cv.check_type("plot_CE", plot_CE, bool)
|
2024-02-29 01:31:54 +00:00
|
|
|
cv.check_value("energy_axis_units", energy_axis_units, {"eV", "keV", "MeV"})
|
|
|
|
|
|
|
|
|
|
axis_scaling_factor = {"eV": 1.0, "keV": 1e-3, "MeV": 1e-6}
|
2016-11-14 20:46:24 -05:00
|
|
|
|
|
|
|
|
# Generate the plot
|
|
|
|
|
if axis is None:
|
2023-03-22 13:32:38 +00:00
|
|
|
fig, ax = plt.subplots(**kwargs)
|
2016-11-14 20:46:24 -05:00
|
|
|
else:
|
|
|
|
|
fig = None
|
|
|
|
|
ax = axis
|
2023-04-28 04:26:21 +01:00
|
|
|
|
|
|
|
|
all_types = []
|
|
|
|
|
|
|
|
|
|
for this, types in reactions.items():
|
|
|
|
|
all_types = all_types + types
|
|
|
|
|
|
|
|
|
|
if plot_CE:
|
|
|
|
|
cv.check_type("this", this, (str, openmc.Material))
|
|
|
|
|
# Calculate for the CE cross sections
|
|
|
|
|
E, data = calculate_cexs(this, types, temperature, sab_name,
|
|
|
|
|
ce_cross_sections, enrichment)
|
|
|
|
|
if divisor_types:
|
|
|
|
|
cv.check_length('divisor types', divisor_types, len(types))
|
|
|
|
|
Ediv, data_div = calculate_cexs(this, divisor_types, temperature,
|
|
|
|
|
sab_name, ce_cross_sections,
|
|
|
|
|
enrichment)
|
|
|
|
|
|
|
|
|
|
# Create a new union grid, interpolate data and data_div on to that
|
|
|
|
|
# grid, and then do the actual division
|
|
|
|
|
Enum = E[:]
|
|
|
|
|
E = np.union1d(Enum, Ediv)
|
|
|
|
|
data_new = np.zeros((len(types), len(E)))
|
|
|
|
|
|
|
|
|
|
for line in range(len(types)):
|
|
|
|
|
data_new[line, :] = \
|
|
|
|
|
np.divide(np.interp(E, Enum, data[line, :]),
|
|
|
|
|
np.interp(E, Ediv, data_div[line, :]))
|
|
|
|
|
if divisor_types[line] != 'unity':
|
|
|
|
|
types[line] = types[line] + ' / ' + divisor_types[line]
|
|
|
|
|
data = data_new
|
|
|
|
|
else:
|
|
|
|
|
# Calculate for MG cross sections
|
|
|
|
|
E, data = calculate_mgxs(this, types, orders, temperature,
|
|
|
|
|
mg_cross_sections, ce_cross_sections,
|
|
|
|
|
enrichment)
|
|
|
|
|
|
|
|
|
|
if divisor_types:
|
|
|
|
|
cv.check_length('divisor types', divisor_types, len(types))
|
|
|
|
|
Ediv, data_div = calculate_mgxs(this, divisor_types,
|
|
|
|
|
divisor_orders, temperature,
|
|
|
|
|
mg_cross_sections,
|
|
|
|
|
ce_cross_sections, enrichment)
|
|
|
|
|
|
|
|
|
|
# Perform the division
|
|
|
|
|
for line in range(len(types)):
|
|
|
|
|
data[line, :] /= data_div[line, :]
|
|
|
|
|
if divisor_types[line] != 'unity':
|
|
|
|
|
types[line] += ' / ' + divisor_types[line]
|
|
|
|
|
|
2024-02-29 01:31:54 +00:00
|
|
|
E *= axis_scaling_factor[energy_axis_units]
|
|
|
|
|
|
2023-04-28 04:26:21 +01:00
|
|
|
# Plot the data
|
|
|
|
|
for i in range(len(data)):
|
|
|
|
|
data[i, :] = np.nan_to_num(data[i, :])
|
|
|
|
|
if np.sum(data[i, :]) > 0.:
|
|
|
|
|
ax.plot(E, data[i, :], label=_get_legend_label(this, types[i]))
|
|
|
|
|
|
2016-11-14 20:46:24 -05:00
|
|
|
# Set to loglog or semilogx depending on if we are plotting a data
|
|
|
|
|
# type which we expect to vary linearly
|
2023-04-28 04:26:21 +01:00
|
|
|
if set(all_types).issubset(PLOT_TYPES_LINEAR):
|
|
|
|
|
ax.set_xscale('log')
|
|
|
|
|
ax.set_yscale('linear')
|
2016-11-14 20:46:24 -05:00
|
|
|
else:
|
2023-04-28 04:26:21 +01:00
|
|
|
ax.set_xscale('log')
|
|
|
|
|
ax.set_yscale('log')
|
2016-11-14 20:46:24 -05:00
|
|
|
|
2024-02-29 01:31:54 +00:00
|
|
|
ax.set_xlabel(f"Energy [{energy_axis_units}]")
|
2016-11-20 20:13:40 -05:00
|
|
|
if plot_CE:
|
2024-02-29 01:31:54 +00:00
|
|
|
ax.set_xlim(
|
|
|
|
|
_MIN_E * axis_scaling_factor[energy_axis_units],
|
|
|
|
|
_MAX_E * axis_scaling_factor[energy_axis_units],
|
|
|
|
|
)
|
2016-11-20 20:13:40 -05:00
|
|
|
else:
|
|
|
|
|
ax.set_xlim(E[-1], E[0])
|
2023-03-06 20:36:34 +00:00
|
|
|
|
2023-04-28 04:26:21 +01:00
|
|
|
ax.set_ylabel(_get_yaxis_label(reactions, divisor_types))
|
2016-11-14 20:46:24 -05:00
|
|
|
ax.legend(loc='best')
|
2023-04-28 04:26:21 +01:00
|
|
|
ax.set_title(_get_title(reactions))
|
2016-11-11 21:58:31 -05:00
|
|
|
|
|
|
|
|
return fig
|
|
|
|
|
|
|
|
|
|
|
2023-03-06 20:36:34 +00:00
|
|
|
def calculate_cexs(this, types, temperature=294., sab_name=None,
|
2023-03-22 12:44:56 -03:00
|
|
|
cross_sections=None, enrichment=None, ncrystal_cfg=None):
|
2016-12-02 18:57:09 -05:00
|
|
|
"""Calculates continuous-energy cross sections of a requested type.
|
2016-11-11 21:58:31 -05:00
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
|
----------
|
2023-03-22 13:11:33 +00:00
|
|
|
this : str or openmc.Material
|
|
|
|
|
Object to source data from. Nuclides and elements should be input as a
|
2023-03-21 21:51:40 +00:00
|
|
|
str
|
2016-11-11 21:58:31 -05:00
|
|
|
types : Iterable of values of PLOT_TYPES
|
|
|
|
|
The type of cross sections to calculate
|
|
|
|
|
temperature : float, optional
|
|
|
|
|
Temperature in Kelvin to plot. If not specified, a default
|
|
|
|
|
temperature of 294K will be plotted. Note that the nearest
|
|
|
|
|
temperature in the library for each nuclide will be used as opposed
|
|
|
|
|
to using any interpolation.
|
|
|
|
|
sab_name : str, optional
|
|
|
|
|
Name of S(a,b) library to apply to MT=2 data when applicable.
|
|
|
|
|
cross_sections : str, optional
|
|
|
|
|
Location of cross_sections.xml file. Default is None.
|
|
|
|
|
enrichment : float, optional
|
|
|
|
|
Enrichment for U235 in weight percent. For example, input 4.95 for
|
|
|
|
|
4.95 weight percent enriched U. Default is None
|
|
|
|
|
(natural composition).
|
2023-03-29 16:56:12 -03:00
|
|
|
ncrystal_cfg : str, optional
|
|
|
|
|
Configuration string for NCrystal material.
|
2016-11-11 21:58:31 -05:00
|
|
|
|
|
|
|
|
Returns
|
|
|
|
|
-------
|
2016-11-16 21:47:07 -05:00
|
|
|
energy_grid : numpy.ndarray
|
2016-11-11 21:58:31 -05:00
|
|
|
Energies at which cross sections are calculated, in units of eV
|
|
|
|
|
data : numpy.ndarray
|
|
|
|
|
Cross sections calculated at the energy grid described by energy_grid
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# Check types
|
2023-03-21 21:51:40 +00:00
|
|
|
cv.check_type('this', this, (str, openmc.Material))
|
2016-11-11 21:58:31 -05:00
|
|
|
cv.check_type('temperature', temperature, Real)
|
|
|
|
|
if sab_name:
|
2017-12-24 16:06:05 +07:00
|
|
|
cv.check_type('sab_name', sab_name, str)
|
2016-11-11 21:58:31 -05:00
|
|
|
if enrichment:
|
|
|
|
|
cv.check_type('enrichment', enrichment, Real)
|
|
|
|
|
|
2023-03-06 20:36:34 +00:00
|
|
|
if isinstance(this, str):
|
2023-03-21 21:51:40 +00:00
|
|
|
if this in ELEMENT_NAMES:
|
|
|
|
|
energy_grid, data = _calculate_cexs_elem_mat(
|
|
|
|
|
this, types, temperature, cross_sections, sab_name, enrichment
|
|
|
|
|
)
|
2018-04-27 19:45:38 -04:00
|
|
|
else:
|
2023-03-21 21:51:40 +00:00
|
|
|
energy_grid, xs = _calculate_cexs_nuclide(
|
2023-03-29 16:53:37 -03:00
|
|
|
this, types, temperature, sab_name, cross_sections,
|
|
|
|
|
ncrystal_cfg
|
2023-03-21 21:51:40 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Convert xs (Iterable of Callable) to a grid of cross section values
|
|
|
|
|
# calculated on the points in energy_grid for consistency with the
|
|
|
|
|
# element and material functions.
|
|
|
|
|
data = np.zeros((len(types), len(energy_grid)))
|
|
|
|
|
for line in range(len(types)):
|
|
|
|
|
data[line, :] = xs[line](energy_grid)
|
2023-03-22 13:32:38 +00:00
|
|
|
else:
|
2016-11-20 16:11:04 -05:00
|
|
|
energy_grid, data = _calculate_cexs_elem_mat(this, types, temperature,
|
|
|
|
|
cross_sections)
|
2016-11-11 21:58:31 -05:00
|
|
|
|
|
|
|
|
return energy_grid, data
|
|
|
|
|
|
|
|
|
|
|
2016-11-20 16:11:04 -05:00
|
|
|
def _calculate_cexs_nuclide(this, types, temperature=294., sab_name=None,
|
2023-03-22 12:44:56 -03:00
|
|
|
cross_sections=None, ncrystal_cfg=None):
|
2016-12-02 18:57:09 -05:00
|
|
|
"""Calculates continuous-energy cross sections of a requested type.
|
2016-11-11 21:58:31 -05:00
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
|
----------
|
2023-03-21 21:51:40 +00:00
|
|
|
this : str
|
2016-11-11 21:58:31 -05:00
|
|
|
Nuclide object to source data from
|
|
|
|
|
types : Iterable of str or Integral
|
|
|
|
|
The type of cross sections to calculate; values can either be those
|
2020-10-19 13:33:02 +01:00
|
|
|
in openmc.PLOT_TYPES or keys from openmc.data.REACTION_MT which
|
2020-10-08 17:33:05 +01:00
|
|
|
correspond to a reaction description e.g '(n,2n)' or integers which
|
|
|
|
|
correspond to reaction channel (MT) numbers.
|
2016-11-11 21:58:31 -05:00
|
|
|
temperature : float, optional
|
|
|
|
|
Temperature in Kelvin to plot. If not specified, a default
|
|
|
|
|
temperature of 294K will be plotted. Note that the nearest
|
|
|
|
|
temperature in the library for each nuclide will be used as opposed
|
|
|
|
|
to using any interpolation.
|
|
|
|
|
sab_name : str, optional
|
|
|
|
|
Name of S(a,b) library to apply to MT=2 data when applicable.
|
|
|
|
|
cross_sections : str, optional
|
|
|
|
|
Location of cross_sections.xml file. Default is None.
|
2023-03-29 16:56:12 -03:00
|
|
|
ncrystal_cfg : str, optional
|
|
|
|
|
Configuration string for NCrystal material.
|
2016-11-11 21:58:31 -05:00
|
|
|
|
|
|
|
|
Returns
|
|
|
|
|
-------
|
2016-11-16 21:47:07 -05:00
|
|
|
energy_grid : numpy.ndarray
|
2016-11-11 21:58:31 -05:00
|
|
|
Energies at which cross sections are calculated, in units of eV
|
2016-11-19 22:52:30 -05:00
|
|
|
data : Iterable of Callable
|
|
|
|
|
Requested cross section functions
|
2016-11-11 21:58:31 -05:00
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# Load the library
|
|
|
|
|
library = openmc.data.DataLibrary.from_xml(cross_sections)
|
|
|
|
|
|
|
|
|
|
# Convert temperature to format needed for access in the library
|
2021-07-29 18:25:37 +01:00
|
|
|
strT = f"{int(round(temperature))}K"
|
2016-11-11 21:58:31 -05:00
|
|
|
T = temperature
|
|
|
|
|
|
|
|
|
|
# Now we can create the data sets to be plotted
|
|
|
|
|
energy_grid = []
|
|
|
|
|
xs = []
|
2017-12-04 21:05:58 -06:00
|
|
|
lib = library.get_by_material(this)
|
2016-11-11 21:58:31 -05:00
|
|
|
if lib is not None:
|
|
|
|
|
nuc = openmc.data.IncidentNeutron.from_hdf5(lib['path'])
|
|
|
|
|
# Obtain the nearest temperature
|
|
|
|
|
if strT in nuc.temperatures:
|
|
|
|
|
nucT = strT
|
|
|
|
|
else:
|
2017-03-06 18:37:25 +01:00
|
|
|
delta_T = np.array(nuc.kTs) - T * openmc.data.K_BOLTZMANN
|
2017-03-06 10:56:10 +01:00
|
|
|
closest_index = np.argmin(np.abs(delta_T))
|
|
|
|
|
nucT = nuc.temperatures[closest_index]
|
2016-11-11 21:58:31 -05:00
|
|
|
|
|
|
|
|
# Prep S(a,b) data if needed
|
|
|
|
|
if sab_name:
|
2025-09-03 05:52:27 +02:00
|
|
|
sab = openmc.data.ThermalScattering.from_hdf5(
|
|
|
|
|
library.get_by_material(sab_name, data_type='thermal')['path'])
|
2016-11-11 21:58:31 -05:00
|
|
|
# Obtain the nearest temperature
|
|
|
|
|
if strT in sab.temperatures:
|
|
|
|
|
sabT = strT
|
|
|
|
|
else:
|
2017-03-06 18:37:25 +01:00
|
|
|
delta_T = np.array(sab.kTs) - T * openmc.data.K_BOLTZMANN
|
|
|
|
|
closest_index = np.argmin(np.abs(delta_T))
|
|
|
|
|
sabT = sab.temperatures[closest_index]
|
|
|
|
|
|
|
|
|
|
# Create an energy grid composed the S(a,b) and the nuclide's grid
|
2016-11-11 21:58:31 -05:00
|
|
|
grid = nuc.energy[nucT]
|
|
|
|
|
sab_Emax = 0.
|
|
|
|
|
sab_funcs = []
|
2020-04-01 12:46:17 -05:00
|
|
|
if sab.elastic is not None:
|
|
|
|
|
elastic = sab.elastic.xs[sabT]
|
2016-11-11 21:58:31 -05:00
|
|
|
if isinstance(elastic, openmc.data.CoherentElastic):
|
|
|
|
|
grid = np.union1d(grid, elastic.bragg_edges)
|
|
|
|
|
if elastic.bragg_edges[-1] > sab_Emax:
|
|
|
|
|
sab_Emax = elastic.bragg_edges[-1]
|
|
|
|
|
elif isinstance(elastic, openmc.data.Tabulated1D):
|
|
|
|
|
grid = np.union1d(grid, elastic.x)
|
|
|
|
|
if elastic.x[-1] > sab_Emax:
|
|
|
|
|
sab_Emax = elastic.x[-1]
|
|
|
|
|
sab_funcs.append(elastic)
|
2020-04-01 12:46:17 -05:00
|
|
|
if sab.inelastic is not None:
|
|
|
|
|
inelastic = sab.inelastic.xs[sabT]
|
2016-11-11 21:58:31 -05:00
|
|
|
grid = np.union1d(grid, inelastic.x)
|
|
|
|
|
if inelastic.x[-1] > sab_Emax:
|
|
|
|
|
sab_Emax = inelastic.x[-1]
|
|
|
|
|
sab_funcs.append(inelastic)
|
|
|
|
|
energy_grid = grid
|
|
|
|
|
else:
|
|
|
|
|
energy_grid = nuc.energy[nucT]
|
|
|
|
|
|
2020-05-19 11:59:47 -04:00
|
|
|
# Parse the types
|
|
|
|
|
mts = []
|
|
|
|
|
ops = []
|
|
|
|
|
yields = []
|
|
|
|
|
for line in types:
|
|
|
|
|
if line in PLOT_TYPES:
|
|
|
|
|
tmp_mts = [mtj for mti in PLOT_TYPES_MT[line] for mtj in
|
|
|
|
|
nuc.get_reaction_components(mti)]
|
|
|
|
|
mts.append(tmp_mts)
|
|
|
|
|
if line.startswith('nu'):
|
|
|
|
|
yields.append(True)
|
|
|
|
|
else:
|
|
|
|
|
yields.append(False)
|
|
|
|
|
if XI_MT in tmp_mts:
|
|
|
|
|
ops.append((np.add,) * (len(tmp_mts) - 2) + (np.multiply,))
|
|
|
|
|
else:
|
|
|
|
|
ops.append((np.add,) * (len(tmp_mts) - 1))
|
2020-10-19 13:33:02 +01:00
|
|
|
elif line in openmc.data.REACTION_MT:
|
|
|
|
|
mt_number = openmc.data.REACTION_MT[line]
|
2020-10-08 18:23:07 +01:00
|
|
|
cv.check_type('MT in types', mt_number, Integral)
|
|
|
|
|
cv.check_greater_than('MT in types', mt_number, 0)
|
|
|
|
|
tmp_mts = nuc.get_reaction_components(mt_number)
|
2020-10-08 17:28:52 +01:00
|
|
|
mts.append(tmp_mts)
|
|
|
|
|
ops.append((np.add,) * (len(tmp_mts) - 1))
|
|
|
|
|
yields.append(False)
|
2020-10-08 18:23:07 +01:00
|
|
|
elif isinstance(line, int):
|
2020-05-19 11:59:47 -04:00
|
|
|
# Not a built-in type, we have to parse it ourselves
|
|
|
|
|
cv.check_type('MT in types', line, Integral)
|
|
|
|
|
cv.check_greater_than('MT in types', line, 0)
|
|
|
|
|
tmp_mts = nuc.get_reaction_components(line)
|
|
|
|
|
mts.append(tmp_mts)
|
|
|
|
|
ops.append((np.add,) * (len(tmp_mts) - 1))
|
|
|
|
|
yields.append(False)
|
2020-10-08 18:23:07 +01:00
|
|
|
else:
|
|
|
|
|
raise TypeError("Invalid type", line)
|
2020-05-19 11:59:47 -04:00
|
|
|
|
2016-11-11 21:58:31 -05:00
|
|
|
for i, mt_set in enumerate(mts):
|
|
|
|
|
# Get the reaction xs data from the nuclide
|
|
|
|
|
funcs = []
|
|
|
|
|
op = ops[i]
|
2016-11-18 19:55:24 -05:00
|
|
|
for mt in mt_set:
|
2016-11-11 21:58:31 -05:00
|
|
|
if mt == 2:
|
|
|
|
|
if sab_name:
|
|
|
|
|
# Then we need to do a piece-wise function of
|
|
|
|
|
# The S(a,b) and non-thermal data
|
|
|
|
|
sab_sum = openmc.data.Sum(sab_funcs)
|
|
|
|
|
pw_funcs = openmc.data.Regions1D(
|
|
|
|
|
[sab_sum, nuc[mt].xs[nucT]],
|
|
|
|
|
[sab_Emax])
|
|
|
|
|
funcs.append(pw_funcs)
|
2023-03-22 12:44:56 -03:00
|
|
|
elif ncrystal_cfg:
|
|
|
|
|
import NCrystal
|
|
|
|
|
nc_scatter = NCrystal.createScatter(ncrystal_cfg)
|
2025-10-03 01:27:36 +02:00
|
|
|
nc_func = nc_scatter.xsect
|
2023-03-22 12:44:56 -03:00
|
|
|
nc_emax = 5 # eV # this should be obtained from NCRYSTAL_MAX_ENERGY
|
|
|
|
|
energy_grid = np.union1d(np.geomspace(min(energy_grid),
|
|
|
|
|
1.1*nc_emax,
|
|
|
|
|
1000),energy_grid) # NCrystal does not have
|
|
|
|
|
# an intrinsic energy grid
|
|
|
|
|
pw_funcs = openmc.data.Regions1D(
|
|
|
|
|
[nc_func, nuc[mt].xs[nucT]],
|
|
|
|
|
[nc_emax])
|
|
|
|
|
funcs.append(pw_funcs)
|
2016-11-11 21:58:31 -05:00
|
|
|
else:
|
|
|
|
|
funcs.append(nuc[mt].xs[nucT])
|
|
|
|
|
elif mt in nuc:
|
2016-11-18 19:55:24 -05:00
|
|
|
if yields[i]:
|
2016-11-21 20:35:06 -05:00
|
|
|
# Get the total yield first if available. This will be
|
|
|
|
|
# used primarily for fission.
|
2016-11-21 20:30:54 -05:00
|
|
|
for prod in chain(nuc[mt].products,
|
|
|
|
|
nuc[mt].derived_products):
|
2016-11-11 21:58:31 -05:00
|
|
|
if prod.particle == 'neutron' and \
|
2016-11-21 20:30:54 -05:00
|
|
|
prod.emission_mode == 'total':
|
2016-11-11 21:58:31 -05:00
|
|
|
func = openmc.data.Combination(
|
|
|
|
|
[nuc[mt].xs[nucT], prod.yield_],
|
|
|
|
|
[np.multiply])
|
|
|
|
|
funcs.append(func)
|
|
|
|
|
break
|
2016-11-16 21:47:07 -05:00
|
|
|
else:
|
2016-11-21 20:30:54 -05:00
|
|
|
# Total doesn't exist so we have to create from
|
2016-11-21 20:35:06 -05:00
|
|
|
# prompt and delayed. This is used for scatter
|
|
|
|
|
# multiplication.
|
2016-11-21 20:30:54 -05:00
|
|
|
func = None
|
|
|
|
|
for prod in chain(nuc[mt].products,
|
|
|
|
|
nuc[mt].derived_products):
|
|
|
|
|
if prod.particle == 'neutron' and \
|
|
|
|
|
prod.emission_mode != 'total':
|
|
|
|
|
if func:
|
|
|
|
|
func = openmc.data.Combination(
|
2016-11-21 20:35:06 -05:00
|
|
|
[prod.yield_, func], [np.add])
|
2016-11-21 20:30:54 -05:00
|
|
|
else:
|
2016-11-21 20:35:06 -05:00
|
|
|
func = prod.yield_
|
2016-11-21 20:30:54 -05:00
|
|
|
if func:
|
2016-11-21 20:35:06 -05:00
|
|
|
funcs.append(openmc.data.Combination(
|
|
|
|
|
[func, nuc[mt].xs[nucT]], [np.multiply]))
|
2016-11-21 20:30:54 -05:00
|
|
|
else:
|
|
|
|
|
# If func is still None, then there were no
|
|
|
|
|
# products. In that case, assume the yield is
|
|
|
|
|
# one as its not provided for some summed
|
|
|
|
|
# reactions like MT=4
|
|
|
|
|
funcs.append(nuc[mt].xs[nucT])
|
2016-11-11 21:58:31 -05:00
|
|
|
else:
|
|
|
|
|
funcs.append(nuc[mt].xs[nucT])
|
|
|
|
|
elif mt == UNITY_MT:
|
|
|
|
|
funcs.append(lambda x: 1.)
|
|
|
|
|
elif mt == XI_MT:
|
|
|
|
|
awr = nuc.atomic_weight_ratio
|
|
|
|
|
alpha = ((awr - 1.) / (awr + 1.))**2
|
|
|
|
|
xi = 1. + alpha * np.log(alpha) / (1. - alpha)
|
|
|
|
|
funcs.append(lambda x: xi)
|
|
|
|
|
else:
|
|
|
|
|
funcs.append(lambda x: 0.)
|
2020-05-26 16:07:51 -04:00
|
|
|
funcs = funcs if funcs else [lambda x: 0.]
|
2016-11-11 21:58:31 -05:00
|
|
|
xs.append(openmc.data.Combination(funcs, op))
|
|
|
|
|
else:
|
2017-12-04 21:05:58 -06:00
|
|
|
raise ValueError(this + " not in library")
|
2016-11-11 21:58:31 -05:00
|
|
|
|
|
|
|
|
return energy_grid, xs
|
|
|
|
|
|
|
|
|
|
|
2016-11-20 16:11:04 -05:00
|
|
|
def _calculate_cexs_elem_mat(this, types, temperature=294.,
|
|
|
|
|
cross_sections=None, sab_name=None,
|
|
|
|
|
enrichment=None):
|
2016-12-02 18:57:09 -05:00
|
|
|
"""Calculates continuous-energy cross sections of a requested type.
|
2016-11-11 21:58:31 -05:00
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
|
----------
|
2023-03-21 21:51:40 +00:00
|
|
|
this : openmc.Material or str
|
|
|
|
|
Object to source data from. Element can be input as str
|
2016-11-11 21:58:31 -05:00
|
|
|
types : Iterable of values of PLOT_TYPES
|
|
|
|
|
The type of cross sections to calculate
|
|
|
|
|
temperature : float, optional
|
|
|
|
|
Temperature in Kelvin to plot. If not specified, a default
|
|
|
|
|
temperature of 294K will be plotted. Note that the nearest
|
|
|
|
|
temperature in the library for each nuclide will be used as opposed
|
|
|
|
|
to using any interpolation.
|
|
|
|
|
cross_sections : str, optional
|
|
|
|
|
Location of cross_sections.xml file. Default is None.
|
2016-11-19 23:29:59 -05:00
|
|
|
sab_name : str, optional
|
|
|
|
|
Name of S(a,b) library to apply to MT=2 data when applicable.
|
|
|
|
|
enrichment : float, optional
|
|
|
|
|
Enrichment for U235 in weight percent. For example, input 4.95 for
|
|
|
|
|
4.95 weight percent enriched U. Default is None
|
|
|
|
|
(natural composition).
|
2016-11-11 21:58:31 -05:00
|
|
|
|
|
|
|
|
Returns
|
|
|
|
|
-------
|
2016-11-16 21:47:07 -05:00
|
|
|
energy_grid : numpy.ndarray
|
2016-11-11 21:58:31 -05:00
|
|
|
Energies at which cross sections are calculated, in units of eV
|
|
|
|
|
data : numpy.ndarray
|
2016-11-19 23:29:59 -05:00
|
|
|
Cross sections calculated at the energy grid described by energy_grid
|
2016-11-11 21:58:31 -05:00
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
2016-11-19 23:29:59 -05:00
|
|
|
if isinstance(this, openmc.Material):
|
|
|
|
|
if this.temperature is not None:
|
|
|
|
|
T = this.temperature
|
|
|
|
|
else:
|
|
|
|
|
T = temperature
|
2016-11-11 21:58:31 -05:00
|
|
|
else:
|
|
|
|
|
T = temperature
|
|
|
|
|
|
|
|
|
|
# Load the library
|
|
|
|
|
library = openmc.data.DataLibrary.from_xml(cross_sections)
|
|
|
|
|
|
2023-03-22 12:44:56 -03:00
|
|
|
ncrystal_cfg = None
|
2016-11-19 23:29:59 -05:00
|
|
|
if isinstance(this, openmc.Material):
|
|
|
|
|
# Expand elements in to nuclides with atomic densities
|
2022-05-23 14:20:01 -05:00
|
|
|
nuc_fractions = this.get_nuclide_atom_densities()
|
2016-11-19 23:29:59 -05:00
|
|
|
# Create a dict of [nuclide name] = nuclide object to carry forward
|
2023-03-21 21:51:40 +00:00
|
|
|
# with a common nuclides format between openmc.Material and Elements
|
2022-05-23 14:20:01 -05:00
|
|
|
nuclides = {nuclide: nuclide for nuclide in nuc_fractions}
|
2023-03-22 12:44:56 -03:00
|
|
|
# Add NCrystal cfg string if it exists
|
|
|
|
|
ncrystal_cfg = this.ncrystal_cfg
|
2016-11-19 23:29:59 -05:00
|
|
|
else:
|
|
|
|
|
# Expand elements in to nuclides with atomic densities
|
2023-03-21 21:51:40 +00:00
|
|
|
nuclides = openmc.Element(this).expand(1., 'ao', enrichment=enrichment,
|
2016-11-19 23:29:59 -05:00
|
|
|
cross_sections=cross_sections)
|
|
|
|
|
# For ease of processing split out the nuclide and its fraction
|
2017-12-04 21:05:58 -06:00
|
|
|
nuc_fractions = {nuclide[0]: nuclide[1] for nuclide in nuclides}
|
2016-11-19 23:29:59 -05:00
|
|
|
# Create a dict of [nuclide name] = nuclide object to carry forward
|
2023-03-21 21:51:40 +00:00
|
|
|
# with a common nuclides format between openmc.Material and Elements
|
2017-12-04 21:05:58 -06:00
|
|
|
nuclides = {nuclide[0]: nuclide[0] for nuclide in nuclides}
|
2016-11-11 21:58:31 -05:00
|
|
|
|
|
|
|
|
# Identify the nuclides which have S(a,b) data
|
|
|
|
|
sabs = {}
|
|
|
|
|
for nuclide in nuclides.items():
|
2016-11-19 23:29:59 -05:00
|
|
|
sabs[nuclide[0]] = None
|
|
|
|
|
if isinstance(this, openmc.Material):
|
2022-12-31 11:32:39 +07:00
|
|
|
for sab_name, _ in this._sab:
|
2016-11-19 23:29:59 -05:00
|
|
|
sab = openmc.data.ThermalScattering.from_hdf5(
|
2019-12-20 15:55:21 -05:00
|
|
|
library.get_by_material(sab_name, data_type='thermal')['path'])
|
2016-11-19 23:29:59 -05:00
|
|
|
for nuc in sab.nuclides:
|
2025-09-03 05:52:27 +02:00
|
|
|
sabs[nuc] = sab_name
|
2016-11-19 23:29:59 -05:00
|
|
|
else:
|
|
|
|
|
if sab_name:
|
2025-09-03 05:52:27 +02:00
|
|
|
sab = openmc.data.ThermalScattering.from_hdf5(
|
|
|
|
|
library.get_by_material(sab_name, data_type='thermal')['path'])
|
2016-11-19 23:29:59 -05:00
|
|
|
for nuc in sab.nuclides:
|
2025-09-03 05:52:27 +02:00
|
|
|
sabs[nuc] = sab_name
|
2016-11-11 21:58:31 -05:00
|
|
|
|
|
|
|
|
# Now we can create the data sets to be plotted
|
2016-11-19 23:29:59 -05:00
|
|
|
xs = {}
|
2016-11-11 21:58:31 -05:00
|
|
|
E = []
|
|
|
|
|
for nuclide in nuclides.items():
|
2016-11-19 23:29:59 -05:00
|
|
|
name = nuclide[0]
|
|
|
|
|
nuc = nuclide[1]
|
2025-09-03 05:52:27 +02:00
|
|
|
sab_name = sabs[name]
|
|
|
|
|
temp_E, temp_xs = calculate_cexs(nuc, types, T, sab_name, cross_sections,
|
2023-03-29 16:53:37 -03:00
|
|
|
ncrystal_cfg=ncrystal_cfg
|
|
|
|
|
)
|
2016-11-11 21:58:31 -05:00
|
|
|
E.append(temp_E)
|
2016-11-19 22:52:30 -05:00
|
|
|
# Since the energy grids are different, store the cross sections as
|
|
|
|
|
# a tabulated function so they can be calculated on any grid needed.
|
2016-11-19 23:29:59 -05:00
|
|
|
xs[name] = [openmc.data.Tabulated1D(temp_E, temp_xs[line])
|
|
|
|
|
for line in range(len(types))]
|
2016-11-11 21:58:31 -05:00
|
|
|
|
|
|
|
|
# Condense the data for every nuclide
|
|
|
|
|
# First create a union energy grid
|
|
|
|
|
energy_grid = E[0]
|
2016-11-16 21:47:07 -05:00
|
|
|
for grid in E[1:]:
|
|
|
|
|
energy_grid = np.union1d(energy_grid, grid)
|
2016-11-11 21:58:31 -05:00
|
|
|
|
|
|
|
|
# Now we can combine all the nuclidic data
|
|
|
|
|
data = np.zeros((len(types), len(energy_grid)))
|
|
|
|
|
for line in range(len(types)):
|
|
|
|
|
if types[line] == 'unity':
|
|
|
|
|
data[line, :] = 1.
|
|
|
|
|
else:
|
2016-11-19 23:29:59 -05:00
|
|
|
for nuclide in nuclides.items():
|
|
|
|
|
name = nuclide[0]
|
|
|
|
|
data[line, :] += (nuc_fractions[name] *
|
|
|
|
|
xs[name][line](energy_grid))
|
2016-11-11 21:58:31 -05:00
|
|
|
|
|
|
|
|
return energy_grid, data
|
2016-11-13 21:48:18 -05:00
|
|
|
|
|
|
|
|
|
2023-03-06 20:36:34 +00:00
|
|
|
def calculate_mgxs(this, types, orders=None, temperature=294.,
|
2016-11-20 16:11:04 -05:00
|
|
|
cross_sections=None, ce_cross_sections=None,
|
|
|
|
|
enrichment=None):
|
2017-12-04 21:05:58 -06:00
|
|
|
"""Calculates multi-group cross sections of a requested type.
|
2016-11-13 21:48:18 -05:00
|
|
|
|
2016-11-29 19:29:41 -05:00
|
|
|
If the data for the nuclide or macroscopic object in the library is
|
|
|
|
|
represented as angle-dependent data then this method will return the
|
2016-12-02 18:57:09 -05:00
|
|
|
geometric average cross section over all angles.
|
2016-11-29 19:29:41 -05:00
|
|
|
|
2016-11-13 21:48:18 -05:00
|
|
|
Parameters
|
|
|
|
|
----------
|
2017-12-04 21:05:58 -06:00
|
|
|
this : str or openmc.Material
|
2023-03-22 13:11:33 +00:00
|
|
|
Object to source data from. Nuclides and elements can be input as a str
|
2016-12-11 13:10:07 -05:00
|
|
|
types : Iterable of values of PLOT_TYPES_MGXS
|
2016-11-14 20:46:24 -05:00
|
|
|
The type of cross sections to calculate
|
2016-11-30 20:16:05 -05:00
|
|
|
orders : Iterable of Integral, optional
|
2016-11-20 16:11:04 -05:00
|
|
|
The scattering order or delayed group index to use for the
|
|
|
|
|
corresponding entry in types. Defaults to the 0th order for scattering
|
|
|
|
|
and the total delayed neutron data.
|
2016-11-14 20:46:24 -05:00
|
|
|
temperature : float, optional
|
|
|
|
|
Temperature in Kelvin to plot. If not specified, a default
|
|
|
|
|
temperature of 294K will be plotted. Note that the nearest
|
|
|
|
|
temperature in the library for each nuclide will be used as opposed
|
|
|
|
|
to using any interpolation.
|
|
|
|
|
cross_sections : str, optional
|
|
|
|
|
Location of MGXS HDF5 Library file. Default is None.
|
|
|
|
|
ce_cross_sections : str, optional
|
|
|
|
|
Location of continuous-energy cross_sections.xml file. Default is None.
|
|
|
|
|
enrichment : float, optional
|
|
|
|
|
Enrichment for U235 in weight percent. For example, input 4.95 for
|
|
|
|
|
4.95 weight percent enriched U. Default is None
|
|
|
|
|
(natural composition).
|
|
|
|
|
|
|
|
|
|
Returns
|
|
|
|
|
-------
|
2016-11-19 21:28:50 -05:00
|
|
|
energy_grid : numpy.ndarray
|
|
|
|
|
Energies at which cross sections are calculated, in units of eV
|
2016-11-14 20:46:24 -05:00
|
|
|
data : numpy.ndarray
|
2016-11-19 21:28:50 -05:00
|
|
|
Cross sections calculated at the energy grid described by energy_grid
|
2016-11-14 20:46:24 -05:00
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# Check types
|
|
|
|
|
cv.check_type('temperature', temperature, Real)
|
|
|
|
|
if enrichment:
|
|
|
|
|
cv.check_type('enrichment', enrichment, Real)
|
2017-12-24 16:06:05 +07:00
|
|
|
cv.check_iterable_type('types', types, str)
|
2016-11-14 20:46:24 -05:00
|
|
|
|
|
|
|
|
cv.check_type("cross_sections", cross_sections, str)
|
|
|
|
|
library = openmc.MGXSLibrary.from_hdf5(cross_sections)
|
|
|
|
|
|
2023-03-21 21:51:40 +00:00
|
|
|
if this in ELEMENT_NAMES or isinstance(this, openmc.Material):
|
2016-11-20 16:11:04 -05:00
|
|
|
mgxs = _calculate_mgxs_elem_mat(this, types, library, orders,
|
|
|
|
|
temperature, ce_cross_sections,
|
|
|
|
|
enrichment)
|
2023-03-21 21:51:40 +00:00
|
|
|
elif isinstance(this, str):
|
|
|
|
|
mgxs = _calculate_mgxs_nuc_macro(this, types, library, orders,
|
|
|
|
|
temperature)
|
2016-11-14 20:46:24 -05:00
|
|
|
else:
|
|
|
|
|
raise TypeError("Invalid type")
|
|
|
|
|
|
2016-11-19 21:28:50 -05:00
|
|
|
# Convert the data to the format needed
|
|
|
|
|
data = np.zeros((len(types), 2 * library.energy_groups.num_groups))
|
|
|
|
|
energy_grid = np.zeros(2 * library.energy_groups.num_groups)
|
2016-11-20 20:13:40 -05:00
|
|
|
for g in range(library.energy_groups.num_groups):
|
2016-12-02 18:57:09 -05:00
|
|
|
energy_grid[g * 2: g * 2 + 2] = \
|
|
|
|
|
library.energy_groups.group_edges[g: g + 2]
|
2016-11-20 20:13:40 -05:00
|
|
|
# Ensure the energy will show on a log-axis by replacing 0s with a
|
|
|
|
|
# sufficiently small number
|
2016-12-02 18:57:09 -05:00
|
|
|
energy_grid[0] = max(energy_grid[0], _MIN_E)
|
2016-11-20 20:13:40 -05:00
|
|
|
|
2016-11-19 21:28:50 -05:00
|
|
|
for line in range(len(types)):
|
|
|
|
|
for g in range(library.energy_groups.num_groups):
|
2017-02-25 08:17:10 -05:00
|
|
|
data[line, g * 2: g * 2 + 2] = mgxs[line, g]
|
2016-11-19 21:28:50 -05:00
|
|
|
|
2016-12-11 13:10:07 -05:00
|
|
|
return energy_grid[::-1], data
|
2016-11-14 20:46:24 -05:00
|
|
|
|
|
|
|
|
|
2016-11-20 16:11:04 -05:00
|
|
|
def _calculate_mgxs_nuc_macro(this, types, library, orders=None,
|
|
|
|
|
temperature=294.):
|
2016-11-14 20:46:24 -05:00
|
|
|
"""Determines the multi-group cross sections of a nuclide or macroscopic
|
2016-12-02 18:57:09 -05:00
|
|
|
object.
|
2016-11-14 20:46:24 -05:00
|
|
|
|
2016-11-29 19:29:41 -05:00
|
|
|
If the data for the nuclide or macroscopic object in the library is
|
|
|
|
|
represented as angle-dependent data then this method will return the
|
2016-12-02 18:57:09 -05:00
|
|
|
geometric average cross section over all angles.
|
2016-11-29 19:29:41 -05:00
|
|
|
|
2016-11-14 20:46:24 -05:00
|
|
|
Parameters
|
|
|
|
|
----------
|
2023-03-21 21:51:40 +00:00
|
|
|
this : str
|
2016-11-14 20:46:24 -05:00
|
|
|
Object to source data from
|
2016-11-13 21:48:18 -05:00
|
|
|
types : Iterable of str
|
|
|
|
|
The type of cross sections to calculate; values can either be those
|
2016-11-14 20:46:24 -05:00
|
|
|
in openmc.PLOT_TYPES_MGXS
|
|
|
|
|
library : openmc.MGXSLibrary
|
2016-11-13 21:48:18 -05:00
|
|
|
MGXS Library containing the data of interest
|
2016-11-30 20:16:05 -05:00
|
|
|
orders : Iterable of Integral, optional
|
2016-11-20 16:11:04 -05:00
|
|
|
The scattering order or delayed group index to use for the
|
|
|
|
|
corresponding entry in types. Defaults to the 0th order for scattering
|
|
|
|
|
and the total delayed neutron data.
|
2016-11-13 21:48:18 -05:00
|
|
|
temperature : float, optional
|
|
|
|
|
Temperature in Kelvin to plot. If not specified, a default
|
|
|
|
|
temperature of 294K will be plotted. Note that the nearest
|
|
|
|
|
temperature in the library for each nuclide will be used as opposed
|
|
|
|
|
to using any interpolation.
|
|
|
|
|
|
|
|
|
|
Returns
|
|
|
|
|
-------
|
|
|
|
|
data : numpy.ndarray
|
2016-11-19 21:28:50 -05:00
|
|
|
Cross sections calculated at the energy grid described by energy_grid
|
2016-11-13 21:48:18 -05:00
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
2016-11-20 16:11:04 -05:00
|
|
|
# Check the parameters and grab order/delayed groups
|
|
|
|
|
if orders:
|
|
|
|
|
cv.check_iterable_type('orders', orders, Integral,
|
|
|
|
|
min_depth=len(types), max_depth=len(types))
|
|
|
|
|
else:
|
|
|
|
|
orders = [None] * len(types)
|
|
|
|
|
for i, line in enumerate(types):
|
2016-11-13 21:48:18 -05:00
|
|
|
cv.check_type("line", line, str)
|
|
|
|
|
cv.check_value("line", line, PLOT_TYPES_MGXS)
|
2016-11-20 16:11:04 -05:00
|
|
|
if orders[i]:
|
|
|
|
|
cv.check_greater_than("order value", orders[i], 0, equality=True)
|
2016-11-13 21:48:18 -05:00
|
|
|
|
2017-12-04 21:05:58 -06:00
|
|
|
xsdata = library.get_by_name(this)
|
2016-11-13 21:48:18 -05:00
|
|
|
|
|
|
|
|
if xsdata is not None:
|
|
|
|
|
# Obtain the nearest temperature
|
2016-12-11 13:10:07 -05:00
|
|
|
t = np.abs(xsdata.temperatures - temperature).argmin()
|
2016-11-13 21:48:18 -05:00
|
|
|
|
|
|
|
|
# Get the data
|
2016-11-14 20:46:24 -05:00
|
|
|
data = np.zeros((len(types), library.energy_groups.num_groups))
|
2016-11-15 20:29:32 -05:00
|
|
|
for i, line in enumerate(types):
|
2016-11-20 16:11:04 -05:00
|
|
|
if 'fission' in line and not xsdata.fissionable:
|
|
|
|
|
continue
|
|
|
|
|
elif line == 'unity':
|
2016-11-15 20:29:32 -05:00
|
|
|
data[i, :] = 1.
|
2016-11-14 20:46:24 -05:00
|
|
|
else:
|
2016-12-02 18:57:09 -05:00
|
|
|
# Now we have to get the cross section data and properly
|
|
|
|
|
# treat it depending on the requested type.
|
|
|
|
|
# First get the data in a generic fashion
|
2016-11-29 19:29:41 -05:00
|
|
|
temp_data = getattr(xsdata, _PLOT_MGXS_ATTR[line])[t]
|
|
|
|
|
shape = temp_data.shape[:]
|
2016-12-02 18:57:09 -05:00
|
|
|
# If we have angular data, then want the geometric
|
|
|
|
|
# average over all provided angles. Since the angles are
|
|
|
|
|
# equi-distant, un-weighted averaging will suffice
|
2016-11-29 19:29:41 -05:00
|
|
|
if xsdata.representation == 'angle':
|
|
|
|
|
temp_data = np.mean(temp_data, axis=(0, 1))
|
2016-12-02 18:57:09 -05:00
|
|
|
|
|
|
|
|
# Now we can look at the shape of the data to identify how
|
|
|
|
|
# it should be modified to produce an array of values
|
|
|
|
|
# with groups.
|
2016-11-29 19:29:41 -05:00
|
|
|
if shape in (xsdata.xs_shapes["[G']"],
|
|
|
|
|
xsdata.xs_shapes["[G]"]):
|
2016-12-02 18:57:09 -05:00
|
|
|
# Then the data is already an array vs groups so copy
|
|
|
|
|
# and move along
|
2016-11-20 16:11:04 -05:00
|
|
|
data[i, :] = temp_data
|
2016-11-29 19:29:41 -05:00
|
|
|
elif shape == xsdata.xs_shapes["[G][G']"]:
|
2016-12-02 18:57:09 -05:00
|
|
|
# Sum the data over outgoing groups to create our array vs
|
|
|
|
|
# groups
|
2016-11-20 16:11:04 -05:00
|
|
|
data[i, :] = np.sum(temp_data, axis=1)
|
2016-11-29 19:29:41 -05:00
|
|
|
elif shape == xsdata.xs_shapes["[DG]"]:
|
2016-12-02 18:57:09 -05:00
|
|
|
# Then we have a constant vs groups with a value for each
|
|
|
|
|
# delayed group. The user-provided value of orders tells us
|
|
|
|
|
# which delayed group we want. If none are provided, then
|
|
|
|
|
# we sum all the delayed groups together.
|
2016-11-20 16:11:04 -05:00
|
|
|
if orders[i]:
|
2016-11-29 19:29:41 -05:00
|
|
|
if orders[i] < len(shape[0]):
|
2016-11-20 16:11:04 -05:00
|
|
|
data[i, :] = temp_data[orders[i]]
|
|
|
|
|
else:
|
|
|
|
|
data[i, :] = np.sum(temp_data[:])
|
2017-02-25 08:17:10 -05:00
|
|
|
elif shape in (xsdata.xs_shapes["[DG][G']"],
|
|
|
|
|
xsdata.xs_shapes["[DG][G]"]):
|
2016-12-02 18:57:09 -05:00
|
|
|
# Then we have an array vs groups with values for each
|
|
|
|
|
# delayed group. The user-provided value of orders tells us
|
|
|
|
|
# which delayed group we want. If none are provided, then
|
|
|
|
|
# we sum all the delayed groups together.
|
2016-11-20 16:11:04 -05:00
|
|
|
if orders[i]:
|
2017-02-25 08:17:10 -05:00
|
|
|
if orders[i] < len(shape[0]):
|
|
|
|
|
data[i, :] = temp_data[orders[i], :]
|
2016-11-20 16:11:04 -05:00
|
|
|
else:
|
2017-02-25 08:17:10 -05:00
|
|
|
data[i, :] = np.sum(temp_data[:, :], axis=0)
|
|
|
|
|
elif shape == xsdata.xs_shapes["[DG][G][G']"]:
|
2016-12-02 18:57:09 -05:00
|
|
|
# Then we have a delayed group matrix. We will first
|
|
|
|
|
# remove the outgoing group dependency
|
2017-02-25 08:17:10 -05:00
|
|
|
temp_data = np.sum(temp_data, axis=-1)
|
2016-12-02 18:57:09 -05:00
|
|
|
# And then proceed in exactly the same manner as the
|
2017-02-25 08:17:10 -05:00
|
|
|
# "[DG][G']" or "[DG][G]" shapes in the previous block.
|
2016-11-20 16:11:04 -05:00
|
|
|
if orders[i]:
|
2017-02-25 08:17:10 -05:00
|
|
|
if orders[i] < len(shape[0]):
|
|
|
|
|
data[i, :] = temp_data[orders[i], :]
|
2016-11-20 16:11:04 -05:00
|
|
|
else:
|
2017-02-25 08:17:10 -05:00
|
|
|
data[i, :] = np.sum(temp_data[:, :], axis=0)
|
2016-11-29 19:29:41 -05:00
|
|
|
elif shape == xsdata.xs_shapes["[G][G'][Order]"]:
|
2016-12-02 18:57:09 -05:00
|
|
|
# This is a scattering matrix with angular data
|
|
|
|
|
# First remove the outgoing group dependence
|
2016-11-20 16:11:04 -05:00
|
|
|
temp_data = np.sum(temp_data, axis=1)
|
2016-12-02 18:57:09 -05:00
|
|
|
# The user either provided a specific order or we resort
|
|
|
|
|
# to the default 0th order
|
2016-11-20 16:11:04 -05:00
|
|
|
if orders[i]:
|
|
|
|
|
order = orders[i]
|
|
|
|
|
else:
|
|
|
|
|
order = 0
|
2016-12-02 18:57:09 -05:00
|
|
|
# If the order is available, store the data for that order
|
|
|
|
|
# if it is not available, then the expansion coefficient
|
|
|
|
|
# is zero and thus we already have the correct value.
|
2016-11-29 19:29:41 -05:00
|
|
|
if order < shape[1]:
|
2016-11-20 16:11:04 -05:00
|
|
|
data[i, :] = temp_data[:, order]
|
2016-11-13 21:48:18 -05:00
|
|
|
else:
|
2021-07-29 18:25:37 +01:00
|
|
|
raise ValueError(f"{this} not present in provided MGXS library")
|
2016-11-13 21:48:18 -05:00
|
|
|
|
2016-11-14 20:46:24 -05:00
|
|
|
return data
|
|
|
|
|
|
|
|
|
|
|
2016-11-20 16:11:04 -05:00
|
|
|
def _calculate_mgxs_elem_mat(this, types, library, orders=None,
|
|
|
|
|
temperature=294., ce_cross_sections=None,
|
|
|
|
|
enrichment=None):
|
2016-11-14 20:46:24 -05:00
|
|
|
"""Determines the multi-group cross sections of an element or material
|
2016-12-02 18:57:09 -05:00
|
|
|
object.
|
2016-11-14 20:46:24 -05:00
|
|
|
|
2016-11-29 19:29:41 -05:00
|
|
|
If the data for the nuclide or macroscopic object in the library is
|
|
|
|
|
represented as angle-dependent data then this method will return the
|
2016-12-02 18:57:09 -05:00
|
|
|
geometric average cross section over all angles.
|
2016-11-29 19:29:41 -05:00
|
|
|
|
2016-11-14 20:46:24 -05:00
|
|
|
Parameters
|
|
|
|
|
----------
|
2023-03-21 21:51:40 +00:00
|
|
|
this : str or openmc.Material
|
|
|
|
|
Object to source data from. Elements can be input as a str
|
2016-11-14 20:46:24 -05:00
|
|
|
types : Iterable of str
|
|
|
|
|
The type of cross sections to calculate; values can either be those
|
|
|
|
|
in openmc.PLOT_TYPES_MGXS
|
|
|
|
|
library : openmc.MGXSLibrary
|
|
|
|
|
MGXS Library containing the data of interest
|
2016-11-30 20:16:05 -05:00
|
|
|
orders : Iterable of Integral, optional
|
2016-11-20 16:11:04 -05:00
|
|
|
The scattering order or delayed group index to use for the
|
|
|
|
|
corresponding entry in types. Defaults to the 0th order for scattering
|
|
|
|
|
and the total delayed neutron data.
|
2016-11-14 20:46:24 -05:00
|
|
|
temperature : float, optional
|
|
|
|
|
Temperature in Kelvin to plot. If not specified, a default
|
|
|
|
|
temperature of 294K will be plotted. Note that the nearest
|
|
|
|
|
temperature in the library for each nuclide will be used as opposed
|
|
|
|
|
to using any interpolation.
|
|
|
|
|
ce_cross_sections : str, optional
|
|
|
|
|
Location of continuous-energy cross_sections.xml file. Default is None.
|
|
|
|
|
This is used only for expanding the elements
|
|
|
|
|
enrichment : float, optional
|
|
|
|
|
Enrichment for U235 in weight percent. For example, input 4.95 for
|
|
|
|
|
4.95 weight percent enriched U. Default is None
|
|
|
|
|
(natural composition).
|
|
|
|
|
|
|
|
|
|
Returns
|
|
|
|
|
-------
|
|
|
|
|
data : numpy.ndarray
|
2016-11-19 21:28:50 -05:00
|
|
|
Cross sections calculated at the energy grid described by energy_grid
|
2016-11-14 20:46:24 -05:00
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
if isinstance(this, openmc.Material):
|
|
|
|
|
if this.temperature is not None:
|
|
|
|
|
T = this.temperature
|
|
|
|
|
else:
|
|
|
|
|
T = temperature
|
2016-11-15 20:29:32 -05:00
|
|
|
|
2022-02-10 00:20:58 +00:00
|
|
|
# Check to see if we have nuclides/elements or a macroscopic object
|
2016-11-15 20:29:32 -05:00
|
|
|
if this._macroscopic is not None:
|
|
|
|
|
# We have macroscopics
|
2022-05-23 14:20:01 -05:00
|
|
|
nuclides = {this._macroscopic: this.density}
|
2016-11-15 20:29:32 -05:00
|
|
|
else:
|
|
|
|
|
# Expand elements in to nuclides with atomic densities
|
2016-11-19 06:21:39 -05:00
|
|
|
nuclides = this.get_nuclide_atom_densities()
|
2016-11-14 20:46:24 -05:00
|
|
|
|
|
|
|
|
# For ease of processing split out nuc and nuc_density
|
2022-05-23 14:20:01 -05:00
|
|
|
nuc_fraction = list(nuclides.values())
|
2016-11-14 20:46:24 -05:00
|
|
|
else:
|
|
|
|
|
T = temperature
|
|
|
|
|
# Expand elements in to nuclides with atomic densities
|
2023-03-21 21:51:40 +00:00
|
|
|
nuclides = openmc.Element(this).expand(100., 'ao', enrichment=enrichment,
|
2016-11-14 20:46:24 -05:00
|
|
|
cross_sections=ce_cross_sections)
|
|
|
|
|
|
|
|
|
|
# For ease of processing split out nuc and nuc_fractions
|
2016-11-19 06:21:39 -05:00
|
|
|
nuc_fraction = [nuclide[1] for nuclide in nuclides]
|
2016-11-14 20:46:24 -05:00
|
|
|
|
|
|
|
|
nuc_data = []
|
2016-11-15 20:29:32 -05:00
|
|
|
for nuclide in nuclides.items():
|
2016-11-14 20:46:24 -05:00
|
|
|
nuc_data.append(_calculate_mgxs_nuc_macro(nuclide[0], types, library,
|
2016-11-20 16:11:04 -05:00
|
|
|
orders, T))
|
2016-11-14 20:46:24 -05:00
|
|
|
|
|
|
|
|
# Combine across the nuclides
|
|
|
|
|
data = np.zeros((len(types), library.energy_groups.num_groups))
|
|
|
|
|
for line in range(len(types)):
|
|
|
|
|
if types[line] == 'unity':
|
|
|
|
|
data[line, :] = 1.
|
|
|
|
|
else:
|
|
|
|
|
for n in range(len(nuclides)):
|
2016-11-19 06:21:39 -05:00
|
|
|
data[line, :] += nuc_fraction[n] * nuc_data[n][line, :]
|
2016-11-14 20:46:24 -05:00
|
|
|
|
|
|
|
|
return data
|