diff --git a/openmc/checkvalue.py b/openmc/checkvalue.py index 948862399..787052f5d 100644 --- a/openmc/checkvalue.py +++ b/openmc/checkvalue.py @@ -1,3 +1,36 @@ +from collections import Iterable +from numbers import Integral, Real + +import numpy as np + +def _isinstance(value, expected_type): + """A Numpy-aware replacement for isinstance + + This function will be obsolete when Numpy v. >= 1.9 is established. + """ + + # Declare numpy numeric types. + np_ints = (np.int_, np.intc, np.intp, np.int8, np.int16, np.int32, np.int64, + np.uint8, np.uint16, np.uint32, np.uint64) + np_floats = (np.float_, np.float16, np.float32, np.float64) + + # Include numpy integers, if necessary. + if type(expected_type) is tuple: + if Integral in expected_type: + expected_type = expected_type + np_ints + elif expected_type is Integral: + expected_type = (Integral, ) + np_ints + + # Include numpy floats, if necessary. + if type(expected_type) is tuple: + if Real in expected_type: + expected_type = expected_type + np_floats + elif expected_type is Real: + expected_type = (Real, ) + np_floats + + # Now, make the instance check. + return isinstance(value, expected_type) + def check_type(name, value, expected_type, expected_iter_type=None): """Ensure that an object is of an expected type. Optionally, if the object is iterable, check that each element is of a particular type. @@ -16,20 +49,97 @@ def check_type(name, value, expected_type, expected_iter_type=None): """ - if not isinstance(value, expected_type): + if not _isinstance(value, expected_type): msg = 'Unable to set "{0}" to "{1}" which is not of type "{2}"'.format( name, value, expected_type.__name__) raise ValueError(msg) if expected_iter_type: for item in value: - if not isinstance(item, expected_iter_type): + if not _isinstance(item, expected_iter_type): msg = 'Unable to set "{0}" to "{1}" since each item must be ' \ 'of type "{2}"'.format(name, value, expected_iter_type.__name__) raise ValueError(msg) +def check_iterable_type(name, value, expected_type, min_depth=1, max_depth=1): + """Ensure that an object is an iterable containing an expected type. + + Parameters + ---------- + name : str + Description of value being checked + value : Iterable + Iterable, possibly of other iterables, that should ultimately contain + the expected type + expected_type : type + type that the iterable should contain + min_depth : int + The minimum number of layers of nested iterables there should be before + reaching the ultimately contained items + max_depth : int + The maximum number of layers of nested iterables there should be before + reaching the ultimately contained items + """ + # Initialize the tree at the very first item. + tree = [value] + index = [0] + + # Traverse the tree. + while index[0] != len(tree[0]): + # If we are done with this level of the tree, go to the next branch on + # the level above this one. + if index[-1] == len(tree[-1]): + del index[-1] + del tree[-1] + index[-1] += 1 + continue + + # Get a string representation of the current index in case we raise an + # exception. + form = '[' + '{:d}, '*(len(index)-1) + '{:d}]' + ind_str = form.format(*index) + + # What is the current item we are looking at? + current_item = tree[-1][index[-1]] + + # If this item is of the expected type, then we've reached the bottom + # level of this branch. + if _isinstance(current_item, expected_type): + # Is this deep enough? + if len(tree) < min_depth: + msg = 'Error setting "{0}": The item at {1} does not meet the '\ + 'minimum depth of {2}'.format(name, ind_str, min_depth) + raise ValueError(msg) + + # This item is okay. Move on to the next item. + index[-1] += 1 + + # If this item is not of the expected type, then it's either an error or + # another level of the tree that we need to pursue deeper. + else: + if isinstance(current_item, Iterable): + # The tree goes deeper here, let's explore it. + tree.append(current_item) + index.append(0) + + # But first, have we exceeded the max depth? + if len(tree) > max_depth: + msg = 'Error setting {0}: Found an iterable at {1}, items '\ + 'in that iterable excceed the maximum depth of {2}' \ + .format(name, ind_str, max_depth) + raise ValueError(msg) + + else: + # This item is completely unexpected. + msg = "Error setting {0}: Items must be of type '{1}', but " \ + "item at {2} is of type '{3}'"\ + .format(name, expected_type.__name__, ind_str, + type(current_item).__name__) + raise ValueError(msg) + + def check_length(name, value, length_min, length_max=None): """Ensure that a sized object has length within a given range. diff --git a/openmc/filter.py b/openmc/filter.py index c9ff24803..5a8190676 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -6,7 +6,8 @@ import numpy as np from openmc import Mesh from openmc.constants import * -from openmc.checkvalue import check_type +from openmc.checkvalue import check_type, check_iterable_type, \ + check_greater_than class Filter(object): """A filter used to constrain a tally to a specific criterion, e.g. only tally @@ -134,15 +135,9 @@ class Filter(object): if self.type in ['cell', 'cellborn', 'surface', 'material', 'universe', 'distribcell']: + check_iterable_type('filter bins', bins, Integral) for edge in bins: - if not isinstance(edge, Integral): - msg = 'Unable to add bin "{0}" to a "{1}" Filter since ' \ - 'it is not an integer'.format(edge, self._type) - raise ValueError(msg) - elif edge < 0: - msg = 'Unable to add bin "{0}" to a "{1}" Filter since ' \ - 'it is negative'.format(edge, self._type) - raise ValueError(msg) + check_greater_than('filter bin', edge, 0, equality=True) elif self._type in ['energy', 'energyout']: for edge in bins: @@ -185,12 +180,8 @@ class Filter(object): # FIXME @num_bins.setter def num_bins(self, num_bins): - if not isinstance(num_bins, Integral) or num_bins < 0: - msg = 'Unable to set the number of bins "{0}" for a "{1}" Filter ' \ - 'since it is not a positive ' \ - 'integer'.format(num_bins, self.type) - raise ValueError(msg) - + check_type('filter num_bins', num_bins, Integral) + check_greater_than('filter num_bins', num_bins, 0, equality=True) self._num_bins = num_bins @mesh.setter diff --git a/openmc/summary.py b/openmc/summary.py index c0673ef23..7f9d5387e 100644 --- a/openmc/summary.py +++ b/openmc/summary.py @@ -411,16 +411,70 @@ class Summary(object): if outer != -22: lattice.outer = self.universes[outer] - # Build array of Universe pointers for the Lattice - universes = \ - np.ndarray(tuple(universe_ids.shape), dtype=openmc.Universe) + # Build array of Universe pointers for the Lattice. Note that + # we need to convert between the HDF5's square array of + # (x, alpha, z) to the Python API's format of a ragged nested + # list of (z, ring, theta). + universes = [] + for z in range(lattice.num_axial): + # Add a list for this axial level. + universes.append([]) + x = lattice.num_rings - 1 + a = 2*lattice.num_rings - 2 + for r in range(lattice.num_rings - 1, 0, -1): + # Add a list for this ring. + universes[-1].append([]) - for i in range(universe_ids.shape[0]): - for j in range(universe_ids.shape[1]): - for k in range(universe_ids.shape[2]): - if universe_ids[i, j, k] != -1: - universes[i, j, k] = self.get_universe_by_id( - universe_ids[i, j, k]) + # Climb down the top-right. + for i in range(r): + universes[-1][-1].append(universe_ids[z, a, x]) + x += 1 + a -= 1 + + # Climb down the right. + for i in range(r): + universes[-1][-1].append(universe_ids[z, a, x]) + a -= 1 + + # Climb down the bottom-right. + for i in range(r): + universes[-1][-1].append(universe_ids[z, a, x]) + x -= 1 + + # Climb up the bottom-left. + for i in range(r): + universes[-1][-1].append(universe_ids[z, a, x]) + x -= 1 + a += 1 + + # Climb up the left. + for i in range(r): + universes[-1][-1].append(universe_ids[z, a, x]) + a += 1 + + # Climb up the top-left. + for i in range(r): + universes[-1][-1].append(universe_ids[z, a, x]) + x += 1 + + # Move down to the next ring. + a -= 1 + + # Convert the ids into Universe objects. + universes[-1][-1] = [self.get_universe_by_id(u_id) + for u_id in universes[-1][-1]] + + # Handle the degenerate center ring separately. + u_id = universe_ids[z, a, x] + universes[-1].append([self.get_universe_by_id(u_id)]) + + # Add the universes to the lattice. + if len(pitch) == 2: + # Lattice is 3D + lattice.universes = universes + else: + # Lattice is 2D; extract the only axial level + lattice.universes = universes[0] if offset_size > 0: lattice.offsets = offsets diff --git a/openmc/temp.py b/openmc/temp.py new file mode 100644 index 000000000..91f608299 --- /dev/null +++ b/openmc/temp.py @@ -0,0 +1,12 @@ +from checkvalue import * +from checkvalue import _isinstance + +import numpy as np + +zs = np.zeros((2,)) + +print _isinstance(zs[0], Integral) +print _isinstance(zs[0], Real) +print _isinstance(zs[0], (Integral, Real)) + +print check_iterable_type('thing', zs, (Real, Integral)) diff --git a/openmc/universe.py b/openmc/universe.py index 1a45d8666..75b673746 100644 --- a/openmc/universe.py +++ b/openmc/universe.py @@ -7,7 +7,7 @@ import sys import numpy as np import openmc -from openmc.checkvalue import check_type, check_length, check_greater_than +import openmc.checkvalue as cv if sys.version_info[0] >= 3: basestring = str @@ -111,13 +111,13 @@ class Cell(object): self._id = AUTO_CELL_ID AUTO_CELL_ID += 1 else: - check_type('cell ID', cell_id, Integral) - check_greater_than('cell ID', cell_id, 0) + cv.check_type('cell ID', cell_id, Integral) + cv.check_greater_than('cell ID', cell_id, 0) self._id = cell_id @name.setter def name(self, name): - check_type('cell name', name, basestring) + cv.check_type('cell name', name, basestring) self._name = name @fill.setter @@ -148,19 +148,19 @@ class Cell(object): @rotation.setter def rotation(self, rotation): - check_type('cell rotation', rotation, Iterable, Real) - check_length('cell rotation', rotation, 3) + cv.check_type('cell rotation', rotation, Iterable, Real) + cv.check_length('cell rotation', rotation, 3) self._rotation = rotation @translation.setter def translation(self, translation): - check_type('cell translation', translation, Iterable, Real) - check_length('cell translation', translation, 3) + cv.check_type('cell translation', translation, Iterable, Real) + cv.check_length('cell translation', translation, 3) self._translation = translation @offsets.setter def offsets(self, offsets): - check_type('cell offsets', offsets, Iterable) + cv.check_type('cell offsets', offsets, Iterable) self._offsets = offsets def add_surface(self, surface, halfspace): @@ -432,13 +432,13 @@ class Universe(object): self._id = AUTO_UNIVERSE_ID AUTO_UNIVERSE_ID += 1 else: - check_type('universe ID', universe_id, Integral) - check_greater_than('universe ID', universe_id, 0, True) + cv.check_type('universe ID', universe_id, Integral) + cv.check_greater_than('universe ID', universe_id, 0, True) self._id = universe_id @name.setter def name(self, name): - check_type('universe name', name, basestring) + cv.check_type('universe name', name, basestring) self._name = name def add_cell(self, cell): @@ -671,24 +671,25 @@ class Lattice(object): self._id = AUTO_UNIVERSE_ID AUTO_UNIVERSE_ID += 1 else: - check_type('lattice ID', lattice_id, Integral) - check_greater_than('lattice ID', lattice_id, 0) + cv.check_type('lattice ID', lattice_id, Integral) + cv.check_greater_than('lattice ID', lattice_id, 0) self._id = lattice_id @name.setter def name(self, name): - check_type('lattice name', name, basestring) + cv.check_type('lattice name', name, basestring) self._name = name @outer.setter def outer(self, outer): - check_type('outer universe', outer, Universe) + cv.check_type('outer universe', outer, Universe) self._outer = outer @universes.setter def universes(self, universes): - check_type('lattice universes', universes, Iterable) - self._universes = np.asarray(universes, dtype=Universe) + cv.check_iterable_type('lattice universes', universes, Universe, + min_depth=2, max_depth=3) + self._universes = universes def get_unique_universes(self): """Determine all unique universes in the lattice @@ -701,13 +702,21 @@ class Lattice(object): """ - unique_universes = np.unique(self._universes.ravel()) - universes = {} + univs = dict() + for k in range(len(self._universes)): + for j in range(len(self._universes[k])): + if isinstance(self._universes[k][j], Universe): + u = self._universes[k][j] + univs[u._id] = u + else: + for i in range(len(self._universes[k][j])): + u = self._universes[k][j][i] + assert isinstance(u, Universe) + univs[u._id] = u - for universe in unique_universes: - universes[universe._id] = universe + univs[self._outer._id] = self._outer - return universes + return univs def get_all_nuclides(self): """Return all nuclides contained in the lattice @@ -825,29 +834,29 @@ class RectLattice(Lattice): @dimension.setter def dimension(self, dimension): - check_type('lattice dimension', dimension, Iterable, Integral) - check_length('lattice dimension', dimension, 2, 3) + cv.check_type('lattice dimension', dimension, Iterable, Integral) + cv.check_length('lattice dimension', dimension, 2, 3) for dim in dimension: - check_greater_than('lattice dimension', dim, 0) + cv.check_greater_than('lattice dimension', dim, 0) self._dimension = dimension @lower_left.setter def lower_left(self, lower_left): - check_type('lattice lower left corner', lower_left, Iterable, Real) - check_length('lattice lower left corner', lower_left, 2, 3) + cv.check_type('lattice lower left corner', lower_left, Iterable, Real) + cv.check_length('lattice lower left corner', lower_left, 2, 3) self._lower_left = lower_left @offsets.setter def offsets(self, offsets): - check_type('lattice offsets', offsets, Iterable) + cv.check_type('lattice offsets', offsets, Iterable) self._offsets = offsets @Lattice.pitch.setter def pitch(self, pitch): - check_type('lattice pitch', pitch, Iterable, Real) - check_length('lattice pitch', pitch, 2, 3) + cv.check_type('lattice pitch', pitch, Iterable, Real) + cv.check_length('lattice pitch', pitch, 2, 3) for dim in pitch: - check_greater_than('lattice pitch', dim, 0.0) + cv.check_greater_than('lattice pitch', dim, 0.0) self._pitch = pitch def get_offset(self, path, filter_offset): @@ -1041,28 +1050,28 @@ class HexLattice(Lattice): @num_rings.setter def num_rings(self, num_rings): - check_type('number of rings', num_rings, Integral) - check_greater_than('number of rings', num_rings, 0) + cv.check_type('number of rings', num_rings, Integral) + cv.check_greater_than('number of rings', num_rings, 0) self._num_rings = num_rings @num_axial.setter def num_axial(self, num_axial): - check_type('number of axial', num_axial, Integral) - check_greater_than('number of axial', num_axial, 0) + cv.check_type('number of axial', num_axial, Integral) + cv.check_greater_than('number of axial', num_axial, 0) self._num_axial = num_axial @center.setter def center(self, center): - check_type('lattice center', center, Iterable, Real) - check_length('lattice center', center, 2, 3) + cv.check_type('lattice center', center, Iterable, Real) + cv.check_length('lattice center', center, 2, 3) self._center = center @Lattice.pitch.setter def pitch(self, pitch): - check_type('lattice pitch', pitch, Iterable, Real) - check_length('lattice pitch', pitch, 1, 2) + cv.check_type('lattice pitch', pitch, Iterable, Real) + cv.check_length('lattice pitch', pitch, 1, 2) for dim in pitch: - check_greater_than('lattice pitch', dim, 0) + cv.check_greater_than('lattice pitch', dim, 0) self._pitch = pitch @Lattice.universes.setter @@ -1091,14 +1100,14 @@ class HexLattice(Lattice): # Set the number of axial positions. if n_dims == 3: - self.num_axial = self._universes.shape[0] + self.num_axial = len(self._universes) else: self._num_axial = None # Set the number of rings and make sure this number is consistent for # all axial positions. if n_dims == 3: - self.num_rings = len(self._universes[0]) + self.num_rings = len(self._universes) for rings in self._universes: if len(rings) != self._num_rings: msg = 'HexLattice ID={0:d} has an inconsistent number of ' \ @@ -1106,7 +1115,7 @@ class HexLattice(Lattice): raise ValueError(msg) else: - self.num_rings = self._universes.shape[0] + self.num_rings = len(self._universes) # Make sure there are the correct number of elements in each ring. if n_dims == 3: diff --git a/src/hdf5_summary.F90 b/src/hdf5_summary.F90 index d4953d076..375ab7460 100644 --- a/src/hdf5_summary.F90 +++ b/src/hdf5_summary.F90 @@ -394,7 +394,7 @@ contains ! Write number of lattice cells. call su % write_data(lat % n_rings, "n_rings", & group="geometry/lattices/lattice " // trim(to_str(lat % id))) - call su % write_data(lat % n_rings, "n_axial", & + call su % write_data(lat % n_axial, "n_axial", & group="geometry/lattices/lattice " // trim(to_str(lat % id))) ! Write lattice center, pitch and outer universe. @@ -407,10 +407,10 @@ contains end if if (lat % is_3d) then - call su % write_data(lat % pitch, "pitch", length=3, & + call su % write_data(lat % pitch, "pitch", length=2, & group="geometry/lattices/lattice " // trim(to_str(lat % id))) else - call su % write_data(lat % pitch, "pitch", length=2, & + call su % write_data(lat % pitch, "pitch", length=1, & group="geometry/lattices/lattice " // trim(to_str(lat % id))) end if @@ -447,7 +447,7 @@ contains end do end do call su % write_data(lattice_universes, "universes", & - &length=(/lat % n_axial, 2*lat % n_rings-1, 2*lat % n_rings-1/), & + &length=(/2*lat % n_rings-1, 2*lat % n_rings-1, lat % n_axial/), & &group="geometry/lattices/lattice " // trim(to_str(lat % id))) deallocate(lattice_universes) end select