diff --git a/examples/python/basic/build-xml.py b/examples/python/basic/build-xml.py index 488603d33..adb790268 100644 --- a/examples/python/basic/build-xml.py +++ b/examples/python/basic/build-xml.py @@ -1,4 +1,6 @@ import openmc +from openmc.source import Source +from openmc.stats import SpatialBox ############################################################################### # Simulation Input File Parameters @@ -92,7 +94,7 @@ settings_file = openmc.SettingsFile() settings_file.batches = batches settings_file.inactive = inactive settings_file.particles = particles -settings_file.set_source_space('box', [-4, -4, -4, 4, 4, 4]) +settings_file.source = Source(space=SpatialBox([-4, -4, -4], [4, 4, 4])) settings_file.export_to_xml() diff --git a/examples/python/boxes/build-xml.py b/examples/python/boxes/build-xml.py index 9c28d37bb..7debc87a1 100644 --- a/examples/python/boxes/build-xml.py +++ b/examples/python/boxes/build-xml.py @@ -1,6 +1,8 @@ import numpy as np import openmc +from openmc.source import Source +from openmc.stats import SpatialBox ############################################################################### # Simulation Input File Parameters @@ -117,7 +119,7 @@ settings_file = openmc.SettingsFile() settings_file.batches = batches settings_file.inactive = inactive settings_file.particles = particles -settings_file.set_source_space('box', np.concatenate(outer_cube.bounding_box)) +settings_file.source = Source(space=SpatialBox(*outer_cube.bounding_box)) settings_file.export_to_xml() ############################################################################### diff --git a/examples/python/lattice/hexagonal/build-xml.py b/examples/python/lattice/hexagonal/build-xml.py index 5fa0f9b1b..0dc8108b5 100644 --- a/examples/python/lattice/hexagonal/build-xml.py +++ b/examples/python/lattice/hexagonal/build-xml.py @@ -1,5 +1,6 @@ import openmc - +from openmc.source import Source +from openmc.stats import SpatialBox ############################################################################### # Simulation Input File Parameters @@ -125,7 +126,8 @@ settings_file = openmc.SettingsFile() settings_file.batches = batches settings_file.inactive = inactive settings_file.particles = particles -settings_file.set_source_space('box', [-1, -1, -1, 1, 1, 1]) +settings_file.source = Source(space=SpatialBox( + [-1, -1, -1], [1, 1, 1])) settings_file.keff_trigger = {'type' : 'std_dev', 'threshold' : 5E-4} settings_file.trigger_active = True settings_file.trigger_max_batches = 100 diff --git a/examples/python/lattice/nested/build-xml.py b/examples/python/lattice/nested/build-xml.py index 501b3ee4b..b4a9d199d 100644 --- a/examples/python/lattice/nested/build-xml.py +++ b/examples/python/lattice/nested/build-xml.py @@ -1,5 +1,6 @@ import openmc - +from openmc.source import Source +from openmc.stats import SpatialBox ############################################################################### # Simulation Input File Parameters @@ -136,7 +137,8 @@ settings_file = openmc.SettingsFile() settings_file.batches = batches settings_file.inactive = inactive settings_file.particles = particles -settings_file.set_source_space('box', [-1, -1, -1, 1, 1, 1]) +settings_file.source = Source(space=SpatialBox( + [-1, -1, -1], [1, 1, 1])) settings_file.export_to_xml() diff --git a/examples/python/lattice/simple/build-xml.py b/examples/python/lattice/simple/build-xml.py index 00fbea22a..26c19e971 100644 --- a/examples/python/lattice/simple/build-xml.py +++ b/examples/python/lattice/simple/build-xml.py @@ -1,4 +1,6 @@ import openmc +from openmc.source import Source +from openmc.stats import SpatialBox ############################################################################### # Simulation Input File Parameters @@ -125,7 +127,8 @@ settings_file = openmc.SettingsFile() settings_file.batches = batches settings_file.inactive = inactive settings_file.particles = particles -settings_file.set_source_space('box', [-1, -1, -1, 1, 1, 1]) +settings_file.source = Source(space=SpatialBox( + [-1, -1, -1], [1, 1, 1])) settings_file.trigger_active = True settings_file.trigger_max_batches = 100 settings_file.export_to_xml() diff --git a/examples/python/pincell/build-xml.py b/examples/python/pincell/build-xml.py index b3bb932dc..c08756ae5 100644 --- a/examples/python/pincell/build-xml.py +++ b/examples/python/pincell/build-xml.py @@ -1,4 +1,6 @@ import openmc +from openmc.source import Source +from openmc.stats import SpatialBox ############################################################################### # Simulation Input File Parameters @@ -168,8 +170,8 @@ settings_file = openmc.SettingsFile() settings_file.batches = batches settings_file.inactive = inactive settings_file.particles = particles -settings_file.set_source_space('box', [-0.62992, -0.62992, -1, \ - 0.62992, 0.62992, 1]) +settings_file.source = Source(space=SpatialBox( + [-0.62992, -0.62992, -1], [0.62992, 0.62992, 1])) settings_file.entropy_lower_left = [-0.39218, -0.39218, -1.e50] settings_file.entropy_upper_right = [0.39218, 0.39218, 1.e50] settings_file.entropy_dimension = [10, 10, 1] diff --git a/examples/python/reflective/build-xml.py b/examples/python/reflective/build-xml.py index 44b544d20..73d07cbb6 100644 --- a/examples/python/reflective/build-xml.py +++ b/examples/python/reflective/build-xml.py @@ -1,6 +1,8 @@ import numpy as np import openmc +from openmc.stats import SpatialBox +from openmc.source import Source ############################################################################### # Simulation Input File Parameters @@ -84,5 +86,5 @@ settings_file = openmc.SettingsFile() settings_file.batches = batches settings_file.inactive = inactive settings_file.particles = particles -settings_file.set_source_space('box', np.concatenate(cell.region.bounding_box)) +settings_file.source = Source(space=SpatialBox(*cell.region.bounding_box)) settings_file.export_to_xml() diff --git a/openmc/settings.py b/openmc/settings.py index 2cb6a1446..b7446046e 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -9,6 +9,7 @@ import numpy as np from openmc.clean_xml import * from openmc.checkvalue import (check_type, check_length, check_value, check_greater_than, check_less_than) +from openmc.source import Source if sys.version_info[0] >= 3: basestring = str @@ -36,8 +37,8 @@ class SettingsFile(object): type are 'variance', 'std_dev', and 'rel_err'. The threshold value should be a float indicating the variance, standard deviation, or relative error used. - source_file : str - Path to a source file + source : openmc.source.Source + Distribution of source sites in space, angle, and energy output : dict Dictionary indicating what files to output. Valid keys are 'summary', 'cross_sections', 'tallies', and 'distribmats'. Values corresponding to @@ -134,16 +135,7 @@ class SettingsFile(object): self._keff_trigger = None # Source subelement - self._source_subelement = None - self._source_file = None - self._source_space_type = None - self._source_space_params = None - self._source_angle_type = None - self._source_angle_interpolation = None - self._source_angle_params = None - self._source_energy_type = None - self._source_energy_interpolation = None - self._source_energy_params = None + self._source = None self._confidence_intervals = None self._cross_sections = None @@ -230,40 +222,8 @@ class SettingsFile(object): return self._keff_trigger @property - def source_file(self): - return self._source_file - - @property - def source_space_type(self): - return self._source_space_type - - @property - def source_space_params(self): - return self._source_space_params - - @property - def source_angle_type(self): - return self._source_angle_type - - @property - def source_angle_interpolation(self): - return self._source_angle_interpolation - - @property - def source_angle_params(self): - return self._source_angle_params - - @property - def source_energy_type(self): - return self._source_energy_type - - @property - def source_energy_interpolation(self): - return self._source_energy_interpolation - - @property - def source_energy_params(self): - return self._source_energy_params + def source(self): + return self._source @property def confidence_intervals(self): @@ -478,156 +438,10 @@ class SettingsFile(object): self._keff_trigger = keff_trigger - @source_file.setter - def source_file(self, source_file): - check_type('source file', source_file, basestring) - self._source_file = source_file - - def set_source_space(self, stype, params): - """Defined the spatial bounds of the external/starting source. - - Parameters - ---------- - stype : str - The type of spatial distribution. Valid options are "box", - "fission", and "point". A "box" spatial distribution has coordinates - sampled uniformly in a parallelepiped. A "fission" spatial - distribution samples locations from a "box" distribution but only - locations in fissionable materials are accepted. A "point" spatial - distribution has coordinates specified by a triplet. - params : Iterable of float - For a "box" or "fission" spatial distribution, ``params`` should be - given as six real numbers, the first three of which specify the - lower-left corner of a parallelepiped and the last three of which - specify the upper-right corner. Source sites are sampled uniformly - through that parallelepiped. - - For a "point" spatial distribution, ``params`` should be given as - three real numbers which specify the (x,y,z) location of an - isotropic point source - - """ - - check_type('source space type', stype, basestring) - check_value('source space type', stype, ['box', 'fission', 'point']) - check_type('source space parameters', params, Iterable, Real) - if stype in ['box', 'fission']: - check_length('source space parameters for a ' - 'box/fission distribution', params, 6) - elif stype == 'point': - check_length('source space parameters for a point source', - params, 3) - - self._source_space_type = stype - self._source_space_params = params - - def set_source_angle(self, stype, params=[], interp='histogram'): - """Defined the angular distribution of the external/starting source. - - Parameters - ---------- - stype : str - The type of angular distribution. Valid options are "isotropic", - "monodirectional", and "tabular". The angle of the particle emitted - from a source site is isotropic if the "isotropic" option is - given. The angle of the particle emitted from a source site is the - direction specified in ``params`` if the "monodirectional" option is - given. The "tabular" option produces directions with polar angles - sampled from a tabulated distribution. - params : Iterable of float - For an "isotropic" angular distribution, ``params`` should not - be specified. - - For a "monodirectional" angular distribution, ``params`` should - be given as three floats which specify the angular cosines - with respect to each axis. - - For a "tabular" angular distribution, ``parameters`` provides the - :math:`(\mu,p)` pairs defining the tabular distribution. All - :math:`\mu` points are given first followed by corresponding - :math:`p` points. - interp : { 'histogram', 'linear-linear' } - For a "tabular" angular distribution, ``interpolation`` can be set - to "histogram" or "linear-linear" thereby specifying how tabular - points are to be interpolated. - - """ - - check_type('source angle type', stype, basestring) - check_value('source angle type', stype, - ['isotropic', 'monodirectional', 'tabular']) - check_type('source angle parameters', params, Iterable, Real) - if stype == 'isotropic' and params is not None: - msg = 'Unable to set source angle parameters since they are not ' \ - 'it is not supported for isotropic type sources' - raise ValueError(msg) - elif stype == 'monodirectional': - check_length('source angle parameters for a monodirectional ' - 'source', params, 3) - elif stype == 'tabular': - check_type('source angle interpolation', interp, basestring) - check_value('source angle interpolation', interp, - ['histogram', 'linear-linear']) - self._source_angle_interpolation = interp - - self._source_angle_type = stype - self._source_angle_params = params - - def set_source_energy(self, stype, params=[], interp='histogram'): - """Defined the energy distribution of the external/starting source. - - Parameters - ---------- - stype : str - - The type of energy distribution. Valid options are "monoenergetic", - "watt", "maxwell", and "tabular". The "monoenergetic" option - produces source sites at a single energy. The "watt" option produces - source sites whose energy is sampled from a Watt fission - spectrum. The "maxwell" option produce source sites whose energy is - sampled from a Maxwell fission spectrum. The "tabular" option - produces source sites whose energy is sampled from a tabulated - distribution. - params : Iterable of float - For a "monoenergetic" energy distribution, ``params`` should be - given as the energy in MeV of the source sites. - - For a "watt" energy distribution, ``params`` should be given as two - real numbers :math:`a` and :math:`b` that parameterize the - distribution :math:`p(E) dE = c e^{-E/a} \sinh \sqrt{b \, E} dE`. - - For a "maxwell" energy distribution, ``params`` should be given as - one real number :math:`a` that parameterizes the distribution - :math:`p(E) dE = c E e^{-E/a} dE`. - - For a "tabular" energy distribution, ``parameters`` provides the - :math:`(E,p)` pairs defining the tabular distribution. All :math:`E` - points are given first followed by corresponding :math:`p` points. - interp : { 'histogram', 'linear-linear' } - For a "tabular" energy distribution, ``interpolation`` can be set - to "histogram" or "linear-linear" thereby specifying how tabular - points are to be interpolated. - - """ - - check_type('source energy type', stype, basestring) - check_value('source energy type', stype, - ['monoenergetic', 'watt', 'maxwell', 'tabular']) - check_type('source energy parameters', params, Iterable, Real) - if stype in ['monoenergetic', 'maxwell']: - check_length('source energy parameters for a monoenergetic ' - 'or Maxwell source', params, 1) - elif stype == 'watt': - check_length('source energy parameters for a Watt source', - params, 2) - elif stype == 'tabular': - check_type('source energy interpolation', interp, basestring) - check_value('source energy interpolation', interp, - ['histogram', 'linear-linear']) - self._source_energy_interpolation = interp - - self._source_energy_type = stype - self._source_energy_params = params + @source.setter + def source(self, source): + check_type('source distribution', source, Source) + self._source = source @output.setter def output(self, output): @@ -966,51 +780,8 @@ class SettingsFile(object): subelement.text = str(self._keff_trigger[key]).lower() def _create_source_subelement(self): - self._create_source_space_subelement() - self._create_source_energy_subelement() - self._create_source_angle_subelement() - - def _create_source_space_subelement(self): - if self._source_space_params is not None: - if self._source_subelement is None: - self._source_subelement = ET.SubElement(self._settings_file, - "source") - - element = ET.SubElement(self._source_subelement, "space") - element.set("type", self._source_space_type) - - subelement = ET.SubElement(element, "parameters") - subelement.text = ' '.join(map(str, self._source_space_params)) - - def _create_source_angle_subelement(self): - if self._source_angle_params is not None: - if self._source_subelement is None: - self._source_subelement = ET.SubElement(self._settings_file, - "source") - - element = ET.SubElement(self._source_subelement, "angle") - element.set("type", self._source_angle_type) - - if self.source_angle_interpolation is not None: - element.set("interpolation", self.source_angle_interpolation) - - subelement = ET.SubElement(element, "parameters") - subelement.text = ' '.join(map(str, self._source_angle_params)) - - def _create_source_energy_subelement(self): - if self._source_energy_params is not None: - if self._source_subelement is None: - self._source_subelement = ET.SubElement(self._settings_file, - "source") - - element = ET.SubElement(self._source_subelement, "energy") - element.set("type", self._source_energy_type) - - if self.source_energy_interpolation is not None: - element.set("interpolation", self.source_energy_interpolation) - - subelement = ET.SubElement(element, "parameters") - subelement.text = ' '.join(map(str, self._source_energy_params)) + if self.source is not None: + self._settings_file.append(self.source.to_xml()) def _create_output_subelement(self): if self._output is not None: diff --git a/openmc/source.py b/openmc/source.py new file mode 100644 index 000000000..57ed7f195 --- /dev/null +++ b/openmc/source.py @@ -0,0 +1,101 @@ +import sys +from xml.etree import ElementTree as ET + +from openmc.stats.univariate import Univariate +from openmc.stats.multivariate import UnitSphere, Spatial +import openmc.checkvalue as cv + +if sys.version_info[0] >= 3: + basestring = str + + +class Source(object): + """Distribution of phase space coordinates for source sites. + + Parameters + ---------- + space : openmc.stats.Spatial, optional + Spatial distribution of source sites + angle : openmc.stats.UnitSphere, optional + Angular distribution of source sites + energy : openmc.stats.Univariate, optional + Energy distribution of source sites + filename : str, optional + Source file from which sites should be sampled + + Attributes + ---------- + space : openmc.stats.Spatial or None + Spatial distribution of source sites + angle : openmc.stats.UnitSphere or None + Angular distribution of source sites + energy : openmc.stats.Univariate or None + Energy distribution of source sites + file : str or None + Source file from which sites should be sampled + + """ + + def __init__(self, space=None, angle=None, energy=None, filename=None): + self._space = None + self._angle = None + self._energy = None + self._probability = None + self._file = None + + if space is not None: + self.space = space + if angle is not None: + self.angle = angle + if energy is not None: + self.energy = energy + if filename is not None: + self.file = filename + + @property + def file(self): + return self._file + + @property + def space(self): + return self._space + + @property + def angle(self): + return self._angle + + @property + def energy(self): + return self._energy + + @file.setter + def file(self, filename): + cv.check_type('source file', filename, basestring) + self._file = filename + + @space.setter + def space(self, space): + cv.check_type('spatial distribution', space, Spatial) + self._space = space + + @angle.setter + def angle(self, angle): + cv.check_type('angular distribution', angle, UnitSphere) + self._angle = angle + + @energy.setter + def energy(self, energy): + cv.check_type('energy distribution', energy, Univariate) + self._energy = energy + + def to_xml(self): + element = ET.Element("source") + if self.file is not None: + element.set("file", self.file) + if self.space is not None: + element.append(self.space.to_xml()) + if self.angle is not None: + element.append(self.angle.to_xml()) + if self.energy is not None: + element.append(self.energy.to_xml()) + return element diff --git a/openmc/stats/__init__.py b/openmc/stats/__init__.py new file mode 100644 index 000000000..3d7b80b28 --- /dev/null +++ b/openmc/stats/__init__.py @@ -0,0 +1,2 @@ +from openmc.stats.univariate import * +from openmc.stats.multivariate import * diff --git a/openmc/stats/multivariate.py b/openmc/stats/multivariate.py new file mode 100644 index 000000000..cb1921ba6 --- /dev/null +++ b/openmc/stats/multivariate.py @@ -0,0 +1,409 @@ +from abc import ABCMeta, abstractmethod +from collections import Iterable +from math import pi +from numbers import Real +import sys +from xml.etree import ElementTree as ET + +import numpy as np +from numpy.linalg import norm + +import openmc.checkvalue as cv +from openmc.stats.univariate import Univariate, Uniform + +if sys.version_info[0] >= 3: + basestring = str + + +class UnitSphere(object): + """Distribution of points on the unit sphere. + + This abstract class is used for angular distributions, since a direction is + represented as a unit vector (i.e., vector on the unit sphere). + + Parameters + ---------- + name : str + Name of the distribution + reference_uvw : Iterable of Real + Direction from which polar angle is measured + + Attributes + ---------- + name : str + Name of the distribution + reference_uvw : Iterable of Real + Direction from which polar angle is measured + + """ + + __metaclass__ = ABCMeta + + def __init__(self, name, reference_uvw=None): + self.name = name + self._reference_uvw = None + if reference_uvw is not None: + self.reference_uvw = reference_uvw + + @property + def name(self): + return self._name + + @property + def reference_uvw(self): + return self._reference_uvw + + @name.setter + def name(self, name): + cv.check_type('name', name, basestring) + self._name = name + + @reference_uvw.setter + def reference_uvw(self, uvw): + cv.check_type('reference direction', uvw, Iterable, Real) + uvw = np.asarray(uvw) + self._reference_uvw = uvw/norm(uvw) + + @abstractmethod + def to_xml(self): + return '' + + +class PolarAzimuthal(UnitSphere): + """Angular distribution represented by polar and azimuthal angles + + This distribution allows one to specify the distribution of the cosine of + the polar angle and the azimuthal angle independently of once another. + + Parameters + ---------- + mu : openmc.stats.Univariate + Distribution of the cosine of the polar angle + phi : openmc.stats.Univariate + Distribution of the azimuthal angle + name : str, optional + Name of the distribution. Defaults to 'angle'. + reference_uvw : Iterable of Real + Direction from which polar angle is measured. Defaults to the positive + z-direction. + + Attributes + ---------- + mu : openmc.stats.Univariate + Distribution of the cosine of the polar angle + phi : openmc.stats.Univariate + Distribution of the azimuthal angle + + """ + + def __init__(self, mu=None, phi=None, name='angle',reference_uvw=[0., 0., 1.]): + super(PolarAzimuthal, self).__init__(name, reference_uvw) + if mu is not None: + self.mu = mu + else: + self.mu = Uniform('mu', -1., 1.) + + if phi is not None: + self.phi = phi + else: + self.phi = Uniform('phi', 0., 2*pi) + + @property + def mu(self): + return self._mu + + @property + def phi(self): + return self._phi + + @mu.setter + def mu(self, mu): + cv.check_type('cosine of polar angle', mu, Univariate) + self._mu = mu + + @phi.setter + def phi(self, phi): + cv.check_type('azimuthal angle', phi, Univariate) + self._phi = phi + + def to_xml(self): + element = ET.Element(self.name) + element.set("type", "mu-phi") + if self.reference_uvw is not None: + element.set("reference_uvw", ' '.join(map(str, self.reference_uvw))) + element.append(self.mu.to_xml()) + element.append(self.phi.to_xml()) + return element + + +class Isotropic(UnitSphere): + """Isotropic angular distribution. + + Parameters + ---------- + name : str, optional + Name of the distribution. Defaults to 'angle'. + + """ + + def __init__(self, name='angle'): + super(Isotropic, self).__init__(name) + + def to_xml(self): + element = ET.Element(self.name) + element.set("type", "isotropic") + return element + + +class Monodirectional(UnitSphere): + """Monodirectional angular distribution. + + A monodirectional angular distribution is one for which the polar and + azimuthal angles are always the same. It is completely specified by the + reference direction vector. + + Parameters + ---------- + name : str, optional + Name of the distribution. Defaults to 'angle'. + reference_uvw : Iterable of Real + Direction from which polar angle is measured. Defaults to the positive + x-direction. + + """ + + + def __init__(self, name='angle', reference_uvw=[1., 0., 0.]): + super(Monodirectional, self).__init__(name, reference_uvw) + + def to_xml(self): + element = ET.Element(self.name) + element.set("type", "monodirectional") + if self.reference_uvw is not None: + element.set("reference_uvw", ' '.join(map(str, self.reference_uvw))) + return element + + +class Spatial(object): + """Distribution of locations in three-dimensional Euclidean space. + + Classes derived from this abstract class can be used for spatial + distributions of source sites. + + Parameters + ---------- + name : str + Name of the distribution + + Attributes + ---------- + name : str + Name of the distribution + + """ + + __metaclass__ = ABCMeta + + def __init__(self, name): + self.name = name + + @property + def name(self): + return self._name + + @name.setter + def name(self, name): + cv.check_type('name', name, basestring) + self._name = name + + @abstractmethod + def to_xml(self): + return '' + + +class SpatialIndependent(Spatial): + """Spatial distribution with independent x, y, and z distributions. + + This distribution allows one to specify a coordinates whose x-, y-, and z- + components are sampled independently from one another. + + Parameters + ---------- + x : openmc.stats.Univariate + Distribution of x-coordinates + y : openmc.stats.Univariate + Distribution of y-coordinates + z : openmc.stats.Univariate + Distribution of z-coordinates + name : str + Name of the distribution + + Attributes + ---------- + x : openmc.stats.Univariate + Distribution of x-coordinates + y : openmc.stats.Univariate + Distribution of y-coordinates + z : openmc.stats.Univariate + Distribution of z-coordinates + + """ + + + def __init__(self, x, y, z, name='space'): + super(SpatialIndependent, self).__init__(name) + self.x = x + self.y = y + self.z = z + + @property + def x(self): + return self._x + + @property + def y(self): + return self._y + + @property + def z(self): + return self._z + + @x.setter + def x(self, x): + cv.check_type('x coordinate', x, Univariate) + self._x = x + + @y.setter + def y(self, y): + cv.check_type('y coordinate', y, Univariate) + self._y = y + + @x.setter + def z(self, z): + cv.check_type('z coordinate', z, Univariate) + self._z = z + + def to_xml(self): + element = ET.Element(self.name) + element.set("type", "independent") + element.append(self.x.to_xml()) + element.append(self.y.to_xml()) + element.append(self.z.to_xml()) + return element + + +class SpatialBox(Spatial): + """Uniform distribution of coordinates in a rectangular cuboid. + + Parameters + ---------- + lower_left : Iterable of Real + Lower-left coordinates of cuboid + upper_right : Iterable of Real + Upper-right coordinates of cuboid + name : str, optional + Name of the distribution + only_fissionable : bool, optional + Whether spatial sites should only be accepted if they occur in + fissionable materials + + Attributes + ---------- + lower_left : Iterable of Real + Lower-left coordinates of cuboid + upper_right : Iterable of Real + Upper-right coordinates of cuboid + only_fissionable : bool, optional + Whether spatial sites should only be accepted if they occur in + fissionable materials + + """ + + + def __init__(self, lower_left, upper_right, name='space', only_fissionable=False): + super(SpatialBox, self).__init__(name) + self.lower_left = lower_left + self.upper_right = upper_right + self.only_fissionable = only_fissionable + + @property + def lower_left(self): + return self._lower_left + + @property + def upper_right(self): + return self._upper_right + + @property + def only_fissionable(self): + return self._only_fissionable + + @lower_left.setter + def lower_left(self, lower_left): + cv.check_type('lower left coordinate', lower_left, Iterable, Real) + cv.check_length('lower left coordinate', lower_left, 3) + self._lower_left = lower_left + + @upper_right.setter + def upper_right(self, upper_right): + cv.check_type('upper right coordinate', upper_right, Iterable, Real) + cv.check_length('upper right coordinate', upper_right, 3) + self._upper_right = upper_right + + @only_fissionable.setter + def only_fissionable(self, only_fissionable): + cv.check_type('only fissionable', only_fissionable, bool) + self._only_fissionable = only_fissionable + + def to_xml(self): + element = ET.Element(self.name) + if self.only_fissionable: + element.set("type", "fission") + else: + element.set("type", "box") + params = ET.SubElement(element, "parameters") + params.text = ' '.join(map(str, self.lower_left)) + ' ' + \ + ' '.join(map(str, self.upper_right)) + return element + + +class SpatialPoint(Spatial): + """Delta function in three dimensions. + + This spatial distribution can be used for a point source where sites are + emitted at a specific location given by its Cartesian coordinates. + + Parameters + ---------- + xyz : Iterable of Real + Cartesian coordinates of location + name : str, optional + Name of the distribution + + Attributes + ---------- + xyz : Iterable of Real + Cartesian coordinates of location + + """ + + def __init__(self, xyz, name='space'): + super(SpatialPoint, self).__init__(name) + self.xyz = xyz + + @property + def xyz(self): + return self._xyz + + @xyz.setter + def xyz(self, xyz): + cv.check_type('coordinate', xyz, Iterable, Real) + cv.check_length('coordinate', xyz, 3) + self._xyz = xyz + + def to_xml(self): + element = ET.Element(self.name) + element.set("type", "point") + params = ET.SubElement(element, "parameters") + params.text = ' '.join(map(str, self.xyz)) + return element diff --git a/openmc/stats/univariate.py b/openmc/stats/univariate.py new file mode 100644 index 000000000..8e32fdc1c --- /dev/null +++ b/openmc/stats/univariate.py @@ -0,0 +1,325 @@ +from abc import ABCMeta, abstractmethod +from collections import Iterable +from numbers import Real +from xml.etree import ElementTree as ET + +import openmc.checkvalue as cv + + +class Univariate(object): + """Probability distribution of a single random variable. + + The Univariate class is an abstract class that can be derived to implement a + specific probability distribution. + + Parameters + ---------- + name : str + Name of the distribution + + Attributes + ---------- + name : str + Name of the distributions + + """ + + __metaclass__ = ABCMeta + + def __init__(self, name): + self.name = name + + @property + def name(self): + return self._name + + @name.setter + def name(self, name): + cv.check_type('name', name, basestring) + self._name = name + + @abstractmethod + def to_xml(self): + return '' + + +class Discrete(Univariate): + """Distribution characterized by a probability mass function. + + The Discrete distribution assigns probability values to discrete values of a + random variable, rather than expressing the distribution as a continuous + random variable. + + Parameters + ---------- + x : Iterable of Real + Values of the random variable + p : Iterable of Real + Discrete probability for each value + + Attributes + ---------- + x : Iterable of Real + Values of the random variable + p : Iterable of Real + Discrete probability for each value + + """ + + def __init__(self, name, x, p): + super(Discrete, self).__init__(name) + self.x = x + self.p = p + + @property + def x(self): + return self._x + + @property + def p(self): + return self._p + + @x.setter + def x(self, x): + cv.check_type('discrete values', x, Iterable, Real) + self._x = x + + @p.setter + def p(self, p): + cv.check_type('discrete probabilities', p, Iterable, Real) + for pk in p: + cv.check_greater_than('discrete probability', pk, 0.0, True) + self._p = p + + def to_xml(self): + element = ET.Element(self.name) + element.set("type", "discrete") + + params = ET.SubElement(element, "parameters") + params.text = ' '.join(map(str, self.x)) + ' ' + ' '.join(map(str, self.p)) + + return element + + +class Uniform(Univariate): + """Distribution with constant probability over a finite interval [a,b] + + Parameters + ---------- + a : float, optional + Lower bound of the sampling interval. Defaults to zero. + b : float, optional + Upper bound of the sampling interval. Defaults to unity. + + Attributes + ---------- + a : float + Lower bound of the sampling interval + b : float + Upper bound of the sampling interval + + """ + + def __init__(self, name, a=0.0, b=1.0): + super(Uniform, self).__init__(name) + self.a = a + self.b = b + + @property + def a(self): + return self._a + + @property + def b(self): + return self._b + + @a.setter + def a(self, a): + cv.check_type('Uniform a', a, Real) + self._a = a + + @b.setter + def b(self, b): + cv.check_type('Uniform b', b, Real) + self._b = b + + def to_xml(self): + element = ET.Element(self.name) + element.set("type", "uniform") + element.set("parameters", '{} {}'.format(self.a, self.b)) + return element + + +class Maxwell(Univariate): + """Maxwellian distribution in energy. + + The Maxwellian distribution in energy is characterized by a single parameter + :math:`\theta` and has a density function :math:`p(E) dE = c E e^{-E/\theta} + dE`. + + Parameters + ---------- + theta : float + Effective temperature for distribution + + Attributes + ---------- + theta : float + Effective temperature for distribution + + """ + + def __init__(self, theta, name='energy'): + super(Maxwell, self).__init__(name) + self.theta = theta + + @property + def theta(self): + return self._theta + + @theta.setter + def theta(self, theta): + cv.check_type('Maxwell temperature', theta, Real) + cv.check_greater_than('Maxwell temperature', theta, 0.0) + self._theta = theta + + def to_xml(self): + element = ET.Element(self.name) + element.set("type", "uniform") + element.set("parameters", str(self.theta)) + return element + + +class Watt(Univariate): + """Watt fission energy spectrum. + + The Watt fission energy spectrum is characterized by two parameters + :math:`a` and :math:`b` and has density function :math:`p(E) dE = c e^{-E/a} + \sinh \sqrt{b \, E} dE`. + + Parameters + ---------- + a : float + First parameter of distribution + b : float + Second parameter of distribution + name : str, optional + Name of the distribution. Defaults to 'energy'. + + Attributes + ---------- + a : float + First parameter of distribution + b : float + Second parameter of distribution + + """ + + def __init__(self, a, b, name='energy'): + super(Watt, self).__init__(name) + self.a = a + self.b = b + + @property + def a(self): + return self._a + + @property + def b(self): + return self._b + + @a.setter + def a(self, a): + cv.check_type('Watt a', a, Real) + cv.check_greater_than('Watt a', a, 0.0) + self._a = a + + @b.setter + def b(self, b): + cv.check_type('Watt b', b, Real) + cv.check_greater_than('Watt b', b, 0.0) + self._b = b + + def to_xml(self): + element = ET.Element(self.name) + element.set("type", "watt") + element.set("parameters", '{} {}'.format(self.a, self.b)) + return element + + +class Tabular(Univariate): + """Piecewise continuous probability distribution. + + This class is used to represent a probability distribution whose density + function is tabulated at specific values and is either histogram or linearly + interpolated between points. + + Parameters + ---------- + name : str + Name of the distribution + x : Iterable of Real + Tabulated values of the random variable + p : Iterable of Real + Tabulated probabilities + interpolation : {'histogram', 'linear-linear'}, optional + Indicate whether the density function is constant between tabulated + points or linearly-interpolated. + + Attributes + ---------- + x : Iterable of Real + Tabulated values of the random variable + p : Iterable of Real + Tabulated probabilities + interpolation : {'histogram', 'linear-linear'}, optional + Indicate whether the density function is constant between tabulated + points or linearly-interpolated. + + """ + + def __init__(self, name, x, p, interpolation='linear-linear'): + super(Tabular, self).__init__(name) + self.x = x + self.p = p + self.interpolation = interpolation + + @property + def x(self): + return self._x + + @property + def p(self): + return self._p + + @property + def interpolation(self): + return self._interpolation + + @x.setter + def x(self, x): + cv.check_type('tabulated values', x, Iterable, Real) + self._x = x + + @p.setter + def p(self, p): + cv.check_type('tabulated probabilities', p, Iterable, Real) + for pk in p: + cv.check_greater_than('tabulated probability', pk, 0.0, True) + self._p = p + + @interpolation.setter + def interpolation(self, interpolation): + cv.check_value('interpolation', interpolation, + ['linear-linear', 'histogram']) + self._interpolation = interpolation + + def to_xml(self): + element = ET.Element(self.name) + element.set("type", "tabular") + element.set("interpolation", self.interpolation) + + params = ET.SubElement(element, "parameters") + params.text = ' '.join(map(str, self.x)) + ' ' + ' '.join(map(str, self.p)) + + return element diff --git a/setup.py b/setup.py index d401f5fba..0bf4549c0 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ except ImportError: kwargs = {'name': 'openmc', 'version': '0.7.1', - 'packages': ['openmc', 'openmc.mgxs'], + 'packages': ['openmc', 'openmc.mgxs', 'openmc.stats'], 'scripts': glob.glob('scripts/openmc-*'), # Metadata diff --git a/tests/input_set.py b/tests/input_set.py index 87b857f4a..46058f41b 100644 --- a/tests/input_set.py +++ b/tests/input_set.py @@ -1,4 +1,6 @@ import openmc +from openmc.source import Source +from openmc.stats import SpatialBox class InputSet(object): @@ -558,7 +560,8 @@ class InputSet(object): self.settings.batches = 10 self.settings.inactive = 5 self.settings.particles = 100 - self.settings.set_source_space('box', (-160, -160, -183, 160, 160, 183)) + self.settings.source = Source(space=SpatialBox( + [-160, -160, -183], [160, 160, 183])) def build_defualt_plots(self): plot = openmc.Plot()