Merge remote-tracking branch 'upstream/develop' into mgxs-scatt-orders

This commit is contained in:
Will Boyd 2016-05-11 14:27:42 -04:00
commit 47adcbfb89
6 changed files with 575 additions and 775 deletions

File diff suppressed because one or more lines are too long

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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