mirror of
https://github.com/openmc-dev/openmc.git
synced 2026-07-29 06:35:48 -04:00
Merge branch 'develop' into centre_for_cylinder_spherical_meshes
This commit is contained in:
commit
527f5f70aa
163 changed files with 14849 additions and 35970 deletions
|
|
@ -604,6 +604,10 @@ class AggregateFilter:
|
|||
def num_bins(self):
|
||||
return len(self.bins) if self.aggregate_filter else 0
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return (self.num_bins,)
|
||||
|
||||
@type.setter
|
||||
def type(self, filter_type):
|
||||
if filter_type not in _FILTER_TYPES:
|
||||
|
|
|
|||
|
|
@ -246,7 +246,7 @@ class CMFDMesh:
|
|||
Real)
|
||||
check_greater_than('CMFD mesh {}-grid length'.format(dims[i]),
|
||||
len(grid[i]), 1)
|
||||
self._grid = np.array(grid)
|
||||
self._grid = [np.array(g) for g in grid]
|
||||
self._display_mesh_warning('rectilinear', 'CMFD mesh grid')
|
||||
|
||||
def _display_mesh_warning(self, mesh_type, variable_label):
|
||||
|
|
@ -1382,9 +1382,7 @@ class CMFDRun:
|
|||
|
||||
"""
|
||||
# Write each element in vector to file
|
||||
with open(base_filename+'.dat', 'w') as fh:
|
||||
for val in vector:
|
||||
fh.write('{:0.8f}\n'.format(val))
|
||||
np.savetxt(f'{base_filename}.dat', vector, fmt='%.8f')
|
||||
|
||||
# Save as numpy format
|
||||
np.save(base_filename, vector)
|
||||
|
|
|
|||
|
|
@ -492,7 +492,7 @@ def isotopes(element):
|
|||
|
||||
# Get the nuclides present in nature
|
||||
result = []
|
||||
for kv in sorted(NATURAL_ABUNDANCE.items()):
|
||||
for kv in NATURAL_ABUNDANCE.items():
|
||||
if re.match(r'{}\d+'.format(element), kv[0]):
|
||||
result.append(kv)
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ _THERMAL_DATA = {
|
|||
'c_Li_in_FLiBe': ThermalTuple('liflib', [3006, 3007], 1),
|
||||
'c_Mg24': ThermalTuple('mg24', [12024], 1),
|
||||
'c_N_in_UN': ThermalTuple('n-un', [7014, 7015], 1),
|
||||
'c_O_in_Al2O3': ThermalTuple('osap00', [92238], 1),
|
||||
'c_O_in_Al2O3': ThermalTuple('osap00', [8016, 8017, 8018], 1),
|
||||
'c_O_in_BeO': ThermalTuple('obeo', [8016, 8017, 8018], 1),
|
||||
'c_O_in_D2O': ThermalTuple('od2o', [8016, 8017, 8018], 1),
|
||||
'c_O_in_H2O_solid': ThermalTuple('oice', [8016, 8017, 8018], 1),
|
||||
|
|
@ -64,8 +64,8 @@ _THERMAL_DATA = {
|
|||
'c_Si_in_SiC': ThermalTuple('sisic', [14028, 14029, 14030], 1),
|
||||
'c_SiO2_alpha': ThermalTuple('sio2-a', [8016, 8017, 8018, 14028, 14029, 14030], 3),
|
||||
'c_SiO2_beta': ThermalTuple('sio2-b', [8016, 8017, 8018, 14028, 14029, 14030], 3),
|
||||
'c_U_in_UN': ThermalTuple('u-un', [92238], 1),
|
||||
'c_U_in_UO2': ThermalTuple('uuo2', [8016, 8017, 8018], 1),
|
||||
'c_U_in_UN': ThermalTuple('u-un', [92233, 92234, 92235, 92236, 92238], 1),
|
||||
'c_U_in_UO2': ThermalTuple('uuo2', [92233, 92234, 92235, 92236, 92238], 1),
|
||||
'c_Y_in_YH2': ThermalTuple('yyh2', [39089], 1),
|
||||
'c_Zr_in_ZrH': ThermalTuple('zrzrh', [40000, 40090, 40091, 40092, 40094, 40096], 1),
|
||||
'c_Zr_in_ZrH2': ThermalTuple('zrzrh2', [40000, 40090, 40091, 40092, 40094, 40096], 1),
|
||||
|
|
|
|||
|
|
@ -223,8 +223,13 @@ class OpenMCOperator(TransportOperator):
|
|||
if mat.depletable:
|
||||
burnable_mats.add(str(mat.id))
|
||||
if mat.volume is None:
|
||||
raise RuntimeError("Volume not specified for depletable "
|
||||
"material with ID={}.".format(mat.id))
|
||||
if mat.name is None:
|
||||
msg = ("Volume not specified for depletable material "
|
||||
f"with ID={mat.id}.")
|
||||
else:
|
||||
msg = ("Volume not specified for depletable material "
|
||||
f"with ID={mat.id} Name={mat.name}.")
|
||||
raise RuntimeError(msg)
|
||||
volume[str(mat.id)] = mat.volume
|
||||
self.heavy_metal += mat.fissionable_mass
|
||||
|
||||
|
|
@ -242,7 +247,6 @@ class OpenMCOperator(TransportOperator):
|
|||
for nuc in model_nuclides:
|
||||
if nuc not in nuclides:
|
||||
nuclides.append(nuc)
|
||||
|
||||
return burnable_mats, volume, nuclides
|
||||
|
||||
def _load_previous_results(self):
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ USE_MULTIPROCESSING = True
|
|||
NUM_PROCESSES = None
|
||||
|
||||
|
||||
def deplete(func, chain, x, rates, dt, matrix_func=None):
|
||||
def deplete(func, chain, x, rates, dt, matrix_func=None, *matrix_args):
|
||||
"""Deplete materials using given reaction rates for a specified time
|
||||
|
||||
Parameters
|
||||
|
|
@ -37,6 +37,8 @@ def deplete(func, chain, x, rates, dt, matrix_func=None):
|
|||
``fission_yields = {parent: {product: yield_frac}}``
|
||||
Expected to return the depletion matrix required by
|
||||
``func``
|
||||
matrix_args : Any, optional
|
||||
Additional arguments passed to matrix_func
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
|
@ -57,7 +59,8 @@ def deplete(func, chain, x, rates, dt, matrix_func=None):
|
|||
if matrix_func is None:
|
||||
matrices = map(chain.form_matrix, rates, fission_yields)
|
||||
else:
|
||||
matrices = map(matrix_func, repeat(chain), rates, fission_yields)
|
||||
matrices = map(matrix_func, repeat(chain), rates, fission_yields,
|
||||
*matrix_args)
|
||||
|
||||
inputs = zip(matrices, x, repeat(dt))
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from xml.etree import ElementTree as ET
|
|||
|
||||
import openmc.checkvalue as cv
|
||||
import openmc
|
||||
from openmc.data import NATURAL_ABUNDANCE, atomic_mass, \
|
||||
from openmc.data import NATURAL_ABUNDANCE, atomic_mass, zam, \
|
||||
isotopes as natural_isotopes
|
||||
|
||||
|
||||
|
|
@ -147,8 +147,8 @@ class Element(str):
|
|||
# and sort to avoid different ordering between Python 2 and 3.
|
||||
mutual_nuclides = natural_nuclides.intersection(library_nuclides)
|
||||
absent_nuclides = natural_nuclides.difference(mutual_nuclides)
|
||||
mutual_nuclides = sorted(list(mutual_nuclides))
|
||||
absent_nuclides = sorted(list(absent_nuclides))
|
||||
mutual_nuclides = sorted(mutual_nuclides, key=zam)
|
||||
absent_nuclides = sorted(absent_nuclides, key=zam)
|
||||
|
||||
# If all naturally occurring isotopes are present in the library,
|
||||
# add them based on their abundance
|
||||
|
|
@ -195,7 +195,7 @@ class Element(str):
|
|||
# If a cross_section library is not present, expand the element into
|
||||
# its natural nuclides
|
||||
else:
|
||||
for nuclide in natural_nuclides:
|
||||
for nuclide in sorted(natural_nuclides, key=zam):
|
||||
abundances[nuclide] = NATURAL_ABUNDANCE[nuclide]
|
||||
|
||||
# Modify mole fractions if enrichment provided
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
from collections.abc import Iterable
|
||||
from numbers import Integral
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
import openmc
|
||||
|
|
@ -23,7 +24,7 @@ def _process_CLI_arguments(volume=False, geometry_debug=False, particles=None,
|
|||
Number of particles to simulate per generation.
|
||||
plot : bool, optional
|
||||
Run in plotting mode. Defaults to False.
|
||||
restart_file : str, optional
|
||||
restart_file : str or PathLike
|
||||
Path to restart file to use
|
||||
threads : int, optional
|
||||
Number of OpenMP threads. If OpenMC is compiled with OpenMP threading
|
||||
|
|
@ -42,7 +43,7 @@ def _process_CLI_arguments(volume=False, geometry_debug=False, particles=None,
|
|||
mpi_args : list of str, optional
|
||||
MPI execute command and any additional MPI arguments to pass,
|
||||
e.g., ['mpiexec', '-n', '8'].
|
||||
path_input : str or Pathlike
|
||||
path_input : str or PathLike
|
||||
Path to a single XML file or a directory containing XML files for the
|
||||
OpenMC executable to read.
|
||||
|
||||
|
|
@ -73,8 +74,8 @@ def _process_CLI_arguments(volume=False, geometry_debug=False, particles=None,
|
|||
if event_based:
|
||||
args.append('-e')
|
||||
|
||||
if isinstance(restart_file, str):
|
||||
args += ['-r', restart_file]
|
||||
if isinstance(restart_file, (str, os.PathLike)):
|
||||
args += ['-r', str(restart_file)]
|
||||
|
||||
if tracks:
|
||||
args.append('-t')
|
||||
|
|
@ -230,7 +231,7 @@ def calculate_volumes(threads=None, output=True, cwd='.',
|
|||
cwd : str, optional
|
||||
Path to working directory to run in. Defaults to the current working
|
||||
directory.
|
||||
path_input : str or Pathlike
|
||||
path_input : str or PathLike
|
||||
Path to a single XML file or a directory containing XML files for the
|
||||
OpenMC executable to read.
|
||||
|
||||
|
|
@ -270,7 +271,7 @@ def run(particles=None, threads=None, geometry_debug=False,
|
|||
:envvar:`OMP_NUM_THREADS` environment variable).
|
||||
geometry_debug : bool, optional
|
||||
Turn on geometry debugging during simulation. Defaults to False.
|
||||
restart_file : str, optional
|
||||
restart_file : str or PathLike
|
||||
Path to restart file to use
|
||||
tracks : bool, optional
|
||||
Enables the writing of particles tracks. The number of particle tracks
|
||||
|
|
@ -291,7 +292,7 @@ def run(particles=None, threads=None, geometry_debug=False,
|
|||
|
||||
.. versionadded:: 0.12
|
||||
|
||||
path_input : str or Pathlike
|
||||
path_input : str or PathLike
|
||||
Path to a single XML file or a directory containing XML files for the
|
||||
OpenMC executable to read.
|
||||
|
||||
|
|
|
|||
|
|
@ -102,6 +102,8 @@ class Filter(IDManagerMixin, metaclass=FilterMeta):
|
|||
Unique identifier for the filter
|
||||
num_bins : Integral
|
||||
The number of filter bins
|
||||
shape : tuple
|
||||
The shape of the filter
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -205,6 +207,10 @@ class Filter(IDManagerMixin, metaclass=FilterMeta):
|
|||
def num_bins(self):
|
||||
return len(self.bins)
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return (self.num_bins,)
|
||||
|
||||
def check_bins(self, bins):
|
||||
"""Make sure given bins are valid for this filter.
|
||||
|
||||
|
|
@ -252,6 +258,8 @@ class Filter(IDManagerMixin, metaclass=FilterMeta):
|
|||
|
||||
"""
|
||||
filter_type = elem.get('type')
|
||||
if filter_type is None:
|
||||
filter_type = elem.find('type').text
|
||||
|
||||
# If the filter type matches this class's short_name, then
|
||||
# there is no overridden from_xml_element method
|
||||
|
|
@ -756,7 +764,7 @@ class ParticleFilter(Filter):
|
|||
|
||||
|
||||
class MeshFilter(Filter):
|
||||
"""Bins tally event locations onto a regular, rectangular mesh.
|
||||
"""Bins tally event locations by mesh elements.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
|
@ -839,6 +847,12 @@ class MeshFilter(Filter):
|
|||
else:
|
||||
self.bins = list(mesh.indices)
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
if isinstance(self, MeshSurfaceFilter):
|
||||
return (self.num_bins,)
|
||||
return self.mesh.dimension
|
||||
|
||||
@property
|
||||
def translation(self):
|
||||
return self._translation
|
||||
|
|
@ -947,7 +961,7 @@ class MeshFilter(Filter):
|
|||
|
||||
|
||||
class MeshSurfaceFilter(MeshFilter):
|
||||
"""Filter events by surface crossings on a regular, rectangular mesh.
|
||||
"""Filter events by surface crossings on a mesh.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
|
@ -958,8 +972,6 @@ class MeshSurfaceFilter(MeshFilter):
|
|||
|
||||
Attributes
|
||||
----------
|
||||
bins : Integral
|
||||
The mesh ID
|
||||
mesh : openmc.MeshBase
|
||||
The mesh object that events will be tallied onto
|
||||
translation : Iterable of float
|
||||
|
|
@ -968,10 +980,8 @@ class MeshSurfaceFilter(MeshFilter):
|
|||
id : int
|
||||
Unique identifier for the filter
|
||||
bins : list of tuple
|
||||
|
||||
A list of mesh indices / surfaces for each filter bin, e.g. [(1, 1,
|
||||
'x-min out'), (1, 1, 'x-min in'), ...]
|
||||
|
||||
num_bins : Integral
|
||||
The number of filter bins
|
||||
|
||||
|
|
@ -1332,6 +1342,36 @@ class EnergyFilter(RealFilter):
|
|||
cv.check_greater_than('filter value', v0, 0., equality=True)
|
||||
cv.check_greater_than('filter value', v1, 0., equality=True)
|
||||
|
||||
def get_tabular(self, values, **kwargs):
|
||||
"""Create a tabulated distribution based on tally results with an energy filter
|
||||
|
||||
This method provides an easy way to create a distribution in energy
|
||||
(e.g., a source spectrum) based on tally results that were obtained from
|
||||
using an :class:`~openmc.EnergyFilter`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
values : iterable of float
|
||||
Array of numeric values, typically from a tally result
|
||||
**kwargs
|
||||
Keyword arguments passed to :class:`openmc.stats.Tabular`
|
||||
|
||||
Returns
|
||||
-------
|
||||
openmc.stats.Tabular
|
||||
Tabular distribution with histogram interpolation
|
||||
"""
|
||||
|
||||
probabilities = np.array(values, dtype=float)
|
||||
probabilities /= probabilities.sum()
|
||||
|
||||
# Determine probability per eV, adding extra 0 at the end since it is a histogram
|
||||
probability_per_ev = probabilities / np.diff(self.values)
|
||||
probability_per_ev = np.append(probability_per_ev, 0.0)
|
||||
|
||||
kwargs.setdefault('interpolation', 'histogram')
|
||||
return openmc.stats.Tabular(self.values, probability_per_ev, **kwargs)
|
||||
|
||||
@property
|
||||
def lethargy_bin_width(self):
|
||||
"""Calculates the base 10 log width of energy bins which is useful when
|
||||
|
|
@ -1703,7 +1743,7 @@ class DistribcellFilter(Filter):
|
|||
# Concatenate with DataFrame of distribcell instance IDs
|
||||
if level_df is not None:
|
||||
level_df = level_df.dropna(axis=1, how='all')
|
||||
level_df = level_df.astype(np.int)
|
||||
level_df = level_df.astype(int)
|
||||
df = pd.concat([level_df, df], axis=1)
|
||||
|
||||
return df
|
||||
|
|
|
|||
|
|
@ -278,7 +278,7 @@ class Geometry:
|
|||
|
||||
"""
|
||||
|
||||
# Using str and os.Pathlike here to avoid error when using just the imported PathLike
|
||||
# Using str and os.PathLike here to avoid error when using just the imported PathLike
|
||||
# TypeError: Subscripted generics cannot be used with class and instance checks
|
||||
check_type('materials', materials, (str, os.PathLike, openmc.Materials))
|
||||
|
||||
|
|
|
|||
|
|
@ -143,6 +143,8 @@ class Material(IDManagerMixin):
|
|||
string += '{: <16}=\t{}'.format('\tDensity', self._density)
|
||||
string += f' [{self._density_units}]\n'
|
||||
|
||||
string += '{: <16}=\t{} [cm^3]\n'.format('\tVolume', self._volume)
|
||||
|
||||
string += '{: <16}\n'.format('\tS(a,b) Tables')
|
||||
|
||||
if self._ncrystal_cfg:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from collections.abc import Iterable
|
||||
from collections import OrderedDict
|
||||
from math import pi
|
||||
from numbers import Real, Integral
|
||||
from pathlib import Path
|
||||
|
|
@ -188,7 +189,7 @@ class StructuredMesh(MeshBase):
|
|||
Returns a numpy.ndarray representing the mesh element centroid
|
||||
coordinates with a shape equal to (ndim, dim1, ..., dimn). Can be
|
||||
unpacked along the first dimension with xx, yy, zz = mesh.centroids.
|
||||
|
||||
|
||||
|
||||
"""
|
||||
ndim = self.n_dimension
|
||||
|
|
@ -1988,3 +1989,25 @@ class UnstructuredMesh(MeshBase):
|
|||
length_multiplier = float(get_text(elem, 'length_multiplier', 1.0))
|
||||
|
||||
return cls(filename, library, mesh_id, '', length_multiplier)
|
||||
|
||||
|
||||
def _read_meshes(elem):
|
||||
"""Generate dictionary of meshes from a given XML node
|
||||
|
||||
Parameters
|
||||
----------
|
||||
elem : xml.etree.ElementTree.Element
|
||||
XML element
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
A dictionary with mesh IDs as keys and openmc.MeshBase
|
||||
instanaces as values
|
||||
"""
|
||||
out = dict()
|
||||
for mesh_elem in elem.findall('mesh'):
|
||||
mesh = MeshBase.from_xml_element(mesh_elem)
|
||||
out[mesh.id] = mesh
|
||||
|
||||
return out
|
||||
|
|
@ -164,7 +164,7 @@ class EnergyGroups:
|
|||
if groups == 'all':
|
||||
return np.arange(self.num_groups)
|
||||
else:
|
||||
indices = np.zeros(len(groups), dtype=np.int)
|
||||
indices = np.zeros(len(groups), dtype=int)
|
||||
|
||||
for i, group in enumerate(groups):
|
||||
cv.check_greater_than('group', group, 0)
|
||||
|
|
|
|||
|
|
@ -586,7 +586,7 @@ class MDGXS(MGXS):
|
|||
if not isinstance(subdomains, str):
|
||||
cv.check_iterable_type('subdomains', subdomains, Integral)
|
||||
elif self.domain_type == 'distribcell':
|
||||
subdomains = np.arange(self.num_subdomains, dtype=np.int)
|
||||
subdomains = np.arange(self.num_subdomains, dtype=int)
|
||||
elif self.domain_type == 'mesh':
|
||||
xyz = [range(1, x + 1) for x in self.domain.dimension]
|
||||
subdomains = list(itertools.product(*xyz))
|
||||
|
|
@ -2473,7 +2473,7 @@ class MatrixMDGXS(MDGXS):
|
|||
if not isinstance(subdomains, str):
|
||||
cv.check_iterable_type('subdomains', subdomains, Integral)
|
||||
elif self.domain_type == 'distribcell':
|
||||
subdomains = np.arange(self.num_subdomains, dtype=np.int)
|
||||
subdomains = np.arange(self.num_subdomains, dtype=int)
|
||||
elif self.domain_type == 'mesh':
|
||||
xyz = [range(1, x + 1) for x in self.domain.dimension]
|
||||
subdomains = list(itertools.product(*xyz))
|
||||
|
|
|
|||
|
|
@ -918,7 +918,7 @@ class MGXS:
|
|||
# Sum the atomic number densities for all nuclides
|
||||
if nuclides == 'sum':
|
||||
nuclides = self.get_nuclides()
|
||||
densities = np.zeros(1, dtype=np.float)
|
||||
densities = np.zeros(1, dtype=float)
|
||||
for nuclide in nuclides:
|
||||
densities[0] += self.get_nuclide_density(nuclide)
|
||||
|
||||
|
|
@ -931,7 +931,7 @@ class MGXS:
|
|||
|
||||
# Tabulate the atomic number densities for each specified nuclide
|
||||
else:
|
||||
densities = np.zeros(len(nuclides), dtype=np.float)
|
||||
densities = np.zeros(len(nuclides), dtype=float)
|
||||
for i, nuclide in enumerate(nuclides):
|
||||
densities[i] = self.get_nuclide_density(nuclide)
|
||||
|
||||
|
|
@ -1720,7 +1720,7 @@ class MGXS:
|
|||
if not isinstance(subdomains, str):
|
||||
cv.check_iterable_type('subdomains', subdomains, Integral)
|
||||
elif self.domain_type == 'distribcell':
|
||||
subdomains = np.arange(self.num_subdomains, dtype=np.int)
|
||||
subdomains = np.arange(self.num_subdomains, dtype=int)
|
||||
elif self.domain_type == 'mesh':
|
||||
subdomains = list(self.domain.indices)
|
||||
else:
|
||||
|
|
@ -1887,7 +1887,7 @@ class MGXS:
|
|||
if not isinstance(subdomains, str):
|
||||
cv.check_iterable_type('subdomains', subdomains, Integral)
|
||||
elif self.domain_type == 'distribcell':
|
||||
subdomains = np.arange(self.num_subdomains, dtype=np.int)
|
||||
subdomains = np.arange(self.num_subdomains, dtype=int)
|
||||
elif self.domain_type == 'sum(distribcell)':
|
||||
domain_filter = self.xs_tally.find_filter('sum(distribcell)')
|
||||
subdomains = domain_filter.bins
|
||||
|
|
@ -1900,7 +1900,7 @@ class MGXS:
|
|||
if self.by_nuclide:
|
||||
if nuclides == 'all':
|
||||
nuclides = self.get_nuclides()
|
||||
densities = np.zeros(len(nuclides), dtype=np.float)
|
||||
densities = np.zeros(len(nuclides), dtype=float)
|
||||
elif nuclides == 'sum':
|
||||
nuclides = ['sum']
|
||||
else:
|
||||
|
|
@ -2447,7 +2447,7 @@ class MatrixMGXS(MGXS):
|
|||
if not isinstance(subdomains, str):
|
||||
cv.check_iterable_type('subdomains', subdomains, Integral)
|
||||
elif self.domain_type == 'distribcell':
|
||||
subdomains = np.arange(self.num_subdomains, dtype=np.int)
|
||||
subdomains = np.arange(self.num_subdomains, dtype=int)
|
||||
elif self.domain_type == 'mesh':
|
||||
subdomains = list(self.domain.indices)
|
||||
else:
|
||||
|
|
@ -4785,7 +4785,7 @@ class ScatterMatrixXS(MatrixMGXS):
|
|||
if not isinstance(subdomains, str):
|
||||
cv.check_iterable_type('subdomains', subdomains, Integral)
|
||||
elif self.domain_type == 'distribcell':
|
||||
subdomains = np.arange(self.num_subdomains, dtype=np.int)
|
||||
subdomains = np.arange(self.num_subdomains, dtype=int)
|
||||
elif self.domain_type == 'mesh':
|
||||
subdomains = list(self.domain.indices)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -124,10 +124,10 @@ class Model:
|
|||
@lru_cache(maxsize=None)
|
||||
def _materials_by_id(self):
|
||||
"""Dictionary mapping material ID --> material"""
|
||||
if self.materials is None:
|
||||
mats = self.geometry.get_all_materials().values()
|
||||
else:
|
||||
if self.materials:
|
||||
mats = self.materials
|
||||
else:
|
||||
mats = self.geometry.get_all_materials().values()
|
||||
return {mat.id: mat for mat in mats}
|
||||
|
||||
@property
|
||||
|
|
@ -248,7 +248,7 @@ class Model:
|
|||
|
||||
Parameters
|
||||
----------
|
||||
path : str or Pathlike
|
||||
path : str or PathLike
|
||||
Path to model.xml file
|
||||
"""
|
||||
tree = ET.parse(path)
|
||||
|
|
@ -473,7 +473,7 @@ class Model:
|
|||
|
||||
Parameters
|
||||
----------
|
||||
path : str or Pathlike
|
||||
path : str or PathLike
|
||||
Location of the XML file to write (default is 'model.xml'). Can be a
|
||||
directory or file path.
|
||||
remove_surfs : bool
|
||||
|
|
@ -620,7 +620,7 @@ class Model:
|
|||
value set by the :envvar:`OMP_NUM_THREADS` environment variable).
|
||||
geometry_debug : bool, optional
|
||||
Turn on geometry debugging during simulation. Defaults to False.
|
||||
restart_file : str, optional
|
||||
restart_file : str or PathLike
|
||||
Path to restart file to use
|
||||
tracks : bool, optional
|
||||
Enables the writing of particles tracks. The number of particle
|
||||
|
|
|
|||
|
|
@ -11,10 +11,12 @@ from typing import Optional
|
|||
from xml.etree import ElementTree as ET
|
||||
|
||||
import openmc.checkvalue as cv
|
||||
from openmc.stats.multivariate import MeshSpatial
|
||||
|
||||
from . import RegularMesh, Source, VolumeCalculation, WeightWindows
|
||||
from ._xml import clean_indentation, get_text, reorder_attributes
|
||||
from openmc.checkvalue import PathLike
|
||||
from .mesh import _read_meshes
|
||||
|
||||
|
||||
class RunMode(Enum):
|
||||
|
|
@ -990,6 +992,10 @@ class Settings:
|
|||
def _create_source_subelement(self, root):
|
||||
for source in self.source:
|
||||
root.append(source.to_xml_element())
|
||||
if isinstance(source.space, MeshSpatial):
|
||||
path = f"./mesh[@id='{source.space.mesh.id}']"
|
||||
if root.find(path) is None:
|
||||
root.append(source.space.mesh.to_xml_element())
|
||||
|
||||
def _create_volume_calcs_subelement(self, root):
|
||||
for calc in self.volume_calculations:
|
||||
|
|
@ -1340,9 +1346,11 @@ class Settings:
|
|||
threshold = float(get_text(elem, 'threshold'))
|
||||
self.keff_trigger = {'type': trigger, 'threshold': threshold}
|
||||
|
||||
def _source_from_xml_element(self, root):
|
||||
def _source_from_xml_element(self, root, meshes=None):
|
||||
for elem in root.findall('source'):
|
||||
self.source.append(Source.from_xml_element(elem))
|
||||
src = Source.from_xml_element(elem, meshes)
|
||||
# add newly constructed source object to the list
|
||||
self.source.append(src)
|
||||
|
||||
def _volume_calcs_from_xml_element(self, root):
|
||||
volume_elems = root.findall("volume_calc")
|
||||
|
|
@ -1721,7 +1729,7 @@ class Settings:
|
|||
settings._rel_max_lost_particles_from_xml_element(elem)
|
||||
settings._generations_per_batch_from_xml_element(elem)
|
||||
settings._keff_trigger_from_xml_element(elem)
|
||||
settings._source_from_xml_element(elem)
|
||||
settings._source_from_xml_element(elem, meshes)
|
||||
settings._volume_calcs_from_xml_element(elem)
|
||||
settings._output_from_xml_element(elem)
|
||||
settings._statepoint_from_xml_element(elem)
|
||||
|
|
@ -1781,4 +1789,5 @@ class Settings:
|
|||
"""
|
||||
tree = ET.parse(path)
|
||||
root = tree.getroot()
|
||||
return cls.from_xml_element(root)
|
||||
meshes = _read_meshes(root)
|
||||
return cls.from_xml_element(root, meshes)
|
||||
|
|
|
|||
|
|
@ -258,13 +258,16 @@ class Source:
|
|||
return element
|
||||
|
||||
@classmethod
|
||||
def from_xml_element(cls, elem: ET.Element) -> 'openmc.Source':
|
||||
def from_xml_element(cls, elem: ET.Element, meshes=None) -> 'openmc.Source':
|
||||
"""Generate source from an XML element
|
||||
|
||||
Parameters
|
||||
----------
|
||||
elem : xml.etree.ElementTree.Element
|
||||
XML element
|
||||
meshes : dict
|
||||
Dictionary with mesh IDs as keys and openmc.MeshBase instaces as
|
||||
values
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
|
@ -313,7 +316,7 @@ class Source:
|
|||
|
||||
space = elem.find('space')
|
||||
if space is not None:
|
||||
source.space = Spatial.from_xml_element(space)
|
||||
source.space = Spatial.from_xml_element(space, meshes)
|
||||
|
||||
angle = elem.find('angle')
|
||||
if angle is not None:
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import numpy as np
|
|||
import openmc.checkvalue as cv
|
||||
from .._xml import get_text
|
||||
from .univariate import Univariate, Uniform, PowerLaw
|
||||
from ..mesh import MeshBase
|
||||
|
||||
|
||||
class UnitSphere(ABC):
|
||||
|
|
@ -261,7 +262,7 @@ class Spatial(ABC):
|
|||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def from_xml_element(cls, elem):
|
||||
def from_xml_element(cls, elem, meshes=None):
|
||||
distribution = get_text(elem, 'type')
|
||||
if distribution == 'cartesian':
|
||||
return CartesianIndependent.from_xml_element(elem)
|
||||
|
|
@ -273,6 +274,8 @@ class Spatial(ABC):
|
|||
return Box.from_xml_element(elem)
|
||||
elif distribution == 'point':
|
||||
return Point.from_xml_element(elem)
|
||||
elif distribution == 'mesh':
|
||||
return MeshSpatial.from_xml_element(elem, meshes)
|
||||
|
||||
|
||||
class CartesianIndependent(Spatial):
|
||||
|
|
@ -617,6 +620,133 @@ class CylindricalIndependent(Spatial):
|
|||
return cls(r, phi, z, origin=origin)
|
||||
|
||||
|
||||
class MeshSpatial(Spatial):
|
||||
"""Spatial distribution for a mesh.
|
||||
|
||||
This distribution specifies a mesh to sample over with source strengths
|
||||
specified for each mesh element.
|
||||
|
||||
.. versionadded:: 0.13.3
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mesh : openmc.MeshBase
|
||||
The mesh instance used for sampling
|
||||
strengths : iterable of float, optional
|
||||
An iterable of values that represents the weights of each element. If no
|
||||
source strengths are specified, they will be equal for all mesh
|
||||
elements.
|
||||
volume_normalized : bool, optional
|
||||
Whether or not the strengths will be multiplied by element volumes at
|
||||
runtime. Default is True.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
mesh : openmc.MeshBase
|
||||
The mesh instance used for sampling
|
||||
strengths : numpy.ndarray or None
|
||||
An array of source strengths for each mesh element
|
||||
volume_normalized : bool
|
||||
Whether or not the strengths will be multiplied by element volumes at
|
||||
runtime.
|
||||
"""
|
||||
|
||||
def __init__(self, mesh, strengths=None, volume_normalized=True):
|
||||
self.mesh = mesh
|
||||
self.strengths = strengths
|
||||
self.volume_normalized = volume_normalized
|
||||
|
||||
@property
|
||||
def mesh(self):
|
||||
return self._mesh
|
||||
|
||||
@mesh.setter
|
||||
def mesh(self, mesh):
|
||||
if mesh is not None:
|
||||
cv.check_type('mesh instance', mesh, MeshBase)
|
||||
self._mesh = mesh
|
||||
|
||||
@property
|
||||
def volume_normalized(self):
|
||||
return self._volume_normalized
|
||||
|
||||
@volume_normalized.setter
|
||||
def volume_normalized(self, volume_normalized):
|
||||
cv.check_type('Multiply strengths by element volumes', volume_normalized, bool)
|
||||
self._volume_normalized = volume_normalized
|
||||
|
||||
@property
|
||||
def strengths(self):
|
||||
return self._strengths
|
||||
|
||||
@strengths.setter
|
||||
def strengths(self, given_strengths):
|
||||
if given_strengths is not None:
|
||||
cv.check_type('strengths array passed in', given_strengths, Iterable, Real)
|
||||
self._strengths = np.asarray(given_strengths, dtype=float).flatten()
|
||||
else:
|
||||
self._strengths = None
|
||||
|
||||
@property
|
||||
def num_strength_bins(self):
|
||||
if self.strengths is None:
|
||||
raise ValueError('Strengths are not set')
|
||||
return self.strengths.size
|
||||
|
||||
def to_xml_element(self):
|
||||
"""Return XML representation of the spatial distribution
|
||||
|
||||
Returns
|
||||
-------
|
||||
element : xml.etree.ElementTree.Element
|
||||
XML element containing spatial distribution data
|
||||
|
||||
"""
|
||||
element = ET.Element('space')
|
||||
element.set('type', 'mesh')
|
||||
element.set("mesh_id", str(self.mesh.id))
|
||||
element.set("volume_normalized", str(self.volume_normalized))
|
||||
|
||||
if self.strengths is not None:
|
||||
subelement = ET.SubElement(element, 'strengths')
|
||||
subelement.text = ' '.join(str(e) for e in self.strengths)
|
||||
|
||||
return element
|
||||
|
||||
@classmethod
|
||||
def from_xml_element(cls, elem, meshes):
|
||||
"""Generate spatial distribution from an XML element
|
||||
|
||||
Parameters
|
||||
----------
|
||||
elem : xml.etree.ElementTree.Element
|
||||
XML element
|
||||
meshes : dict
|
||||
A dictionary with mesh IDs as keys and openmc.MeshBase instances as
|
||||
values
|
||||
|
||||
Returns
|
||||
-------
|
||||
openmc.stats.MeshSpatial
|
||||
Spatial distribution generated from XML element
|
||||
|
||||
"""
|
||||
|
||||
mesh_id = int(elem.get('mesh_id'))
|
||||
|
||||
# check if this mesh has been read in from another location already
|
||||
if mesh_id not in meshes:
|
||||
raise RuntimeError(f'Could not locate mesh with ID "{mesh_id}"')
|
||||
|
||||
volume_normalized = elem.get("volume_normalized")
|
||||
volume_normalized = get_text(elem, 'volume_normalized').lower() == 'true'
|
||||
strengths = get_text(elem, 'strengths')
|
||||
if strengths is not None:
|
||||
strengths = [float(b) for b in get_text(elem, 'strengths').split()]
|
||||
|
||||
return cls(meshes[mesh_id], strengths, volume_normalized)
|
||||
|
||||
|
||||
class Box(Spatial):
|
||||
"""Uniform distribution of coordinates in a rectangular cuboid.
|
||||
|
||||
|
|
|
|||
|
|
@ -857,7 +857,6 @@ class Tabular(Univariate):
|
|||
'or linear-linear interpolation.')
|
||||
if self.interpolation == 'linear-linear':
|
||||
mean = 0.0
|
||||
self.normalize()
|
||||
for i in range(1, len(self.x)):
|
||||
y_min = self.p[i-1]
|
||||
y_max = self.p[i]
|
||||
|
|
@ -872,9 +871,13 @@ class Tabular(Univariate):
|
|||
mean += exp_val
|
||||
|
||||
elif self.interpolation == 'histogram':
|
||||
mean = 0.5 * (self.x[:-1] + self.x[1:])
|
||||
mean *= np.diff(self.cdf())
|
||||
mean = sum(mean)
|
||||
x_l = self.x[:-1]
|
||||
x_r = self.x[1:]
|
||||
p_l = self.p[:-1]
|
||||
mean = (0.5 * (x_l + x_r) * (x_r - x_l) * p_l).sum()
|
||||
|
||||
# Normalize for when integral of distribution is not 1
|
||||
mean /= self.integral()
|
||||
|
||||
return mean
|
||||
|
||||
|
|
|
|||
|
|
@ -1405,7 +1405,7 @@ class Tally(IDManagerMixin):
|
|||
|
||||
return df
|
||||
|
||||
def get_reshaped_data(self, value='mean'):
|
||||
def get_reshaped_data(self, value='mean', expand_dims=False):
|
||||
"""Returns an array of tally data with one dimension per filter.
|
||||
|
||||
The tally data in OpenMC is stored as a 3D array with the dimensions
|
||||
|
|
@ -1417,17 +1417,24 @@ class Tally(IDManagerMixin):
|
|||
|
||||
This builds and returns a reshaped version of the tally data array with
|
||||
unique dimensions corresponding to each tally filter. For example,
|
||||
suppose this tally has arrays of data with shape (8,5,5) corresponding
|
||||
to two filters (2 and 4 bins, respectively), five nuclides and five
|
||||
suppose this tally has arrays of data with shape (30,5,5) corresponding
|
||||
to two filters (2 and 15 bins, respectively), five nuclides and five
|
||||
scores. This method will return a version of the data array with the
|
||||
with a new shape of (2,4,5,5) such that the first two dimensions
|
||||
correspond directly to the two filters with two and four bins.
|
||||
with a new shape of (2,15,5,5) such that the first two dimensions
|
||||
correspond directly to the two filters with two and fifteen bins. If
|
||||
expand_dims is True and our filter above with 15 bins is an instance of
|
||||
:class:`openmc.MeshFilter` with a shape of (3,5,1). The resulting tally
|
||||
data array will have a new shape of (2,3,5,1,5,5).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
value : str
|
||||
A string for the type of value to return - 'mean' (default),
|
||||
'std_dev', 'rel_err', 'sum', or 'sum_sq' are accepted
|
||||
expand_dims : bool, optional
|
||||
Whether or not to expand the dimensions of filters with multiple
|
||||
dimensions. This will result in more than one dimension per filter
|
||||
for the returned data array.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
|
@ -1439,12 +1446,32 @@ class Tally(IDManagerMixin):
|
|||
# Get the 3D array of data in filters, nuclides and scores
|
||||
data = self.get_values(value=value)
|
||||
|
||||
# Build a new array shape with one dimension per filter
|
||||
new_shape = tuple(f.num_bins for f in self.filters)
|
||||
# Build a new array shape with one dimension per filter or expand
|
||||
# multidimensional filters if desired
|
||||
new_shape = tuple()
|
||||
idx0 = None
|
||||
for i, f in enumerate(self.filters):
|
||||
if expand_dims:
|
||||
# Mesh filter indices are backwards so we need to flip them
|
||||
if isinstance(f, openmc.MeshFilter):
|
||||
fshape = f.shape[::-1]
|
||||
new_shape += fshape
|
||||
idx0, idx1 = i, i + len(fshape) - 1
|
||||
else:
|
||||
new_shape += f.shape
|
||||
else:
|
||||
new_shape += (np.prod(f.shape),)
|
||||
|
||||
new_shape += (self.num_nuclides, self.num_scores)
|
||||
|
||||
# Reshape the data with one dimension for each filter
|
||||
data = np.reshape(data, new_shape)
|
||||
|
||||
# If we had a MeshFilter we should swap the axes to have the same shape
|
||||
# for the data and the filter
|
||||
if idx0 is not None:
|
||||
data = np.swapaxes(data, idx0, idx1)
|
||||
|
||||
return data
|
||||
|
||||
def hybrid_product(self, other, binary_op, filter_product=None,
|
||||
|
|
|
|||
|
|
@ -91,6 +91,23 @@ class UniverseBase(ABC, IDManagerMixin):
|
|||
else:
|
||||
raise ValueError('No volume information found for this universe.')
|
||||
|
||||
def get_all_universes(self):
|
||||
"""Return all universes that are contained within this one.
|
||||
|
||||
Returns
|
||||
-------
|
||||
universes : collections.OrderedDict
|
||||
Dictionary whose keys are universe IDs and values are
|
||||
:class:`Universe` instances
|
||||
|
||||
"""
|
||||
# Append all Universes within each Cell to the dictionary
|
||||
universes = OrderedDict()
|
||||
for cell in self.get_all_cells().values():
|
||||
universes.update(cell.get_all_universes())
|
||||
|
||||
return universes
|
||||
|
||||
@abstractmethod
|
||||
def create_xml_subelement(self, xml_element, memo=None):
|
||||
"""Add the universe xml representation to an incoming xml element
|
||||
|
|
@ -111,6 +128,13 @@ class UniverseBase(ABC, IDManagerMixin):
|
|||
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def _partial_deepcopy(self):
|
||||
"""Deepcopy all parameters of an openmc.UniverseBase object except its cells.
|
||||
This should only be used from the openmc.UniverseBase.clone() context.
|
||||
|
||||
"""
|
||||
|
||||
def clone(self, clone_materials=True, clone_regions=True, memo=None):
|
||||
"""Create a copy of this universe with a new unique ID, and clones
|
||||
all cells within this universe.
|
||||
|
|
@ -138,8 +162,7 @@ class UniverseBase(ABC, IDManagerMixin):
|
|||
|
||||
# If no memoize'd clone exists, instantiate one
|
||||
if self not in memo:
|
||||
clone = deepcopy(self)
|
||||
clone.id = None
|
||||
clone = self._partial_deepcopy()
|
||||
|
||||
# Clone all cells for the universe clone
|
||||
clone._cells = OrderedDict()
|
||||
|
|
@ -532,23 +555,6 @@ class Universe(UniverseBase):
|
|||
|
||||
return materials
|
||||
|
||||
def get_all_universes(self):
|
||||
"""Return all universes that are contained within this one.
|
||||
|
||||
Returns
|
||||
-------
|
||||
universes : collections.OrderedDict
|
||||
Dictionary whose keys are universe IDs and values are
|
||||
:class:`Universe` instances
|
||||
|
||||
"""
|
||||
# Append all Universes within each Cell to the dictionary
|
||||
universes = OrderedDict()
|
||||
for cell in self.get_all_cells().values():
|
||||
universes.update(cell.get_all_universes())
|
||||
|
||||
return universes
|
||||
|
||||
def create_xml_subelement(self, xml_element, memo=None):
|
||||
# Iterate over all Cells
|
||||
for cell in self._cells.values():
|
||||
|
|
@ -611,6 +617,15 @@ class Universe(UniverseBase):
|
|||
if not instances_only:
|
||||
cell._paths.append(cell_path)
|
||||
|
||||
def _partial_deepcopy(self):
|
||||
"""Clone all of the openmc.Universe object's attributes except for its cells,
|
||||
as they are copied within the clone function. This should only to be
|
||||
used within the openmc.UniverseBase.clone() context.
|
||||
"""
|
||||
clone = openmc.Universe(name=self.name)
|
||||
clone.volume = self.volume
|
||||
return clone
|
||||
|
||||
|
||||
class DAGMCUniverse(UniverseBase):
|
||||
"""A reference to a DAGMC file to be used in the model.
|
||||
|
|
@ -947,3 +962,14 @@ class DAGMCUniverse(UniverseBase):
|
|||
out.auto_mat_ids = bool(elem.get('auto_mat_ids'))
|
||||
|
||||
return out
|
||||
|
||||
def _partial_deepcopy(self):
|
||||
"""Clone all of the openmc.DAGMCUniverse object's attributes except for
|
||||
its cells, as they are copied within the clone function. This should
|
||||
only to be used within the openmc.UniverseBase.clone() context.
|
||||
"""
|
||||
clone = openmc.DAGMCUniverse(name=self.name, filename=self.filename)
|
||||
clone.volume = self.volume
|
||||
clone.auto_geom_ids = self.auto_geom_ids
|
||||
clone.auto_mat_ids = self.auto_mat_ids
|
||||
return clone
|
||||
Loading…
Add table
Add a link
Reference in a new issue