From 7ed1dd17e60a8a42b68547645babb76b0d966e9f Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 1 Jun 2015 22:22:53 +0700 Subject: [PATCH] Adding docstrings and updated the element, executor, filter, geometry, material, mesh, and nuclide modules. --- docs/source/pythonapi/element.rst | 1 - docs/source/pythonapi/executor.rst | 1 - docs/source/pythonapi/filter.rst | 1 - docs/source/pythonapi/geometry.rst | 1 - docs/source/pythonapi/material.rst | 1 - docs/source/pythonapi/mesh.rst | 1 - docs/source/pythonapi/nuclide.rst | 1 - openmc/element.py | 43 +++--- openmc/executor.py | 41 ++++-- openmc/filter.py | 115 ++++++++-------- openmc/geometry.py | 103 ++++++++++---- openmc/material.py | 208 ++++++++++++++++++++--------- openmc/mesh.py | 122 +++++++---------- openmc/nuclide.py | 37 ++--- 14 files changed, 401 insertions(+), 275 deletions(-) diff --git a/docs/source/pythonapi/element.rst b/docs/source/pythonapi/element.rst index 372468ffe..473cbba45 100644 --- a/docs/source/pythonapi/element.rst +++ b/docs/source/pythonapi/element.rst @@ -6,4 +6,3 @@ Element .. automodule:: openmc.element :members: - :undoc-members: diff --git a/docs/source/pythonapi/executor.rst b/docs/source/pythonapi/executor.rst index bdb46cda5..ef6693ec9 100644 --- a/docs/source/pythonapi/executor.rst +++ b/docs/source/pythonapi/executor.rst @@ -6,4 +6,3 @@ Executor .. automodule:: openmc.executor :members: - :undoc-members: diff --git a/docs/source/pythonapi/filter.rst b/docs/source/pythonapi/filter.rst index 30074f16a..f93ba5a15 100644 --- a/docs/source/pythonapi/filter.rst +++ b/docs/source/pythonapi/filter.rst @@ -6,4 +6,3 @@ Filter .. automodule:: openmc.filter :members: - :undoc-members: diff --git a/docs/source/pythonapi/geometry.rst b/docs/source/pythonapi/geometry.rst index f6ddf4f3e..6b87edb97 100644 --- a/docs/source/pythonapi/geometry.rst +++ b/docs/source/pythonapi/geometry.rst @@ -6,4 +6,3 @@ Geometry .. automodule:: openmc.geometry :members: - :undoc-members: diff --git a/docs/source/pythonapi/material.rst b/docs/source/pythonapi/material.rst index c95c371ab..16a3af701 100644 --- a/docs/source/pythonapi/material.rst +++ b/docs/source/pythonapi/material.rst @@ -6,4 +6,3 @@ Materials .. automodule:: openmc.material :members: - :undoc-members: diff --git a/docs/source/pythonapi/mesh.rst b/docs/source/pythonapi/mesh.rst index b68e8f1a1..dbecd7c31 100644 --- a/docs/source/pythonapi/mesh.rst +++ b/docs/source/pythonapi/mesh.rst @@ -6,4 +6,3 @@ Mesh .. automodule:: openmc.mesh :members: - :undoc-members: diff --git a/docs/source/pythonapi/nuclide.rst b/docs/source/pythonapi/nuclide.rst index 75ee6e705..9e3214e92 100644 --- a/docs/source/pythonapi/nuclide.rst +++ b/docs/source/pythonapi/nuclide.rst @@ -6,4 +6,3 @@ Nuclide .. automodule:: openmc.nuclide :members: - :undoc-members: diff --git a/openmc/element.py b/openmc/element.py index b6d5775ee..e87fa4207 100644 --- a/openmc/element.py +++ b/openmc/element.py @@ -1,58 +1,63 @@ from openmc.checkvalue import * class Element(object): + """A natural element used in a material via . Internally, OpenMC will + expand the natural element into isotopes based on the known natural + abundances. + + Parameters + ---------- + name : str + Chemical symbol of the element, e.g. Pu + xs : str + Cross section identifier, e.g. 71c + + Attributes + ---------- + name : str + Chemical symbol of the element, e.g. Pu + xs : str + Cross section identifier, e.g. 71c + + """ def __init__(self, name='', xs=None): - # Initialize class attributes self._name = '' self._xs = None - # Set the Material class attributes + # Set class attributes self.name = name if not xs is None: self.xs = xs - def __eq__(self, element2): - # Check type if not isinstance(element2, Element): return False - # Check name + # Check name and xs if self._name != element2._name: return False - - # Check xs elif self._xs != element2._xs: return False - else: return True - def __hash__(self): - hashable = [] - hashable.append(self._name) - hashable.append(self._xs) - return hash(tuple(hashable)) - + return hash((self._name, self._xs)) @property def xs(self): return self._xs - @property def name(self): return self._name - @xs.setter def xs(self, xs): - if not is_string(xs): msg = 'Unable to set cross-section identifier xs for Element ' \ 'with a non-string value {0}'.format(xs) @@ -60,10 +65,8 @@ class Element(object): self._xs = xs - @name.setter def name(self, name): - if not is_string(name): msg = 'Unable to set name for Element with a non-string ' \ 'value {0}'.format(name) @@ -71,9 +74,7 @@ class Element(object): self._name = name - def __repr__(self): - string = 'Element - {0}\n'.format(self._name) string += '{0: <16}{1}{2}\n'.format('\tXS', '=\t', self._xs) return string diff --git a/openmc/executor.py b/openmc/executor.py index d8f52bbec..ed3ec14ae 100644 --- a/openmc/executor.py +++ b/openmc/executor.py @@ -1,3 +1,4 @@ +from __future__ import print_function import subprocess import os @@ -5,37 +6,39 @@ from openmc.checkvalue import * class Executor(object): + """Control execution of OpenMC + Attributes + ---------- + working_directory : str + Path to working directory to run in + + """ def __init__(self): self._working_directory = '.' - def _run_openmc(self, command, output): - # Launch a subprocess to run OpenMC - p = subprocess.Popen(command, shell=True, + p = subprocess.Popen(command, shell=True, cwd=self._working_directory, stdout=subprocess.PIPE) # Capture and re-print OpenMC output in real-time while (True and output): line = p.stdout.readline() - print(line), + print(line, end='') # If OpenMC is finished, break loop if line == '' and p.poll() != None: break - @property def working_directory(self): return self._working_directory - @working_directory.setter def working_directory(self, working_directory): - if not is_string(working_directory): msg = 'Unable to set Executor\'s working directory to {0} ' \ 'since it is not a string'.format(working_directory) @@ -48,14 +51,34 @@ class Executor(object): self._working_directory = working_directory - def plot_geometry(self, output=True): - self._run_openmc('openmc -p', output) + """Run OpenMC in plotting mode""" + self._run_openmc('openmc -p', output) def run_simulation(self, particles=None, threads=None, geometry_debug=False, restart_file=None, tracks=False, mpi_procs=1, output=True): + """Run an OpenMC simulation. + + Parameters + ---------- + particles : int + Number of particles to simulate per generation + threads : int + Number of OpenMP threads + geometry_debug : bool + Turn on geometry debugging during simulation + restart_file : str + Path to restart file to use + tracks : bool + Write tracks for all particles + mpi_procs : int + Number of MPI processes + output : bool + Capture OpenMC output from standard out + + """ post_args = ' ' pre_args = '' diff --git a/openmc/filter.py b/openmc/filter.py index 089f9f18a..387f7eecb 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -6,10 +6,30 @@ from openmc.constants import * class Filter(object): + """A filter used to constrain a tally to a specific criterion, e.g. only tally + events when the particle is in a certain cell and energy range. + + Parameters + ---------- + type : str + The type of the tally filter. Acceptable values are "universe", + "material", "cell", "cellborn", "surface", "mesh", "energy", + "energyout", and "distribcell". + bins : int or list of int or list of float or ndarray + The bins for the filter. This takes on different meaning for different + filters. + + Attributes + ---------- + type : str + The type of the tally filter. + bins : int or list of int or list of float or ndarray + The bins for the filter + + """ # Initialize Filter class attributes def __init__(self, type=None, bins=None): - self.type = type self._num_bins = 0 self.bins = bins @@ -17,9 +37,7 @@ class Filter(object): self._offset = -1 self._stride = None - def __eq__(self, filter2): - # Check type if self._type != filter2._type: return False @@ -37,19 +55,14 @@ class Filter(object): def __hash__(self): - hashable = [] - hashable.append(self._type) - hashable.append(self._bins) - return hash(tuple(hashable)) + return hash((self._type, self._bins)) def __deepcopy__(self, memo): - existing = memo.get(id(self)) # If this is the first time we have tried to copy this object, create a copy if existing is None: - clone = type(self).__new__(type(self)) clone._type = self._type clone._bins = copy.deepcopy(self._bins, memo) @@ -66,43 +79,34 @@ class Filter(object): else: return existing - @property def type(self): return self._type - @property def bins(self): return self._bins - @property def num_bins(self): return self._num_bins - @property def mesh(self): return self._mesh - @property def offset(self): return self._offset - @property def stride(self): return self._stride - @type.setter def type(self, type): - if type is None: self._type = type - elif not type in FILTER_TYPES.values(): msg = 'Unable to set Filter type to "{0}" since it is not one ' \ 'of the supported types'.format(type) @@ -110,13 +114,10 @@ class Filter(object): self._type = type - @bins.setter def bins(self, bins): - if bins is None: self.num_bins = 0 - elif self._type is None: msg = 'Unable to set bins for Filter to "{0}" since ' \ 'the Filter type has not yet been set'.format(bins) @@ -132,30 +133,23 @@ class Filter(object): if self._type in ['cell', 'cellborn', 'surface', 'material', 'universe', 'distribcell']: - for edge in bins: - if not is_integer(edge): msg = 'Unable to add bin "{0}" to a {1} Filter since ' \ 'it is a non-integer'.format(edge, self._type) raise ValueError(msg) - elif edge < 0: msg = 'Unable to add bin "{0}" to a {1} Filter since ' \ 'it is a negative integer'.format(edge, self._type) raise ValueError(msg) - elif self._type in ['energy', 'energyout']: - for edge in bins: - if not is_integer(edge) and not is_float(edge): msg = 'Unable to add bin edge "{0}" to a {1} Filter ' \ 'since it is a non-integer or floating point ' \ 'value'.format(edge, self._type) raise ValueError(msg) - elif edge < 0.: msg = 'Unable to add bin edge "{0}" to a {1} Filter ' \ 'since it is a negative value'.format(edge, self._type) @@ -163,27 +157,22 @@ class Filter(object): # Check that bin edges are monotonically increasing for index in range(len(bins)): - if index > 0 and bins[index] < bins[index-1]: msg = 'Unable to add bin edges "{0}" to a {1} Filter ' \ 'since they are not monotonically ' \ 'increasing'.format(bins, self._type) raise ValueError(msg) - # mesh filters elif self._type == 'mesh': - if not len(bins) == 1: msg = 'Unable to add bins "{0}" to a mesh Filter since ' \ 'only a single mesh can be used per tally'.format(bins) raise ValueError(msg) - elif not is_integer(bins[0]): msg = 'Unable to add bin "{0}" to mesh Filter since it ' \ 'is a non-integer'.format(bins[0]) raise ValueError(msg) - elif bins[0] < 0: msg = 'Unable to add bin "{0}" to mesh Filter since it ' \ 'is a negative integer'.format(bins[0]) @@ -192,11 +181,9 @@ class Filter(object): # If all error checks passed, add bin edges self._bins = bins - # FIXME @num_bins.setter def num_bins(self, num_bins): - if not is_integer(num_bins) or num_bins < 0: msg = 'Unable to set the number of bins "{0}" for a {1} Filter ' \ 'since it is not a positive ' \ @@ -205,10 +192,8 @@ class Filter(object): self._num_bins = num_bins - @mesh.setter def mesh(self, mesh): - if not isinstance(mesh, Mesh): msg = 'Unable to set Mesh to "{0}" for Filter since it is not a ' \ 'Mesh object'.format(mesh) @@ -218,10 +203,8 @@ class Filter(object): self.type = 'mesh' self.bins = self._mesh._id - @offset.setter def offset(self, offset): - if not is_integer(offset): msg = 'Unable to set offset "{0}" for a {1} Filter since it is a ' \ 'non-integer value'.format(offset, self._type) @@ -229,10 +212,8 @@ class Filter(object): self._offset = offset - @stride.setter def stride(self, stride): - if not is_integer(stride): msg = 'Unable to set stride "{0}" for a {1} Filter since it is a ' \ 'non-integer value'.format(stride, self._type) @@ -247,6 +228,19 @@ class Filter(object): def can_merge(self, filter): + """Determine if filter can be merged with another. + + Parameters + ---------- + filter : Filter + Filter to compare with + + Returns + ------- + bool + Whether the filter can be merged + + """ if not isinstance(filter, Filter): return False @@ -270,8 +264,20 @@ class Filter(object): else: return True - def merge(self, filter): + """Merge this filter with another. + + Parameters + ---------- + filter : Filter + Filter to merge with + + Returns + ------- + merged_filter : Filter + Filter resulting from the merge + + """ if not self.can_merge(filter): msg = 'Unable to merge {0} with {1} filters'.format(self._type, filter._type) @@ -287,33 +293,32 @@ class Filter(object): return merged_filter - def get_bin_index(self, filter_bin): """Returns the index in the Filter for some bin. Parameters ---------- - filter_bin : int, tuple - The bin is the integer ID for 'material', 'surface', 'cell', - 'cellborn', and 'universe' Filters. The bin is an integer for - the cell instance ID for 'distribcell' Filters. The bin is - a 2-tuple of floats for 'energy' and 'energyout' filters - corresponding to the energy boundaries of the bin of interest. - The bin is a (x,y,z) 3-tuple for 'mesh' filters corresponding to - the mesh cell of interest. - + filter_bin : int or tuple + The bin is the integer ID for 'material', 'surface', 'cell', + 'cellborn', and 'universe' Filters. The bin is an integer for the + cell instance ID for 'distribcell' Filters. The bin is a 2-tuple of + floats for 'energy' and 'energyout' filters corresponding to the + energy boundaries of the bin of interest. The bin is a (x,y,z) + 3-tuple for 'mesh' filters corresponding to the mesh cell of + interest. + Returns ------- + filter_index : int The index in the Tally data array for this filter bin. - """ + """ # FIXME: This does not work for distribcells!!! try: # Filter bins for a mesh are an (x,y,z) tuple if self.type == 'mesh': - # Convert (x,y,z) to a single bin -- this is similar to # subroutine mesh_indices_to_bin in openmc/src/mesh.F90. if (len(self.mesh.dimension) == 3): @@ -350,9 +355,7 @@ class Filter(object): return filter_index - def __repr__(self): - string = 'Filter\n' string += '{0: <16}{1}{2}\n'.format('\tType', '=\t', self._type) string += '{0: <16}{1}{2}\n'.format('\tBins', '=\t', self._bins) diff --git a/openmc/geometry.py b/openmc/geometry.py index 84fb6005b..d6c9140bb 100644 --- a/openmc/geometry.py +++ b/openmc/geometry.py @@ -12,19 +12,24 @@ def reset_auto_ids(): class Geometry(object): + """Geometry representing a collection of surfaces, cells, and universes. + + Attributes + ---------- + root_universe : openmc.universe.Universe + Root universe which contains all others + + """ def __init__(self): - # Initialize Geometry class attributes self._root_universe = None self._offsets = {} - @property def root_universe(self): return self._root_universe - @root_universe.setter def root_universe(self, root_universe): @@ -34,7 +39,7 @@ class Geometry(object): raise ValueError(msg) elif root_universe._id != 0: - msg = 'Unable to add root Universe {0} to Geometry since ' \ + msg = 'Unable to add root Universe {0} to Geometry since ' \ 'it has ID={1} instead of ' \ 'ID=0'.format(root_universe, root_universe._id) raise ValueError(msg) @@ -43,22 +48,25 @@ class Geometry(object): def get_offset(self, path, filter_offset): - """ - Returns the corresponding location in the results array for a given + """Returns the corresponding location in the results array for a given path and filter number. Parameters ---------- path : list - A list of IDs that form the path to the target. It should begin - with 0 for the base universe, and should cover every universe, - cell, and lattice passed through. For the case of the lattice, - a tuple should be provided to indicate which coordinates in the - lattice should be entered. This should be in the - form: (lat_id, i_x, i_y, i_z) + A list of IDs that form the path to the target. It should begin with + 0 for the base universe, and should cover every universe, cell, and + lattice passed through. For the case of the lattice, a tuple should + be provided to indicate which coordinates in the lattice should be + entered. This should be in the form: (lat_id, i_x, i_y, i_z) filter_offset : int - An integer that specifies which offset map the filter is using + An integer that specifies which offset map the filter is using + + Returns + ------- + offset : int + Location in the results array for the path and filter """ @@ -74,16 +82,39 @@ class Geometry(object): # Return the final offset return offset - def get_all_cells(self): + """Return all cells defined + + Returns + ------- + list of openmc.universe.Cell + Cells in the geometry + + """ + return self._root_universe.get_all_cells() - def get_all_universes(self): + """Return all universes defined + + Returns + ------- + list of openmc.universe.Universe + Universes in the geometry + + """ + return self._root_universe.get_all_universes() - def get_all_nuclides(self): + """Return all nuclides assigned to a material in the geometry + + Returns + ------- + list of openmc.nuclide.Nuclide + Nuclides in the geometry + + """ nuclides = {} materials = self.get_all_materials() @@ -93,8 +124,15 @@ class Geometry(object): return nuclides - def get_all_materials(self): + """Return all materials assigned to a cell + + Returns + ------- + list of openmc.material.Material + Materials in the geometry + + """ material_cells = self.get_all_material_cells() materials = set() @@ -104,9 +142,7 @@ class Geometry(object): return list(materials) - def get_all_material_cells(self): - all_cells = self.get_all_cells() material_cells = set() @@ -116,16 +152,21 @@ class Geometry(object): return list(material_cells) - def get_all_material_universes(self): + """Return all universes composed of at least one non-fill cell + + Returns + ------- + list of openmc.universe.Universe + Universes with non-fill cells + + """ all_universes = self.get_all_universes() material_universes = set() for universe_id, universe in all_universes.items(): - cells = universe._cells - for cell_id, cell in cells.items(): if cell._type == 'normal': material_universes.add(universe) @@ -133,24 +174,28 @@ class Geometry(object): return list(material_universes) - class GeometryFile(object): + """Geometry file used for an OpenMC simulation. Corresponds directly to the + geometry.xml input file. + + Attributes + ---------- + geometry : Geometry + The geometry to be used + + """ def __init__(self): - # Initialize GeometryFile class attributes self._geometry = None self._geometry_file = ET.Element("geometry") - @property def geometry(self): return self._geometry - @geometry.setter def geometry(self, geometry): - if not isinstance(geometry, Geometry): msg = 'Unable to set the Geometry to {0} for the GeometryFile ' \ 'since it is not a Geometry object'.format(geometry) @@ -158,8 +203,10 @@ class GeometryFile(object): self._geometry = geometry - def export_to_xml(self): + """Create a geometry.xml file that can be used for a simulation. + + """ root_universe = self._geometry._root_universe root_universe.create_xml_subelement(self._geometry_file) diff --git a/openmc/material.py b/openmc/material.py index da205dc5b..4ba5120cd 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -23,20 +23,34 @@ def reset_auto_material_id(): # Units for density supported by OpenMC DENSITY_UNITS = ['g/cm3', 'g/cc', 'kg/cm3', 'at/b-cm', 'at/cm3', 'sum'] -# ENDF temperatures -ENDF_TEMPS = np.array([300, 600, 700, 900, 1200, 1500]) - -# ENDF ZAIDs -ENDF_ZAIDS = np.array(['70c', '71c', '72c', '73c', '74c']) - # Constant for density when not needed NO_DENSITY = 99999. class Material(object): + """A material composed of a collection of nuclides/elements that can be assigned + to a region of space. + + Parameters + ---------- + material_id : int + Unique identifier for the material + name : str + Name of the material + + Attributes + ---------- + id : int + Unique identifier for the material + density : float + Density of the material (units defined separately) + density_units : str + Units used for `density`. Can be one of 'g/cm3', 'g/cc', 'kg/cm3', + 'atom/b-cm', 'atom/cm3', or 'sum'. + + """ def __init__(self, material_id=None, name=''): - # Initialize class attributes self.id = material_id self.name = name @@ -62,40 +76,32 @@ class Material(object): # If specified, this file will be used instead of composition values self._distrib_otf_file = None - @property def id(self): return self._id - @property def name(self): return self._name - @property def density(self): return self._density - @property def density_units(self): return self._density_units - @property def convert_to_distrib_comps(self): return self._convert_to_distrib_comps - @property def distrib_otf_file(self): return self._distrib_otf_file - @id.setter def id(self, material_id): - global AUTO_MATERIAL_ID, MATERIAL_IDS # If the Material already has an ID, remove it from global list @@ -127,20 +133,26 @@ class Material(object): self._id = material_id MATERIAL_IDS.append(material_id) - @name.setter def name(self, name): - if not is_string(name): msg = 'Unable to set name for Material ID={0} with a non-string ' \ 'value {1}'.format(self._id, name) raise ValueError(msg) - else: self._name = name - def set_density(self, units, density=NO_DENSITY): + """Set the density of the material + + Parameters + ---------- + units : str + Physical units of density + density : float, optional + Value of the density + + """ if not is_float(density): msg = 'Unable to set the density for Material ID={0} to a ' \ @@ -161,10 +173,8 @@ class Material(object): self._density = density self._density_units = units - @distrib_otf_file.setter def distrib_otf_file(self, filename): - # TODO: remove this when distributed materials are merged warnings.warn('This feature is not yet implemented in a release ' \ 'version of openmc') @@ -176,10 +186,8 @@ class Material(object): self._distrib_otf_file = filename - @convert_to_distrib_comps.setter def convert_to_distrib_comps(self): - # TODO: remove this when distributed materials are merged warnings.warn('This feature is not yet implemented in a release ' \ 'version of openmc') @@ -188,6 +196,18 @@ class Material(object): def add_nuclide(self, nuclide, percent, percent_type='ao'): + """Add a nuclide to the material + + Parameters + ---------- + nuclide : str or openmc.nuclide.Nuclide + Nuclide to add + percent : float + Atom or weight percent + percent_type : str + 'ao' for atom percent and 'wo' for weight percent + + """ if not isinstance(nuclide, (openmc.Nuclide, str)): msg = 'Unable to add a Nuclide to Material ID={0} with a ' \ @@ -215,6 +235,14 @@ class Material(object): def remove_nuclide(self, nuclide): + """Remove a nuclide from the material + + Parameters + ---------- + nuclide : openmc.nuclide.Nuclide + Nuclide to remove + + """ if not isinstance(nuclide, openmc.Nuclide): msg = 'Unable to remove a Nuclide {0} in Material ID={1} ' \ @@ -225,8 +253,19 @@ class Material(object): if nuclide._name in self._nuclides: del self._nuclides[nuclide._name] - def add_element(self, element, percent, percent_type='ao'): + """Add a natural element to the material + + Parameters + ---------- + element : openmc.element.Element + Element to add + percent : float + Atom or weight percent + percent_type : str + 'ao' for atom percent and 'wo' for weight percent + + """ if not isinstance(element, openmc.Element): msg = 'Unable to add an Element to Material ID={0} with a ' \ @@ -250,6 +289,14 @@ class Material(object): def remove_element(self, element): + """Remove a natural element from the material + + Parameters + ---------- + element : openmc.element.Element + Element to remove + + """ # If the Material contains the Element, delete it if element._name in self._elements: @@ -257,6 +304,16 @@ class Material(object): def add_s_alpha_beta(self, name, xs): + r"""Add an :math:`S(\alpha,\beta)` table to the material + + Parameters + ---------- + name : str + Name of the :math:`S(\alpha,\beta)` table + xs : str + Cross section identifier, e.g. '71t' + + """ if not is_string(name): msg = 'Unable to add an S(a,b) table to Material ID={0} with a ' \ @@ -272,6 +329,15 @@ class Material(object): def get_all_nuclides(self): + """Returns all nuclides in the material + + Returns + ------- + nuclides : dict + Dictionary whose keys are nuclide names and values are 2-tuples of + (nuclide, density) + + """ nuclides = {} @@ -282,9 +348,7 @@ class Material(object): return nuclides - def _repr__(self): - string = 'Material\n' string += '{0: <16}{1}{2}\n'.format('\tID', '=\t', self._id) string += '{0: <16}{1}{2}\n'.format('\tName', '=\t', self._name) @@ -316,9 +380,7 @@ class Material(object): return string - - def get_nuclide_xml(self, nuclide, distrib=False): - + def _get_nuclide_xml(self, nuclide, distrib=False): xml_element = ET.Element("nuclide") xml_element.set("name", nuclide[0]._name) @@ -333,9 +395,7 @@ class Material(object): return xml_element - - def get_element_xml(self, element, distrib=False): - + def _get_element_xml(self, element, distrib=False): xml_element = ET.Element("element") xml_element.set("name", str(element[0]._name)) @@ -347,28 +407,31 @@ class Material(object): return xml_element - - def get_nuclides_xml(self, nuclides, distrib=False): - + def _get_nuclides_xml(self, nuclides, distrib=False): xml_elements = [] for nuclide in nuclides.values(): - xml_elements.append(self.get_nuclide_xml(nuclide, distrib)) + xml_elements.append(self._get_nuclide_xml(nuclide, distrib)) return xml_elements - - def get_elements_xml(self, elements, distrib=False): - + def _get_elements_xml(self, elements, distrib=False): xml_elements = [] for element in elements.values(): - xml_elements.append(self.get_element_xml(element, distrib)) + xml_elements.append(self._get_element_xml(element, distrib)) return xml_elements - def get_material_xml(self): + """Return XML representation of the material + + Returns + ------- + element : xml.etree.ElementTree.Element + XML element containing material data + + """ # Create Material XML element element = ET.Element("material") @@ -384,19 +447,17 @@ class Material(object): subelement.set("units", self._density_units) if not self._convert_to_distrib_comps: - # Create nuclide XML subelements - subelements = self.get_nuclides_xml(self._nuclides) + subelements = self._get_nuclides_xml(self._nuclides) for subelement in subelements: element.append(subelement) # Create element XML subelements - subelements = self.get_elements_xml(self._elements) + subelements = self._get_elements_xml(self._elements) for subelement in subelements: element.append(subelement) else: - subelement = ET.SubElement(element, "compositions") comps = [] @@ -409,29 +470,24 @@ class Material(object): raise ValueError(msg) comps.append(per) - if self._distrib_otf_file is None: - # Create values and units subelements subsubelement = ET.SubElement(subelement, "values") subsubelement.text = ' '.join([str(c) for c in comps]) subsubelement = ET.SubElement(subelement, "units") subsubelement.text = dist_per_type - else: - # Specify the materials file subsubelement = ET.SubElement(subelement, "otf_file_path") subsubelement.text = self._distrib_otf_file - # Create nuclide XML subelements subelements = self.get_nuclides_xml(self._nuclides, distrib=True) for subelement_nuc in subelements: subelement.append(subelement_nuc) # Create element XML subelements - subelements = self.get_elements_xml(self._elements, distrib=True) + subelements = self._get_elements_xml(self._elements, distrib=True) for subelement_ele in subelements: subelement.append(subelement_ele) @@ -445,31 +501,44 @@ class Material(object): class MaterialsFile(object): + """Materials file used for an OpenMC simulation. Corresponds directly to the + materials.xml input file. + + Attributes + ---------- + default_xs : str + The default cross section identifier applied to a nuclide when none is + specified + + """ def __init__(self): - # Initialize MaterialsFile class attributes self._materials = [] self._default_xs = None self._materials_file = ET.Element("materials") - @property def default_xs(self): return self._default_xs - @default_xs.setter def default_xs(self, xs): - if not is_string(xs): msg = 'Unable to set default xs to a non-string value'.format(xs) raise ValueError(msg) self._default_xs = xs - def add_material(self, material): + """Add a material to the file. + + Parameters + ---------- + material : Material + Material to add + + """ if not isinstance(material, Material): msg = 'Unable to add a non-Material {0} to the ' \ @@ -480,6 +549,14 @@ class MaterialsFile(object): def add_materials(self, materials): + """Add multiple materials to the file. + + Parameters + ---------- + materials : tuple or list of Material + Materials to add + + """ if not isinstance(materials, (tuple, list, MappingView)): msg = 'Unable to create OpenMC materials.xml file from {0} which ' \ @@ -489,8 +566,15 @@ class MaterialsFile(object): for material in materials: self.add_material(material) + def remove_material(self, material): + """Remove a material from the file - def remove_materials(self, material): + Parameters + ---------- + material : Material + Material to remove + + """ if not isinstance(material, Material): msg = 'Unable to remove a non-Material {0} from the ' \ @@ -499,9 +583,7 @@ class MaterialsFile(object): self._materials.remove(material) - - def create_material_subelements(self): - + def _create_material_subelements(self): subelement = ET.SubElement(self._materials_file, "default_xs") if not self._default_xs is None: @@ -511,10 +593,12 @@ class MaterialsFile(object): xml_element = material.get_material_xml() self._materials_file.append(xml_element) - def export_to_xml(self): + """Create a materials.xml file that can be used for a simulation. - self.create_material_subelements() + """ + + self._create_material_subelements() # Clean the indentation in the file to be user-readable sort_xml_elements(self._materials_file) diff --git a/openmc/mesh.py b/openmc/mesh.py index 0006bd804..960a5e578 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -14,9 +14,37 @@ def reset_auto_mesh_id(): class Mesh(object): + """A structured Cartesian mesh in two or three dimensions + + Parameters + ---------- + mesh_id : int + Unique identifier for the mesh + name : str + Name of the mesh + + Attributes + ---------- + id : int + Unique identifier for the mesh + name : str + Name of the mesh + type : str + Type of the mesh + dimension : tuple or list or ndarray + The number of mesh cells in each direction. + lower_left : tuple or list or ndarray + The lower-left corner of the structured mesh. If only two coordinate are + given, it is assumed that the mesh is an x-y mesh. + upper_right : tuple or list or ndarray + The upper-right corner of the structrued mesh. If only two coordinate + are given, it is assumed that the mesh is an x-y mesh. + width : tuple or list or ndarray + The width of mesh cells in each direction. + + """ def __init__(self, mesh_id=None, name=''): - # Initialize Mesh class attributes self.id = mesh_id self.name = name @@ -26,9 +54,7 @@ class Mesh(object): self._upper_right = None self._width = None - def __eq__(self, mesh2): - # Check type if self._type != mesh2._type: return False @@ -49,14 +75,11 @@ class Mesh(object): else: return True - def __deepcopy__(self, memo): - existing = memo.get(id(self)) # If this is the first time we have tried to copy this object, create a copy if existing is None: - clone = type(self).__new__(type(self)) clone._id = self._id clone._name = self._name @@ -74,50 +97,40 @@ class Mesh(object): else: return existing - @property def id(self): return self._id - @property def name(self): return self._name - @property def type(self): return self._type - @property def dimension(self): return self._dimension - @property def lower_left(self): return self._lower_left - @property def upper_right(self): return self._upper_right - @property def width(self): return self._width - @property def num_mesh_cells(self): return np.prod(self._dimension) - @id.setter def id(self, mesh_id): - if mesh_id is None: global AUTO_MESH_ID self._id = AUTO_MESH_ID @@ -136,27 +149,21 @@ class Mesh(object): else: self._id = mesh_id - @name.setter def name(self, name): - if not is_string(name): msg = 'Unable to set name for Mesh ID={0} with a non-string ' \ 'value {1}'.format(self._id, name) raise ValueError(msg) - else: self._name = name - @type.setter def type(self, type): - if not is_string(type): msg = 'Unable to set Mesh ID={0} for type {1} which is not ' \ 'a string'.format(self._id, type) raise ValueError(msg) - elif not type in ['rectangular', 'hexagonal']: msg = 'Unable to set Mesh ID={0} for type {1} which since ' \ 'only rectangular and hexagonal meshes are ' \ @@ -165,23 +172,19 @@ class Mesh(object): self._type = type - @dimension.setter def dimension(self, dimension): - if not isinstance(dimension, (tuple, list, np.ndarray)): msg = 'Unable to set Mesh ID={0} with dimension {1} which is ' \ 'not a Python list, tuple or NumPy ' \ 'array'.format(self._id, dimension) raise ValueError(msg) - elif len(dimension) != 2 and len(dimension) != 3: msg = 'Unable to set Mesh ID={0} with dimension {1} since it ' \ 'must include 2 or 3 dimensions'.format(self._id, dimension) raise ValueError(msg) for dim in dimension: - if not is_integer(dim): msg = 'Unable to set Mesh ID={0} with dimension {1} which ' \ 'is a non-integer'.format(self._id, dim) @@ -189,10 +192,8 @@ class Mesh(object): self._dimension = dimension - @lower_left.setter def lower_left(self, lower_left): - if not isinstance(lower_left, (tuple, list, np.ndarray)): msg = 'Unable to set Mesh ID={0} with lower_left {1} which is ' \ 'not a Python list, tuple or NumPy ' \ @@ -205,7 +206,6 @@ class Mesh(object): raise ValueError(msg) for coord in lower_left: - if not is_integer(coord) and not is_float(coord): msg = 'Unable to set Mesh ID={0} with lower_left {1} which ' \ 'is neither neither an integer nor a floating point ' \ @@ -214,10 +214,8 @@ class Mesh(object): self._lower_left = lower_left - @upper_right.setter def upper_right(self, upper_right): - if not isinstance(upper_right, (tuple, list, np.ndarray)): msg = 'Unable to set Mesh ID={0} with upper_right {1} which ' \ 'is not a Python list, tuple or NumPy ' \ @@ -230,7 +228,6 @@ class Mesh(object): raise ValueError(msg) for coord in upper_right: - if not is_integer(coord) and not is_float(coord): msg = 'Unable to set Mesh ID={0} with upper_right {1} which ' \ 'is neither an integer nor a floating point ' \ @@ -239,12 +236,9 @@ class Mesh(object): self._upper_right = upper_right - @width.setter def width(self, width): - if not width is None: - if not isinstance(width, (tuple, list, np.ndarray)): msg = 'Unable to set Mesh ID={0} with width {1} which ' \ 'is not a Python list, tuple or NumPy ' \ @@ -257,7 +251,6 @@ class Mesh(object): raise ValueError(msg) for dim in width: - if not is_integer(dim) and not is_float(dim): msg = 'Unable to set Mesh ID={0} with width {1} which is ' \ 'neither an integer nor a floating point ' \ @@ -266,9 +259,7 @@ class Mesh(object): self._width = width - def __repr__(self): - string = 'Mesh\n' string += '{0: <16}{1}{2}\n'.format('\tID', '=\t', self._id) string += '{0: <16}{1}{2}\n'.format('\tName', '=\t', self._name) @@ -279,53 +270,32 @@ class Mesh(object): string += '{0: <16}{1}{2}\n'.format('\tPixels', '=\t', self._width) return string - def get_mesh_xml(self): + """Return XML representation of the mesh + + Returns + ------- + element : xml.etree.ElementTree.Element + XML element containing mesh data + + """ element = ET.Element("mesh") element.set("id", str(self._id)) element.set("type", self._type) - if len(self._dimension) == 2: - subelement = ET.SubElement(element, "dimension") - subelement.text = '{0} {1}'.format(self._dimension[0], - self._dimension[1]) - else: - subelement = ET.SubElement(element, "dimension") - subelement.text = '{0} {1} {2}'.format(self._dimension[0], - self._dimension[1], - self._dimension[2]) + subelement = ET.SubElement(element, "dimension") + subelement.text = ' '.join(map(str, self._dimension)) - if len(self._lower_left) == 2: - subelement = ET.SubElement(element, "lower_left") - subelement.text = '{0} {1}'.format(self._lower_left[0], - self._lower_left[1]) - else: - subelement = ET.SubElement(element, "lower_left") - subelement.text = '{0} {1} {2}'.format(self._lower_left[0], - self._lower_left[1], - self._lower_left[2]) + subelement = ET.SubElement(element, "lower_left") + subelement.text = ' '.join(map(str, self._lower_left)) if not self._upper_right is None: - if len(self._upper_right) == 2: - subelement = ET.SubElement(element, "upper_right") - subelement.text = '{0} {1}'.format(self._upper_right[0], - self._upper_right[1]) - else: - subelement = ET.SubElement(element, "upper_right") - subelement.text = '{0} {1} {2}'.format(self._upper_right[0], - self._upper_right[1], - self._upper_right[2]) + subelement = ET.SubElement(element, "upper_right") + subelement.text = ' '.join(map(str, self._upper_right)) if not self._width is None: - if len(self._width) == 2: - subelement = ET.SubElement(element, "width") - subelement.text = '{0} {1}'.format(self._width[0], - self._width[1]) - else: - subelement = ET.SubElement(element, "width") - subelement.text = '{0} {1} {2}'.format(self._width[0], - self._width[1], - self._width[2]) + subelement = ET.SubElement(element, "width") + subelement.text = ' '.join(map(str, self._width)) - return element \ No newline at end of file + return element diff --git a/openmc/nuclide.py b/openmc/nuclide.py index c2287b23d..ffce801de 100644 --- a/openmc/nuclide.py +++ b/openmc/nuclide.py @@ -2,9 +2,28 @@ from openmc.checkvalue import * class Nuclide(object): + """A nuclide that can be used in a material. + + Parameters + ---------- + name : str + Name of the nuclide, e.g. U-235 + xs : str + Cross section identifier, e.g. 71c + + Attributes + ---------- + name : str + Name of the nuclide, e.g. U-235 + xs : str + Cross section identifier, e.g. 71c + zaid : int + 1000*(atomic number) + mass number. As an example, the zaid of U-235 + would be 92235. + + """ def __init__(self, name='', xs=None): - # Initialize class attributes self._name = '' self._xs = None @@ -16,9 +35,7 @@ class Nuclide(object): if not xs is None: self.xs = xs - def __eq__(self, nuclide2): - # Check type if not isinstance(nuclide2, Nuclide): return False @@ -34,29 +51,23 @@ class Nuclide(object): else: return True - def __hash__(self): return hash((self._name, self._xs)) - @property def name(self): return self._name - @property def xs(self): return self._xs - @property def zaid(self): return self._zaid - @name.setter def name(self, name): - if not is_string(name): msg = 'Unable to set name for Nuclide with a non-string ' \ 'value {0}'.format(name) @@ -64,10 +75,8 @@ class Nuclide(object): self._name = name - @xs.setter def xs(self, xs): - if not is_string(xs): msg = 'Unable to set cross-section identifier xs for Nuclide ' \ 'with a non-string value {0}'.format(xs) @@ -75,10 +84,8 @@ class Nuclide(object): self._xs = xs - @zaid.setter def zaid(self, zaid): - if not is_integer(zaid): msg = 'Unable to set zaid for Nuclide ' \ 'with a non-integer {0}'.format(zaid) @@ -86,11 +93,9 @@ class Nuclide(object): self._zaid = zaid - def __repr__(self): - string = 'Nuclide - {0}\n'.format(self._name) string += '{0: <16}{1}{2}\n'.format('\tXS', '=\t', self._xs) if self._zaid is not None: string += '{0: <16}{1}{2}\n'.format('\tZAID', '=\t', self._zaid) - return string \ No newline at end of file + return string