From cfb63396989cc276a6089866f1d02dd33d2be8fb Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 22 Feb 2017 13:06:07 -0600 Subject: [PATCH] Change how volume is stored --- openmc/cell.py | 32 +++++---- openmc/geometry.py | 6 +- openmc/material.py | 26 ++++--- openmc/universe.py | 28 +++++--- openmc/volume.py | 82 +++++++++++++++------- tests/test_volume_calc/test_volume_calc.py | 8 +-- 6 files changed, 114 insertions(+), 68 deletions(-) diff --git a/openmc/cell.py b/openmc/cell.py index 859adb6d1d..2b303b44f5 100644 --- a/openmc/cell.py +++ b/openmc/cell.py @@ -86,9 +86,9 @@ class Cell(object): distribcell_paths : list of str The paths traversed through the CSG tree to reach each distribcell instance - volume_information : dict - Estimate of the volume and total number of atoms of each nuclide from a - stochastic volume calculation. This information is set with the + volume : float + Volume of the cell in cm^3. This can either be set manually or + calculated in a stochastic volume calculation and added via the :meth:`Cell.add_volume_information` method. """ @@ -106,7 +106,8 @@ class Cell(object): self._offsets = None self._distribcell_index = None self._distribcell_paths = None - self._volume_information = None + self._volume = None + self._atoms = None def __contains__(self, point): if self.region is None: @@ -224,8 +225,8 @@ class Cell(object): return self._distribcell_paths @property - def volume_information(self): - return self._volume_information + def volume(self): + return self._volume @id.setter def id(self, cell_id): @@ -326,6 +327,12 @@ class Cell(object): cv.check_type('cell region', region, Region) self._region = region + @volume.setter + def volume(self, volume): + if volume is not None: + cv.check_type('cell volume', volume, Real) + self._volume = volume + @distribcell_index.setter def distribcell_index(self, ind): cv.check_type('distribcell index', ind, Integral) @@ -391,10 +398,9 @@ class Cell(object): """ if volume_calc.domain_type == 'cell': - for cell_id in volume_calc.results: - if cell_id == self.id: - self._volume_information = volume_calc.results[cell_id] - break + if self.id in volume_calc.volumes: + self._volume = volume_calc.volumes[self.id][0] + self._atoms = volume_calc.atoms[self.id] else: raise ValueError('No volume information found for this cell.') else: @@ -446,9 +452,9 @@ class Cell(object): elif self.fill_type == 'void': pass else: - if self.volume_information is not None: - volume = self.volume_information['volume'][0] - for name, atoms in self.volume_information['atoms']: + if self._atoms is not None: + volume = self.volume + for name, atoms in self._atoms.items(): nuclide = openmc.Nuclide(name) density = 1.0e-24 * atoms[0]/volume # density in atoms/b-cm nuclides[name] = (nuclide, density) diff --git a/openmc/geometry.py b/openmc/geometry.py index 023a7abd5c..53a7ffa28e 100644 --- a/openmc/geometry.py +++ b/openmc/geometry.py @@ -61,15 +61,15 @@ class Geometry(object): """ if volume_calc.domain_type == 'cell': for cell in self.get_all_cells(): - if cell.id in volume_calc.results: + if cell.id in volume_calc.volumes: cell.add_volume_information(volume_calc) elif volume_calc.domain_type == 'material': for material in self.get_all_materials(): - if material.id in volume_calc.results: + if material.id in volume_calc.volumes: material.add_volume_information(volume_calc) elif volume_calc.domain_type == 'universe': for universe in self.get_all_universes(): - if universe.id in volume_calc.results: + if universe.id in volume_calc.volumes: universe.add_volume_information(volume_calc) def export_to_xml(self, path='geometry.xml'): diff --git a/openmc/material.py b/openmc/material.py index 179d43579b..49738174d4 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -71,9 +71,9 @@ class Material(object): The average molar mass of nuclides in the material in units of grams per mol. For example, UO2 with 3 nuclides will have an average molar mass of 270 / 3 = 90 g / mol. - volume_information : dict - Estimate of the volume and total number of atoms of each nuclide from a - stochastic volume calculation. This information is set with the + volume : float + Volume of the material in cm^3. This can either be set manually or + calculated in a stochastic volume calculation and added via the :meth:`Material.add_volume_information` method. """ @@ -86,7 +86,8 @@ class Material(object): self._density = None self._density_units = '' self._depletable = False - self._volume_information = None + self._volume = None + self._atoms = {} # A list of tuples (nuclide, percent, percent type) self._nuclides = [] @@ -232,8 +233,8 @@ class Material(object): return mass / moles @property - def volume_information(self): - return self._volume_information + def volume(self): + return self._volume @id.setter def id(self, material_id): @@ -268,6 +269,12 @@ class Material(object): depletable, bool) self._depletable = depletable + @volume.setter + def volume(self, volume): + if volume is not None: + cv.check_type('material volume', volume, Real) + self._volume = volume + @classmethod def from_hdf5(cls, group): """Create material from HDF5 group @@ -321,10 +328,9 @@ class Material(object): """ if volume_calc.domain_type == 'material': - for mat_id in volume_calc.results: - if mat_id == self.id: - self._volume_information = volume_calc.results[mat_id] - break + if self.id in volume_calc.volumes: + self._volume = volume_calc.volumes[self.id] + self._atoms = volume_calc.atoms[self.id] else: raise ValueError('No volume information found for this material.') else: diff --git a/openmc/universe.py b/openmc/universe.py index 65d09f7079..5e001ae6ac 100644 --- a/openmc/universe.py +++ b/openmc/universe.py @@ -1,5 +1,5 @@ from collections import OrderedDict, Iterable -from numbers import Integral +from numbers import Integral, Real import random import sys @@ -42,9 +42,9 @@ class Universe(object): cells : collections.OrderedDict Dictionary whose keys are cell IDs and values are :class:`Cell` instances - volume_information : dict - Estimate of the volume and total number of atoms of each nuclide from a - stochastic volume calculation. This information is set with the + volume : float + Volume of the universe in cm^3. This can either be set manually or + calculated in a stochastic volume calculation and added via the :meth:`Universe.add_volume_information` method. """ @@ -53,7 +53,8 @@ class Universe(object): # Initialize Cell class attributes self.id = universe_id self.name = name - self._volume_information = None + self._volume = None + self._atoms = {} # Keys - Cell IDs # Values - Cells @@ -105,8 +106,8 @@ class Universe(object): return self._cells @property - def volume_information(self): - return self._volume_information + def volume(self): + return self._volume @id.setter def id(self, universe_id): @@ -127,6 +128,12 @@ class Universe(object): else: self._name = '' + @volume.setter + def volume(self, volume): + if volume is not None: + cv.check_type('universe volume', volume, Real) + self._volume = volume + @classmethod def from_hdf5(cls, group, cells): """Create universe from HDF5 group @@ -166,10 +173,9 @@ class Universe(object): """ if volume_calc.domain_type == 'cell': - for univ_id in volume_calc.results: - if univ_id == self.id: - self._volume_information = volume_calc.results[univ_id] - break + if self.id in volume_calc.volumes: + self._volume = volume_calc.volumes[self.id] + self._atoms = volume_calc.atoms[self.id] else: raise ValueError('No volume information found for this universe.') else: diff --git a/openmc/volume.py b/openmc/volume.py index d85a9a17cc..f7bad73b4c 100644 --- a/openmc/volume.py +++ b/openmc/volume.py @@ -1,4 +1,4 @@ -from collections import Iterable, Mapping +from collections import Iterable, Mapping, OrderedDict from numbers import Real, Integral from xml.etree import ElementTree as ET from warnings import warn @@ -43,21 +43,21 @@ class VolumeCalculation(object): Lower-left coordinates of bounding box used to sample points upper_right : Iterable of float Upper-right coordinates of bounding box used to sample points - results : dict - Dictionary whose keys are unique IDs of domains and values are - dictionaries with calculated volumes and total number of atoms for each - nuclide present in the domain. - volumes : dict - Dictionary whose keys are unique IDs of domains and values are the - estimated volumes + atoms : dict + Dictionary mapping unique IDs of domains to a mapping of nuclides to + total number of atoms for each nuclide present in the domain. For + example, {10: {'U235': 1.0e22, 'U238': 5.0e22, ...}}. atoms_dataframe : pandas.DataFrame DataFrame showing the estimated number of atoms for each nuclide present in each domain specified. + volumes : dict + Dictionary mapping unique IDs of domains to estimated volumes in cm^3. """ def __init__(self, domains, samples, lower_left=None, upper_right=None): - self._results = None + self._atoms = {} + self._volumes = {} cv.check_type('domains', domains, Iterable, (openmc.Cell, openmc.Material, openmc.Universe)) @@ -122,25 +122,25 @@ class VolumeCalculation(object): def upper_right(self): return self._upper_right - @property - def results(self): - return self._results - @property def domain_type(self): return self._domain_type + @property + def atoms(self): + return self._atoms + @property def volumes(self): - return {uid: results['volume'] for uid, results in self.results.items()} + return self._volumes @property def atoms_dataframe(self): items = [] columns = [self.domain_type.capitalize(), 'Nuclide', 'Atoms', 'Uncertainty'] - for uid, results in self.results.items(): - for name, atoms in results['atoms']: + for uid, atoms_dict in self.atoms.items(): + for name, atoms in atoms_dict.items(): items.append((uid, name, atoms[0], atoms[1])) return pd.DataFrame.from_records(items, columns=columns) @@ -170,10 +170,15 @@ class VolumeCalculation(object): cv.check_length(name, upper_right, 3) self._upper_right = upper_right - @results.setter - def results(self, results): - cv.check_type('results', results, Mapping) - self._results = results + @volumes.setter + def volumes(self, volumes): + cv.check_type('volumes', volumes, Mapping) + self._volumes = volumes + + @atoms.setter + def atoms(self, atoms): + cv.check_type('atoms', atoms, Mapping) + self._atoms = atoms @classmethod def from_hdf5(cls, filename): @@ -198,7 +203,8 @@ class VolumeCalculation(object): lower_left = f.attrs['lower_left'] upper_right = f.attrs['upper_right'] - results = {} + volumes = {} + atoms = {} ids = [] for obj_name in f: if obj_name.startswith('domain_'): @@ -207,12 +213,13 @@ class VolumeCalculation(object): group = f[obj_name] volume = tuple(group['volume'].value) nucnames = group['nuclides'].value - atoms = group['atoms'].value + atoms_ = group['atoms'].value - atom_list = [] - for name_i, atoms_i in zip(nucnames, atoms): - atom_list.append((name_i.decode(), tuple(atoms_i))) - results[domain_id] = {'volume': volume, 'atoms': atom_list} + atom_dict = OrderedDict() + for name_i, atoms_i in zip(nucnames, atoms_): + atom_dict[name_i.decode()] = tuple(atoms_i) + volumes[domain_id] = volume + atoms[domain_id] = atom_dict # Instantiate some throw-away domains that are used by the constructor # to assign IDs @@ -225,9 +232,30 @@ class VolumeCalculation(object): # Instantiate the class and assign results vol = cls(domains, samples, lower_left, upper_right) - vol.results = results + vol.volumes = volumes + vol.atoms = atoms return vol + def load_results(self, filename): + """Load stochastic volume calculation results from an HDF5 file. + + Parameters + ---------- + filename : str + Path to volume.h5 file + + """ + results = type(self).from_hdf5(filename) + + # Make sure properties match + assert self.domains == results.domains + assert self.lower_left == results.lower_left + assert self.upper_right == results.upper_right + + # Copy results + self.volumes = results.volumes + self.atoms = results.atoms + def to_xml_element(self): """Return XML representation of the volume calculation diff --git a/tests/test_volume_calc/test_volume_calc.py b/tests/test_volume_calc/test_volume_calc.py index 4461e231dd..52b474cb7d 100644 --- a/tests/test_volume_calc/test_volume_calc.py +++ b/tests/test_volume_calc/test_volume_calc.py @@ -62,13 +62,13 @@ class VolumeTest(PyAPITestHarness): outstr += 'Volume calculation {}\n'.format(i) # Read volume calculation results - vol = openmc.VolumeCalculation.from_hdf5(filename) + volume_calc = openmc.VolumeCalculation.from_hdf5(filename) # Write cell volumes and total # of atoms for each nuclide - for uid, results in sorted(vol.results.items()): + for uid, volume in sorted(volume_calc.volumes.items()): outstr += 'Domain {0}: {1[0]:.4f} +/- {1[1]:.4f} cm^3\n'.format( - uid, results['volume']) - outstr += str(vol.atoms_dataframe) + '\n' + uid, volume) + outstr += str(volume_calc.atoms_dataframe) + '\n' return outstr