mirror of
https://github.com/openmc-dev/openmc.git
synced 2026-07-27 21:55:41 -04:00
Merge remote-tracking branch 'upstream/develop' into mgxs-scatt-orders
This commit is contained in:
commit
47adcbfb89
6 changed files with 575 additions and 775 deletions
File diff suppressed because one or more lines are too long
|
|
@ -1215,11 +1215,10 @@ Each ``material`` element can have the following attributes or sub-elements:
|
|||
An element with attributes/sub-elements called ``value`` and ``units``. The
|
||||
``value`` attribute is the numeric value of the density while the ``units``
|
||||
can be "g/cm3", "kg/m3", "atom/b-cm", "atom/cm3", or "sum". The "sum" unit
|
||||
indicates that values appearing in ``ao`` attributes for ``<nuclide>`` and
|
||||
``<element>`` sub-elements are to be interpreted as nuclide/element
|
||||
densities in atom/b-cm, and the total density of the material is taken as
|
||||
the sum of all nuclides/elements. The "sum" option cannot be used in
|
||||
conjunction with weight percents. The "macro" unit is used with
|
||||
indicates that values appearing in ``ao`` or ``wo`` attributes for ``<nuclide>``
|
||||
and ``<element>`` sub-elements are to be interpreted as absolute nuclide/element
|
||||
densities in atom/b-cm or g/cm3, and the total density of the material is
|
||||
taken as the sum of all nuclides/elements. The "macro" unit is used with
|
||||
a ``macroscopic`` quantity to indicate that the density is already included
|
||||
in the library and thus not needed here. However, if a value is provided
|
||||
for the ``value``, then this is treated as a number density multiplier on
|
||||
|
|
|
|||
205
openmc/filter.py
205
openmc/filter.py
|
|
@ -1,4 +1,4 @@
|
|||
from collections import Iterable
|
||||
from collections import Iterable, OrderedDict
|
||||
import copy
|
||||
from numbers import Real, Integral
|
||||
import sys
|
||||
|
|
@ -516,7 +516,7 @@ class Filter(object):
|
|||
|
||||
return filter_bin
|
||||
|
||||
def get_pandas_dataframe(self, data_size, summary=None):
|
||||
def get_pandas_dataframe(self, data_size, distribcell_paths=True):
|
||||
"""Builds a Pandas DataFrame for the Filter's bins.
|
||||
|
||||
This method constructs a Pandas DataFrame object for the filter with
|
||||
|
|
@ -531,12 +531,13 @@ class Filter(object):
|
|||
----------
|
||||
data_size : Integral
|
||||
The total number of bins in the tally corresponding to this filter
|
||||
summary : None or openmc.Summary
|
||||
An optional Summary object to be used to construct columns for
|
||||
distribcell tally filters (default is None). The geometric
|
||||
information in the Summary object is embedded into a Multi-index
|
||||
column with a geometric "path" to each distribcell instance.
|
||||
NOTE: This option requires the OpenCG Python package.
|
||||
distribcell_paths : bool, optional
|
||||
Construct columns for distribcell tally filters (default is True).
|
||||
The geometric information in the Summary object is embedded into a
|
||||
Multi-index column with a geometric "path" to each distribcell
|
||||
instance. NOTE: This option assumes that all distribcell paths are
|
||||
of the same length and do not have the same universes and cells but
|
||||
different lattice cell indices.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
|
@ -554,7 +555,7 @@ class Filter(object):
|
|||
|
||||
1. a single column with the cell instance IDs (without summary info)
|
||||
2. separate columns for the cell IDs, universe IDs, and lattice IDs
|
||||
and x,y,z cell indices corresponding to each (with summary info).
|
||||
and x,y,z cell indices corresponding to each (distribcell paths).
|
||||
|
||||
For 'energy' and 'energyout' filters, the DataFrame includes one
|
||||
column for the lower energy bound and one column for the upper
|
||||
|
|
@ -566,8 +567,7 @@ class Filter(object):
|
|||
Raises
|
||||
------
|
||||
ImportError
|
||||
When Pandas is not installed, or summary info is requested but
|
||||
OpenCG is not installed.
|
||||
When Pandas is not installed
|
||||
|
||||
See also
|
||||
--------
|
||||
|
|
@ -626,106 +626,117 @@ class Filter(object):
|
|||
elif self.type == 'distribcell':
|
||||
level_df = None
|
||||
|
||||
if isinstance(summary, Summary):
|
||||
# Attempt to import the OpenCG package
|
||||
try:
|
||||
import opencg
|
||||
except ImportError:
|
||||
msg = 'The OpenCG package must be installed ' \
|
||||
'to use a Summary for distribcell dataframes'
|
||||
raise ImportError(msg)
|
||||
# Create Pandas Multi-index columns for each level in CSG tree
|
||||
if distribcell_paths:
|
||||
|
||||
# Extract the OpenCG geometry from the Summary
|
||||
opencg_geometry = summary.opencg_geometry
|
||||
openmc_geometry = summary.openmc_geometry
|
||||
# Distribcell paths require linked metadata from the Summary
|
||||
if self.distribcell_paths is None:
|
||||
msg = 'Unable to construct distribcell paths since ' \
|
||||
'the Summary is not linked to the StatePoint'
|
||||
raise ValueError(msg)
|
||||
|
||||
# Use OpenCG to compute the number of regions
|
||||
opencg_geometry.initialize_cell_offsets()
|
||||
num_regions = opencg_geometry.num_regions
|
||||
# Make copy of array of distribcell paths to use in
|
||||
# Pandas Multi-index column construction
|
||||
distribcell_paths = copy.deepcopy(self.distribcell_paths)
|
||||
num_offsets = len(distribcell_paths)
|
||||
|
||||
# Initialize a dictionary mapping OpenMC distribcell
|
||||
# offsets to OpenCG LocalCoords linked lists
|
||||
offsets_to_coords = {}
|
||||
|
||||
for offset, path in enumerate(self.distribcell_paths):
|
||||
region = opencg_geometry.get_region_from_path(path)
|
||||
coords = opencg_geometry.find_region(region)
|
||||
offsets_to_coords[offset] = coords
|
||||
|
||||
# Each distribcell offset is a DataFrame bin
|
||||
# Unravel the paths into DataFrame columns
|
||||
num_offsets = len(offsets_to_coords)
|
||||
|
||||
# Initialize termination condition for while loop
|
||||
# Loop over CSG levels in the distribcell paths
|
||||
level_counter = 0
|
||||
levels_remain = True
|
||||
counter = 0
|
||||
|
||||
# Iterate over each level in the CSG tree hierarchy
|
||||
while levels_remain:
|
||||
levels_remain = False
|
||||
|
||||
# Initialize dictionary to build Pandas Multi-index
|
||||
# column for this level in the CSG tree hierarchy
|
||||
level_dict = {}
|
||||
# Use level key as first index in Pandas Multi-index column
|
||||
level_counter += 1
|
||||
level_key = 'level {}'.format(level_counter)
|
||||
|
||||
# Initialize prefix Multi-index keys
|
||||
counter += 1
|
||||
level_key = 'level {0}'.format(counter)
|
||||
univ_key = (level_key, 'univ', 'id')
|
||||
cell_key = (level_key, 'cell', 'id')
|
||||
lat_id_key = (level_key, 'lat', 'id')
|
||||
lat_x_key = (level_key, 'lat', 'x')
|
||||
lat_y_key = (level_key, 'lat', 'y')
|
||||
lat_z_key = (level_key, 'lat', 'z')
|
||||
# Use the first distribcell path to determine if level
|
||||
# is a universe/cell or lattice level
|
||||
first_path = distribcell_paths[0]
|
||||
next_index = first_path.index('-')
|
||||
level = first_path[:next_index]
|
||||
|
||||
# Allocate NumPy arrays for each CSG level and
|
||||
# each Multi-index column in the DataFrame
|
||||
level_dict[univ_key] = np.empty(num_offsets)
|
||||
level_dict[cell_key] = np.empty(num_offsets)
|
||||
level_dict[lat_id_key] = np.empty(num_offsets)
|
||||
level_dict[lat_x_key] = np.empty(num_offsets)
|
||||
level_dict[lat_y_key] = np.empty(num_offsets)
|
||||
level_dict[lat_z_key] = np.empty(num_offsets)
|
||||
# Trim universe/lattice info from path
|
||||
first_path = first_path[next_index+2:]
|
||||
|
||||
# Initialize Multi-index columns to NaN - this is
|
||||
# necessary since some distribcell instances may
|
||||
# have very different LocalCoords linked lists
|
||||
level_dict[univ_key][:] = np.NAN
|
||||
level_dict[cell_key][:] = np.NAN
|
||||
level_dict[lat_id_key][:] = np.NAN
|
||||
level_dict[lat_x_key][:] = np.NAN
|
||||
level_dict[lat_y_key][:] = np.NAN
|
||||
level_dict[lat_z_key][:] = np.NAN
|
||||
# Create a dictionary for this level for Pandas Multi-index
|
||||
level_dict = OrderedDict()
|
||||
|
||||
# Iterate over all regions (distribcell instances)
|
||||
for offset in range(num_offsets):
|
||||
coords = offsets_to_coords[offset]
|
||||
# This level is a lattice (e.g., ID(x,y,z))
|
||||
if '(' in level:
|
||||
level_type = 'lattice'
|
||||
|
||||
# If entire LocalCoords has been unraveled into
|
||||
# Multi-index columns already, continue
|
||||
if coords is None:
|
||||
continue
|
||||
# Initialize prefix Multi-index keys
|
||||
lat_id_key = (level_key, 'lat', 'id')
|
||||
lat_x_key = (level_key, 'lat', 'x')
|
||||
lat_y_key = (level_key, 'lat', 'y')
|
||||
lat_z_key = (level_key, 'lat', 'z')
|
||||
|
||||
# Assign entry to Universe Multi-index column
|
||||
if coords._type == 'universe':
|
||||
level_dict[univ_key][offset] = coords._universe._id
|
||||
level_dict[cell_key][offset] = coords._cell._id
|
||||
# Allocate NumPy arrays for each CSG level and
|
||||
# each Multi-index column in the DataFrame
|
||||
level_dict[lat_id_key] = np.empty(num_offsets)
|
||||
level_dict[lat_x_key] = np.empty(num_offsets)
|
||||
level_dict[lat_y_key] = np.empty(num_offsets)
|
||||
level_dict[lat_z_key] = np.empty(num_offsets)
|
||||
|
||||
# This level is a universe / cell (e.g., ID->ID)
|
||||
else:
|
||||
level_type = 'universe'
|
||||
|
||||
# Initialize prefix Multi-index keys
|
||||
univ_key = (level_key, 'univ', 'id')
|
||||
cell_key = (level_key, 'cell', 'id')
|
||||
|
||||
# Allocate NumPy arrays for each CSG level and
|
||||
# each Multi-index column in the DataFrame
|
||||
level_dict[univ_key] = np.empty(num_offsets)
|
||||
level_dict[cell_key] = np.empty(num_offsets)
|
||||
|
||||
# Determine any levels remain in path
|
||||
if '-' not in first_path:
|
||||
levels_remain = False
|
||||
|
||||
# Populate Multi-index arrays with all distribcell paths
|
||||
for i, path in enumerate(distribcell_paths):
|
||||
|
||||
if level_type == 'lattice':
|
||||
# Extract lattice ID, indices from path
|
||||
next_index = path.index('-')
|
||||
lat_id_indices = path[:next_index]
|
||||
|
||||
# Trim lattice info from distribcell path
|
||||
distribcell_paths[i] = path[next_index+2:]
|
||||
|
||||
# Extract the lattice cell indices from the path
|
||||
i1 = lat_id_indices.index('(')
|
||||
i2 = lat_id_indices.index(')')
|
||||
i3 = lat_id_indices[i1+1:i2]
|
||||
|
||||
# Assign entry to Lattice Multi-index column
|
||||
level_dict[lat_id_key][i] = path[:i1]
|
||||
level_dict[lat_x_key][i] = int(i3.split(',')[0]) - 1
|
||||
level_dict[lat_y_key][i] = int(i3.split(',')[1]) - 1
|
||||
level_dict[lat_z_key][i] = int(i3.split(',')[2]) - 1
|
||||
|
||||
# Assign entry to Lattice Multi-index column
|
||||
else:
|
||||
# Reverse y index per lattice ordering in OpenCG
|
||||
level_dict[lat_id_key][offset] = coords._lattice._id
|
||||
level_dict[lat_x_key][offset] = coords._lat_x
|
||||
level_dict[lat_y_key][offset] = \
|
||||
coords._lattice.dimension[1] - coords._lat_y - 1
|
||||
level_dict[lat_z_key][offset] = coords._lat_z
|
||||
# Extract universe ID from path
|
||||
next_index = path.index('-')
|
||||
universe_id = int(path[:next_index])
|
||||
|
||||
# Move to next node in LocalCoords linked list
|
||||
if coords._next is None:
|
||||
offsets_to_coords[offset] = None
|
||||
else:
|
||||
offsets_to_coords[offset] = coords._next
|
||||
levels_remain = True
|
||||
# Trim universe info from distribcell path
|
||||
path = path[next_index+2:]
|
||||
|
||||
# Extract cell ID from path
|
||||
if '-' in path:
|
||||
next_index = path.index('-')
|
||||
cell_id = int(path[:next_index])
|
||||
distribcell_paths[i] = path[next_index+2:]
|
||||
else:
|
||||
cell_id = int(path)
|
||||
distribcell_paths[i] = ''
|
||||
|
||||
# Assign entry to Universe, Cell Multi-index columns
|
||||
level_dict[univ_key][i] = universe_id
|
||||
level_dict[cell_key][i] = cell_id
|
||||
|
||||
# Tile the Multi-index columns
|
||||
for level_key, level_bins in level_dict.items():
|
||||
|
|
@ -740,7 +751,7 @@ class Filter(object):
|
|||
else:
|
||||
level_df = pd.concat([level_df, pd.DataFrame(level_dict)], axis=1)
|
||||
|
||||
# Create DataFrame column for distribcell instances IDs
|
||||
# Create DataFrame column for distribcell instance IDs
|
||||
# NOTE: This is performed regardless of whether the user
|
||||
# requests Summary geometric information
|
||||
filter_bins = np.arange(self.num_bins)
|
||||
|
|
|
|||
|
|
@ -245,19 +245,25 @@ class Material(object):
|
|||
|
||||
"""
|
||||
|
||||
cv.check_type('the density for Material ID="{0}"'.format(self._id),
|
||||
density, Real)
|
||||
cv.check_value('density units', units, DENSITY_UNITS)
|
||||
|
||||
if density is None and units is not 'sum':
|
||||
msg = 'Unable to set the density for Material ID="{0}" ' \
|
||||
'because a density must be set when not using ' \
|
||||
'sum unit'.format(self._id)
|
||||
raise ValueError(msg)
|
||||
|
||||
self._density = density
|
||||
self._density_units = units
|
||||
|
||||
if units is 'sum':
|
||||
if density is not None:
|
||||
msg = 'Density "{0}" for Material ID="{1}" is ignored ' \
|
||||
'because the unit is "sum"'.format(density, self.id)
|
||||
warnings.warn(msg)
|
||||
else:
|
||||
if density is None:
|
||||
msg = 'Unable to set the density for Material ID="{0}" ' \
|
||||
'because a density value must be given when not using ' \
|
||||
'"sum" unit'.format(self.id)
|
||||
raise ValueError(msg)
|
||||
|
||||
cv.check_type('the density for Material ID="{0}"'.format(self.id),
|
||||
density, Real)
|
||||
self._density = density
|
||||
|
||||
@distrib_otf_file.setter
|
||||
def distrib_otf_file(self, filename):
|
||||
# TODO: remove this when distributed materials are merged
|
||||
|
|
|
|||
|
|
@ -1352,7 +1352,7 @@ class MGXS(object):
|
|||
modified.write('\n\\end{document}')
|
||||
|
||||
def get_pandas_dataframe(self, groups='all', nuclides='all',
|
||||
xs_type='macro', summary=None):
|
||||
xs_type='macro', distribcell_paths=True):
|
||||
"""Build a Pandas DataFrame for the MGXS data.
|
||||
|
||||
This method leverages :meth:`openmc.Tally.get_pandas_dataframe`, but
|
||||
|
|
@ -1372,12 +1372,11 @@ class MGXS(object):
|
|||
xs_type: {'macro', 'micro'}
|
||||
Return macro or micro cross section in units of cm^-1 or barns.
|
||||
Defaults to 'macro'.
|
||||
summary : None or openmc.Summary
|
||||
An optional Summary object to be used to construct columns for
|
||||
distribcell tally filters (default is None). The geometric
|
||||
information in the Summary object is embedded into a multi-index
|
||||
column with a geometric "path" to each distribcell intance.
|
||||
NOTE: This option requires the OpenCG Python package.
|
||||
distribcell_paths : bool, optional
|
||||
Construct columns for distribcell tally filters (default is True).
|
||||
The geometric information in the Summary object is embedded into
|
||||
a Multi-index column with a geometric "path" to each distribcell
|
||||
instance.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
|
@ -1404,7 +1403,8 @@ class MGXS(object):
|
|||
# Use tally summation to sum across all nuclides
|
||||
query_nuclides = self.get_all_nuclides()
|
||||
xs_tally = self.xs_tally.summation(nuclides=query_nuclides)
|
||||
df = xs_tally.get_pandas_dataframe(summary=summary)
|
||||
df = xs_tally.get_pandas_dataframe(
|
||||
distribcell_paths=distribcell_paths)
|
||||
|
||||
# Remove nuclide column since it is homogeneous and redundant
|
||||
df.drop('nuclide', axis=1, inplace=True)
|
||||
|
|
@ -1412,11 +1412,13 @@ class MGXS(object):
|
|||
# If the user requested a specific set of nuclides
|
||||
elif self.by_nuclide and nuclides != 'all':
|
||||
xs_tally = self.xs_tally.get_slice(nuclides=nuclides)
|
||||
df = xs_tally.get_pandas_dataframe(summary=summary)
|
||||
df = xs_tally.get_pandas_dataframe(
|
||||
distribcell_paths=distribcell_paths)
|
||||
|
||||
# If the user requested all nuclides, keep nuclide column in dataframe
|
||||
else:
|
||||
df = self.xs_tally.get_pandas_dataframe(summary=summary)
|
||||
df = self.xs_tally.get_pandas_dataframe(
|
||||
distribcell_paths=distribcell_paths)
|
||||
|
||||
# Remove the score column since it is homogeneous and redundant
|
||||
df = df.drop('score', axis=1)
|
||||
|
|
@ -2727,7 +2729,7 @@ class Chi(MGXS):
|
|||
return xs
|
||||
|
||||
def get_pandas_dataframe(self, groups='all', nuclides='all',
|
||||
xs_type='macro', summary=None):
|
||||
xs_type='macro', distribcell_paths=False):
|
||||
"""Build a Pandas DataFrame for the MGXS data.
|
||||
|
||||
This method leverages :meth:`openmc.Tally.get_pandas_dataframe`, but
|
||||
|
|
@ -2747,12 +2749,11 @@ class Chi(MGXS):
|
|||
xs_type: {'macro', 'micro'}
|
||||
Return macro or micro cross section in units of cm^-1 or barns.
|
||||
Defaults to 'macro'.
|
||||
summary : None or openmc.Summary
|
||||
An optional Summary object to be used to construct columns for
|
||||
distribcell tally filters (default is None). The geometric
|
||||
information in the Summary object is embedded into a multi-index
|
||||
column with a geometric "path" to each distribcell intance.
|
||||
NOTE: This option requires the OpenCG Python package.
|
||||
distribcell_paths : bool, optional
|
||||
Construct columns for distribcell tally filters (default is True).
|
||||
The geometric information in the Summary object is embedded into
|
||||
a Multi-index column with a geometric "path" to each distribcell
|
||||
instance.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
|
@ -2768,8 +2769,8 @@ class Chi(MGXS):
|
|||
"""
|
||||
|
||||
# Build the dataframe using the parent class method
|
||||
df = super(Chi, self).get_pandas_dataframe(groups, nuclides,
|
||||
xs_type, summary)
|
||||
df = super(Chi, self).get_pandas_dataframe(
|
||||
groups, nuclides, xs_type, distribcell_paths=distribcell_paths)
|
||||
|
||||
# If user requested micro cross sections, multiply by the atom
|
||||
# densities to cancel out division made by the parent class method
|
||||
|
|
|
|||
|
|
@ -1538,8 +1538,8 @@ class Tally(object):
|
|||
|
||||
return data
|
||||
|
||||
def get_pandas_dataframe(self, filters=True, nuclides=True,
|
||||
scores=True, summary=None, float_format='{:.2e}'):
|
||||
def get_pandas_dataframe(self, filters=True, nuclides=True, scores=True,
|
||||
distribcell_paths=True, float_format='{:.2e}'):
|
||||
"""Build a Pandas DataFrame for the Tally data.
|
||||
|
||||
This method constructs a Pandas DataFrame object for the Tally data
|
||||
|
|
@ -1557,12 +1557,11 @@ class Tally(object):
|
|||
Include columns with nuclide bin information (default is True).
|
||||
scores : bool
|
||||
Include columns with score bin information (default is True).
|
||||
summary : None or openmc.Summary
|
||||
An optional Summary object to be used to construct columns for
|
||||
distribcell tally filters (default is None). The geometric
|
||||
information in the Summary object is embedded into a Multi-index
|
||||
column with a geometric "path" to each distribcell intance.
|
||||
NOTE: This option requires the OpenCG Python package.
|
||||
distribcell_paths : bool, optional
|
||||
Construct columns for distribcell tally filters (default is True).
|
||||
The geometric information in the Summary object is embedded into a
|
||||
Multi-index column with a geometric "path" to each distribcell
|
||||
instance.
|
||||
float_format : str
|
||||
All floats in the DataFrame will be formatted using the given
|
||||
format string before printing.
|
||||
|
|
@ -1588,14 +1587,6 @@ class Tally(object):
|
|||
msg = 'The Tally ID="{0}" has no data to return'.format(self.id)
|
||||
raise KeyError(msg)
|
||||
|
||||
# If using Summary, ensure StatePoint.link_with_summary(...) was called
|
||||
if summary and not self.with_summary:
|
||||
msg = 'The Tally ID="{0}" has not been linked with the Summary. ' \
|
||||
'Call the StatePoint.link_with_summary(...) method ' \
|
||||
'before using Tally.get_pandas_dataframe(...) with ' \
|
||||
'Summary info'.format(self.id)
|
||||
raise KeyError(msg)
|
||||
|
||||
# Initialize a pandas dataframe for the tally data
|
||||
import pandas as pd
|
||||
df = pd.DataFrame()
|
||||
|
|
@ -1608,7 +1599,8 @@ class Tally(object):
|
|||
|
||||
# Append each Filter's DataFrame to the overall DataFrame
|
||||
for self_filter in self.filters:
|
||||
filter_df = self_filter.get_pandas_dataframe(data_size, summary)
|
||||
filter_df = self_filter.get_pandas_dataframe(
|
||||
data_size, distribcell_paths)
|
||||
df = pd.concat([df, filter_df], axis=1)
|
||||
|
||||
# Include DataFrame column for nuclides if user requested it
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue