From 33d7166aef4f744ed636dc9b82f6013599a7e8aa Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 4 Sep 2019 10:35:55 -0500 Subject: [PATCH 001/158] Various fixes for building documentation (esp. latexpdf) --- docs/requirements-rtd.txt | 1 + docs/source/conf.py | 2 +- docs/source/methods/photon_physics.rst | 4 ++-- openmc/mgxs/mgxs.py | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/requirements-rtd.txt b/docs/requirements-rtd.txt index 15345247eb..7801c670f8 100644 --- a/docs/requirements-rtd.txt +++ b/docs/requirements-rtd.txt @@ -1,3 +1,4 @@ sphinx-numfig jupyter sphinxcontrib-katex +sphinxcontrib-svg2pdfconverter diff --git a/docs/source/conf.py b/docs/source/conf.py index a24f9b398a..55776b8f49 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -56,7 +56,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx_numfig', 'notebook_sphinxext'] if not on_rtd: - extensions.append('sphinx.ext.imgconverter') + extensions.append('sphinxcontrib.rsvgconverter') # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/docs/source/methods/photon_physics.rst b/docs/source/methods/photon_physics.rst index 4494fc6431..f9bdcfa1cc 100644 --- a/docs/source/methods/photon_physics.rst +++ b/docs/source/methods/photon_physics.rst @@ -579,7 +579,7 @@ sampled using the leading order term of the Sauter–Gluckstern–Hull distribution, .. math:: - :label: sauter–gluckstern–hull + :label: sauter-gluckstern-hull p(\mu_{\pm}) = C(1 - \beta_{\pm}\mu_{\pm})^{-2}, @@ -588,7 +588,7 @@ ratio of the velocity of the charged particle to the speed of light given in :eq:`beta-2`. The inverse transform method is used to sample :math:`\mu_{-}` and -:math:`\mu_{+}` from :eq:`sauter–gluckstern–hull`, using the sampling formula +:math:`\mu_{+}` from :eq:`sauter-gluckstern-hull`, using the sampling formula .. math:: :label: sample-mu diff --git a/openmc/mgxs/mgxs.py b/openmc/mgxs/mgxs.py index 46b0048385..e9fee5a2f1 100644 --- a/openmc/mgxs/mgxs.py +++ b/openmc/mgxs/mgxs.py @@ -3552,7 +3552,7 @@ class ScatterMatrixXS(MatrixMGXS): .. math:: \begin{aligned} - \langle \sigma}_{s,\ell,g'\rightarrow g} \phi \rangle &= \int_{r \in V} dr + \langle \sigma_{s,\ell,g'\rightarrow g} \phi \rangle &= \int_{r \in V} dr \int_{4\pi} d\Omega' \int_{E_{g'}}^{E_{g'-1}} dE' \int_{4\pi} d\Omega \int_{E_g}^{E_{g-1}} dE \; P_\ell (\Omega \cdot \Omega') \sigma_s (r, E' \rightarrow E, \Omega' \cdot \Omega) \psi(r, E', \Omega')\\ From e4e824fe4d88821ca5f94cf0d70769944edf96d5 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 4 Sep 2019 14:46:45 -0500 Subject: [PATCH 002/158] Skip tests that require NJOY if it's not installed --- tests/unit_tests/test_data_neutron.py | 12 ++++++++++-- tests/unit_tests/test_data_thermal.py | 11 +++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/tests/unit_tests/test_data_neutron.py b/tests/unit_tests/test_data_neutron.py index 3cf036aa2c..5313199a71 100644 --- a/tests/unit_tests/test_data_neutron.py +++ b/tests/unit_tests/test_data_neutron.py @@ -1,7 +1,6 @@ -#!/usr/bin/env python - from collections.abc import Mapping, Callable import os +import shutil import numpy as np import pandas as pd @@ -12,6 +11,10 @@ import openmc.data _TEMPERATURES = [300., 600., 900.] _ENDF_DATA = os.environ['OPENMC_ENDF_DATA'] +# Check if NJOY is available +needs_njoy = pytest.mark.skipif(shutil.which('njoy') is None, + reason="NJOY not installed") + @pytest.fixture(scope='module') def pu239(): @@ -198,6 +201,7 @@ def test_fission(pu239): assert photon.particle == 'photon' +@needs_njoy def test_derived_products(am244): fission = am244.reactions[18] total_neutron = fission.derived_products[0] @@ -220,6 +224,7 @@ def test_urr(pu239): assert ptable.table.shape[0] == ptable.energy.size +@needs_njoy def test_get_reaction_components(h2): assert h2.get_reaction_components(1) == [2, 16, 102] assert h2.get_reaction_components(101) == [102] @@ -437,6 +442,7 @@ def test_laboratory(be9): assert np.all((-1. <= mu.x) & (mu.x <= 1.)) +@needs_njoy def test_correlated(tmpdir): endf_file = os.path.join(_ENDF_DATA, 'neutrons', 'n-014_Si_030.endf') si30 = openmc.data.IncidentNeutron.from_njoy(endf_file, heatr=False) @@ -447,6 +453,7 @@ def test_correlated(tmpdir): si30_copy = openmc.data.IncidentNeutron.from_hdf5(filename) +@needs_njoy def test_nbody(tmpdir, h2): # Convert to HDF5 and read back filename = str(tmpdir.join('h2.h5')) @@ -461,6 +468,7 @@ def test_nbody(tmpdir, h2): assert nbody1.q_value == nbody2.q_value +@needs_njoy def test_ace_convert(run_in_tmpdir): filename = os.path.join(_ENDF_DATA, 'neutrons', 'n-001_H_001.endf') ace_ascii = 'ace_ascii' diff --git a/tests/unit_tests/test_data_thermal.py b/tests/unit_tests/test_data_thermal.py index 47cbe27b97..ef6fb704cb 100644 --- a/tests/unit_tests/test_data_thermal.py +++ b/tests/unit_tests/test_data_thermal.py @@ -1,9 +1,8 @@ -#!/usr/bin/env python - from collections.abc import Callable from math import exp import os import random +import shutil import numpy as np import pytest @@ -12,6 +11,10 @@ import openmc.data _ENDF_DATA = os.environ['OPENMC_ENDF_DATA'] +# Check if NJOY is available +needs_njoy = pytest.mark.skipif(shutil.which('njoy') is None, + reason="NJOY not installed") + @pytest.fixture(scope='module') def h2o(): @@ -101,6 +104,7 @@ def test_graphite_xs(graphite): assert elastic([1e-3, 1.0]) == pytest.approx([0.0, 0.62586153]) +@needs_njoy def test_graphite_njoy(): path_c0 = os.path.join(_ENDF_DATA, 'neutrons', 'n-006_C_000.endf') path_gr = os.path.join(_ENDF_DATA, 'thermal_scatt', 'tsl-graphite.endf') @@ -112,6 +116,7 @@ def test_graphite_njoy(): assert graphite.temperatures == ['296K'] +@needs_njoy def test_export_to_hdf5(tmpdir, h2o_njoy, hzrh_njoy, graphite): filename = str(tmpdir.join('water.h5')) h2o_njoy.export_to_hdf5(filename) @@ -131,6 +136,7 @@ def test_export_to_hdf5(tmpdir, h2o_njoy, hzrh_njoy, graphite): assert os.path.exists(filename) +@needs_njoy def test_continuous_dist(h2o_njoy): for temperature, dist in h2o_njoy.inelastic.distribution.items(): assert temperature.endswith('K') @@ -173,6 +179,7 @@ def test_hzrh_elastic(hzrh): assert dist.debye_waller > 0.0 +@needs_njoy def test_hzrh_njoy(hzrh_njoy): endf, ace = hzrh_njoy From d85df13ae74b97acf6107a08fa328c72211ade6d Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 4 Sep 2019 15:10:53 -0500 Subject: [PATCH 003/158] Make surfaces take coefficients as first arguments --- openmc/mesh.py | 26 +- openmc/model/funcs.py | 4 +- openmc/region.py | 8 +- openmc/surface.py | 238 +++++++++--------- .../asymmetric_lattice/test.py | 12 +- .../deplete/example_geometry.py | 17 +- tests/regression_tests/periodic/test.py | 14 +- .../photon_production/test.py | 10 +- .../resonance_scattering/test.py | 2 +- tests/regression_tests/salphabeta/test.py | 10 +- tests/regression_tests/triso/test.py | 12 +- tests/regression_tests/volume_calc/test.py | 10 +- tests/unit_tests/conftest.py | 8 +- tests/unit_tests/test_complex_cell_capi.py | 28 +-- tests/unit_tests/test_model_triso.py | 24 +- tests/unit_tests/test_region.py | 18 +- tests/unit_tests/test_surface.py | 2 +- 17 files changed, 218 insertions(+), 225 deletions(-) diff --git a/openmc/mesh.py b/openmc/mesh.py index 4186b56a88..0a8b51c5f6 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -350,18 +350,14 @@ class RegularMesh(MeshBase): n_dim = len(self.dimension) # Build the cell which will contain the lattice - xplanes = [openmc.XPlane(x0=self.lower_left[0], - boundary_type=bc[0]), - openmc.XPlane(x0=self.upper_right[0], - boundary_type=bc[1])] + xplanes = [openmc.XPlane(self.lower_left[0], bc[0]), + openmc.XPlane(self.upper_right[0], bc[1])] if n_dim == 1: - yplanes = [openmc.YPlane(y0=-1e10, boundary_type='reflective'), - openmc.YPlane(y0=1e10, boundary_type='reflective')] + yplanes = [openmc.YPlane(-1e10, 'reflective'), + openmc.YPlane(1e10, 'reflective')] else: - yplanes = [openmc.YPlane(y0=self.lower_left[1], - boundary_type=bc[2]), - openmc.YPlane(y0=self.upper_right[1], - boundary_type=bc[3])] + yplanes = [openmc.YPlane(self.lower_left[1], bc[2]), + openmc.YPlane(self.upper_right[1], bc[3])] if n_dim <= 2: # Would prefer to have the z ranges be the max supported float, but @@ -371,13 +367,11 @@ class RegularMesh(MeshBase): # inconsistency between what numpy uses as the max float and what # Fortran expects for a real(8), so this avoids code complication # and achieves the same goal. - zplanes = [openmc.ZPlane(z0=-1e10, boundary_type='reflective'), - openmc.ZPlane(z0=1e10, boundary_type='reflective')] + zplanes = [openmc.ZPlane(-1e10, 'reflective'), + openmc.ZPlane(1e10, 'reflective')] else: - zplanes = [openmc.ZPlane(z0=self.lower_left[2], - boundary_type=bc[4]), - openmc.ZPlane(z0=self.upper_right[2], - boundary_type=bc[5])] + zplanes = [openmc.ZPlane(self.lower_left[2], bc[4]), + openmc.ZPlane(self.upper_right[2], bc[5])] root_cell = openmc.Cell() root_cell.region = ((+xplanes[0] & -xplanes[1]) & (+yplanes[0] & -yplanes[1]) & diff --git a/openmc/model/funcs.py b/openmc/model/funcs.py index bb5d39a2d9..3ee5052402 100644 --- a/openmc/model/funcs.py +++ b/openmc/model/funcs.py @@ -253,8 +253,8 @@ def hexagonal_prism(edge_length=1., orientation='y', origin=(0., 0.), x, y = origin if orientation == 'y': - right = XPlane(x0=x + sqrt(3.)/2*l, boundary_type=boundary_type) - left = XPlane(x0=x - sqrt(3.)/2*l, boundary_type=boundary_type) + right = XPlane(x + sqrt(3.)/2*l, boundary_type) + left = XPlane(x - sqrt(3.)/2*l, boundary_type) c = sqrt(3.)/3. # y = -x/sqrt(3) + a diff --git a/openmc/region.py b/openmc/region.py index d12ae7112a..a751e1ebf2 100644 --- a/openmc/region.py +++ b/openmc/region.py @@ -527,10 +527,10 @@ class Complement(Region): The Complement of an existing :class:`openmc.Region` can be created by using the ~ operator as the following example demonstrates: - >>> xl = openmc.XPlane(x0=-10.0) - >>> xr = openmc.XPlane(x0=10.0) - >>> yl = openmc.YPlane(y0=-10.0) - >>> yr = openmc.YPlane(y0=10.0) + >>> xl = openmc.XPlane(-10.0) + >>> xr = openmc.XPlane(10.0) + >>> yl = openmc.YPlane(-10.0) + >>> yr = openmc.YPlane(10.0) >>> inside_box = +xl & -xr & +yl & -yr >>> outside_box = ~inside_box >>> type(outside_box) diff --git a/openmc/surface.py b/openmc/surface.py index dc251485d2..e4bbe93eba 100644 --- a/openmc/surface.py +++ b/openmc/surface.py @@ -277,49 +277,48 @@ class Surface(IDManagerMixin, metaclass=ABCMeta): # Create the Surface based on its type if surf_type == 'x-plane': x0 = coeffs[0] - surface = XPlane(surface_id, bc, x0, name) + surface = XPlane(x0, bc, name, surface_id) elif surf_type == 'y-plane': y0 = coeffs[0] - surface = YPlane(surface_id, bc, y0, name) + surface = YPlane(y0, bc, name, surface_id) elif surf_type == 'z-plane': z0 = coeffs[0] - surface = ZPlane(surface_id, bc, z0, name) + surface = ZPlane(z0, bc, name, surface_id) elif surf_type == 'plane': A, B, C, D = coeffs - surface = Plane(surface_id, bc, A, B, C, D, name) + surface = Plane(A, B, C, D, bc, name, surface_id) elif surf_type == 'x-cylinder': y0, z0, r = coeffs - surface = XCylinder(surface_id, bc, y0, z0, r, name) + surface = XCylinder(y0, z0, r, bc, name, surface_id) elif surf_type == 'y-cylinder': x0, z0, r = coeffs - surface = YCylinder(surface_id, bc, x0, z0, r, name) + surface = YCylinder(x0, z0, r, bc, name, surface_id) elif surf_type == 'z-cylinder': x0, y0, r = coeffs - surface = ZCylinder(surface_id, bc, x0, y0, r, name) + surface = ZCylinder(x0, y0, r, bc, name, surface_id) elif surf_type == 'sphere': x0, y0, z0, r = coeffs - surface = Sphere(surface_id, bc, x0, y0, z0, r, name) + surface = Sphere(x0, y0, z0, r, bc, name, surface_id) elif surf_type in ['x-cone', 'y-cone', 'z-cone']: x0, y0, z0, r2 = coeffs if surf_type == 'x-cone': - surface = XCone(surface_id, bc, x0, y0, z0, r2, name) + surface = XCone(x0, y0, z0, r2, bc, name, surface_id) elif surf_type == 'y-cone': - surface = YCone(surface_id, bc, x0, y0, z0, r2, name) + surface = YCone(x0, y0, z0, r2, bc, name, surface_id) elif surf_type == 'z-cone': - surface = ZCone(surface_id, bc, x0, y0, z0, r2, name) + surface = ZCone(x0, y0, z0, r2, bc, name, surface_id) elif surf_type == 'quadric': a, b, c, d, e, f, g, h, j, k = coeffs - surface = Quadric(surface_id, bc, a, b, c, d, e, f, g, - h, j, k, name) + surface = Quadric(a, b, c, d, e, f, g, h, j, k, bc, name, surface_id) return surface @@ -329,13 +328,6 @@ class Plane(Surface): Parameters ---------- - surface_id : int, optional - Unique identifier for the surface. If not specified, an identifier will - automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective'}, optional - Boundary condition that defines the behavior for particles hitting the - surface. Defaults to transmissive boundary condition where particles - freely pass through the surface. a : float, optional The 'A' parameter for the plane. Defaults to 1. b : float, optional @@ -344,8 +336,15 @@ class Plane(Surface): The 'C' parameter for the plane. Defaults to 0. d : float, optional The 'D' parameter for the plane. Defaults to 0. + boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + Boundary condition that defines the behavior for particles hitting the + surface. Defaults to transmissive boundary condition where particles + freely pass through the surface. name : str, optional Name of the plane. If not specified, the name will be the empty string. + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. Attributes ---------- @@ -377,8 +376,8 @@ class Plane(Surface): _type = 'plane' _coeff_keys = ('a', 'b', 'c', 'd') - def __init__(self, surface_id=None, boundary_type='transmission', - a=1., b=0., c=0., d=0., name='', **kwargs): + def __init__(self, a=1., b=0., c=0., d=0., boundary_type='transmission', + name='', surface_id=None, **kwargs): super().__init__(surface_id, boundary_type, name=name) self._periodic_surface = None self.a = a @@ -532,18 +531,18 @@ class XPlane(Plane): Parameters ---------- - surface_id : int, optional - Unique identifier for the surface. If not specified, an identifier will - automatically be assigned. + x0 : float, optional + Location of the plane. Defaults to 0. boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. Only axis-aligned periodicity is supported, i.e., x-planes can only be paired with x-planes. - x0 : float, optional - Location of the plane. Defaults to 0. name : str, optional Name of the plane. If not specified, the name will be the empty string. + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. Attributes ---------- @@ -569,9 +568,9 @@ class XPlane(Plane): _type = 'x-plane' _coeff_keys = ('x0',) - def __init__(self, surface_id=None, boundary_type='transmission', - x0=0., name=''): - super().__init__(surface_id, boundary_type, name=name) + def __init__(self, x0=0., boundary_type='transmission', + name='', surface_id=None): + super().__init__(surface_id=surface_id, boundary_type=boundary_type, name=name) self.x0 = x0 @property @@ -657,18 +656,18 @@ class YPlane(Plane): Parameters ---------- - surface_id : int, optional - Unique identifier for the surface. If not specified, an identifier will - automatically be assigned. + y0 : float, optional + Location of the plane boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. Only axis-aligned periodicity is supported, i.e., x-planes can only be paired with x-planes. - y0 : float, optional - Location of the plane name : str, optional Name of the plane. If not specified, the name will be the empty string. + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. Attributes ---------- @@ -694,10 +693,9 @@ class YPlane(Plane): _type = 'y-plane' _coeff_keys = ('y0',) - def __init__(self, surface_id=None, boundary_type='transmission', - y0=0., name=''): - # Initialize YPlane class attributes - super().__init__(surface_id, boundary_type, name=name) + def __init__(self, y0=0., boundary_type='transmission', + name='', surface_id=None): + super().__init__(surface_id=surface_id, boundary_type=boundary_type, name=name) self.y0 = y0 @property @@ -820,10 +818,9 @@ class ZPlane(Plane): _type = 'z-plane' _coeff_keys = ('z0',) - def __init__(self, surface_id=None, boundary_type='transmission', - z0=0., name=''): - # Initialize ZPlane class attributes - super().__init__(surface_id, boundary_type, name=name) + def __init__(self, z0=0., boundary_type='transmission', + name='', surface_id=None): + super().__init__(surface_id=surface_id, boundary_type=boundary_type, name=name) self.z0 = z0 @property @@ -909,18 +906,18 @@ class Cylinder(Surface): Parameters ---------- - surface_id : int, optional - Unique identifier for the surface. If not specified, an identifier will - automatically be assigned. + r : float, optional + Radius of the cylinder. Defaults to 1. boundary_type : {'transmission, 'vacuum', 'reflective'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. - r : float, optional - Radius of the cylinder. Defaults to 1. name : str, optional Name of the cylinder. If not specified, the name will be the empty string. + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. Attributes ---------- @@ -939,8 +936,8 @@ class Cylinder(Surface): Type of the surface """ - def __init__(self, surface_id=None, boundary_type='transmission', - r=1., name=''): + def __init__(self, r=1., boundary_type='transmission', + name='', surface_id=None): super().__init__(surface_id, boundary_type, name=name) self.r = r @@ -960,22 +957,22 @@ class XCylinder(Cylinder): Parameters ---------- - surface_id : int, optional - Unique identifier for the surface. If not specified, an identifier will - automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective'}, optional - Boundary condition that defines the behavior for particles hitting the - surface. Defaults to transmissive boundary condition where particles - freely pass through the surface. y0 : float, optional y-coordinate of the center of the cylinder. Defaults to 0. z0 : float, optional z-coordinate of the center of the cylinder. Defaults to 0. r : float, optional Radius of the cylinder. Defaults to 0. + boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + Boundary condition that defines the behavior for particles hitting the + surface. Defaults to transmissive boundary condition where particles + freely pass through the surface. name : str, optional Name of the cylinder. If not specified, the name will be the empty string. + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. Attributes ---------- @@ -1000,12 +997,12 @@ class XCylinder(Cylinder): _type = 'x-cylinder' _coeff_keys = ('y0', 'z0', 'r') - def __init__(self, surface_id=None, boundary_type='transmission', - y0=0., z0=0., r=1., name='', *, R=None): + def __init__(self, y0=0., z0=0., r=1., boundary_type='transmission', + name='', surface_id=None, *, R=None): if R is not None: warn(_WARNING_UPPER.format(type(self).__name__, 'r', 'R'), FutureWarning) r = R - super().__init__(surface_id, boundary_type, r, name=name) + super().__init__(r, boundary_type, name, surface_id) self.y0 = y0 self.z0 = z0 @@ -1107,22 +1104,22 @@ class YCylinder(Cylinder): Parameters ---------- - surface_id : int, optional - Unique identifier for the surface. If not specified, an identifier will - automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective'}, optional - Boundary condition that defines the behavior for particles hitting the - surface. Defaults to transmissive boundary condition where particles - freely pass through the surface. x0 : float, optional x-coordinate of the center of the cylinder. Defaults to 0. z0 : float, optional z-coordinate of the center of the cylinder. Defaults to 0. r : float, optional Radius of the cylinder. Defaults to 1. + boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + Boundary condition that defines the behavior for particles hitting the + surface. Defaults to transmissive boundary condition where particles + freely pass through the surface. name : str, optional Name of the cylinder. If not specified, the name will be the empty string. + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. Attributes ---------- @@ -1147,12 +1144,12 @@ class YCylinder(Cylinder): _type = 'y-cylinder' _coeff_keys = ('x0', 'z0', 'r') - def __init__(self, surface_id=None, boundary_type='transmission', - x0=0., z0=0., r=1., name='', *, R=None): + def __init__(self, x0=0., z0=0., r=1., boundary_type='transmission', + name='', surface_id=None, *, R=None): if R is not None: warn(_WARNING_UPPER.format(type(self).__name__, 'r', 'R'), FutureWarning) r = R - super().__init__(surface_id, boundary_type, r, name=name) + super().__init__(r, boundary_type, name, surface_id) self.x0 = x0 self.z0 = z0 @@ -1294,12 +1291,12 @@ class ZCylinder(Cylinder): _type = 'z-cylinder' _coeff_keys = ('x0', 'y0', 'r') - def __init__(self, surface_id=None, boundary_type='transmission', - x0=0., y0=0., r=1., name='', *, R=None): + def __init__(self, x0=0., y0=0., r=1., boundary_type='transmission', + name='', surface_id=None, *, R=None): if R is not None: warn(_WARNING_UPPER.format(type(self).__name__, 'r', 'R'), FutureWarning) r = R - super().__init__(surface_id, boundary_type, r, name=name) + super().__init__(r, boundary_type, name, surface_id) self.x0 = x0 self.y0 = y0 @@ -1400,13 +1397,6 @@ class Sphere(Surface): Parameters ---------- - surface_id : int, optional - Unique identifier for the surface. If not specified, an identifier will - automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective'}, optional - Boundary condition that defines the behavior for particles hitting the - surface. Defaults to transmissive boundary condition where particles - freely pass through the surface. x0 : float, optional x-coordinate of the center of the sphere. Defaults to 0. y0 : float, optional @@ -1415,8 +1405,15 @@ class Sphere(Surface): z-coordinate of the center of the sphere. Defaults to 0. r : float, optional Radius of the sphere. Defaults to 1. + boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + Boundary condition that defines the behavior for particles hitting the + surface. Defaults to transmissive boundary condition where particles + freely pass through the surface. name : str, optional Name of the sphere. If not specified, the name will be the empty string. + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. Attributes ---------- @@ -1445,8 +1442,8 @@ class Sphere(Surface): _type = 'sphere' _coeff_keys = ('x0', 'y0', 'z0', 'r') - def __init__(self, surface_id=None, boundary_type='transmission', - x0=0., y0=0., z0=0., r=1., name='', *, R=None): + def __init__(self, x0=0., y0=0., z0=0., r=1., boundary_type='transmission', + name='', surface_id=None, *, R=None): if R is not None: warn(_WARNING_UPPER.format(type(self).__name__, 'r', 'R'), FutureWarning) r = R @@ -1574,6 +1571,14 @@ class Cone(Surface): Parameters ---------- + x0 : float, optional + x-coordinate of the apex. Defaults to 0. + y0 : float, optional + y-coordinate of the apex. Defaults to 0. + z0 : float, optional + z-coordinate of the apex. Defaults to 0. + r2 : float, optional + Parameter related to the aperature. Defaults to 1. surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. @@ -1581,14 +1586,6 @@ class Cone(Surface): Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. - x0 : float, optional - x-coordinate of the apex. Defaults to 0. - y0 : float - y-coordinate of the apex. Defaults to 0. - z0 : float - z-coordinate of the apex. Defaults to 0. - r2 : float - Parameter related to the aperature. Defaults to 1. name : str Name of the cone. If not specified, the name will be the empty string. @@ -1618,8 +1615,8 @@ class Cone(Surface): _coeff_keys = ('x0', 'y0', 'z0', 'r2') - def __init__(self, surface_id=None, boundary_type='transmission', - x0=0., y0=0., z0=0., r2=1., name='', *, R2=None): + def __init__(self, x0=0., y0=0., z0=0., r2=1., boundary_type='transmission', + name='', surface_id=None, *, R2=None): if R2 is not None: warn(_WARNING_UPPER.format(type(self).__name__, 'r2', 'R2'), FutureWarning) r2 = R2 @@ -1695,13 +1692,6 @@ class XCone(Cone): Parameters ---------- - surface_id : int, optional - Unique identifier for the surface. If not specified, an identifier will - automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective'}, optional - Boundary condition that defines the behavior for particles hitting the - surface. Defaults to transmissive boundary condition where particles - freely pass through the surface. x0 : float, optional x-coordinate of the apex. Defaults to 0. y0 : float, optional @@ -1710,8 +1700,15 @@ class XCone(Cone): z-coordinate of the apex. Defaults to 0. r2 : float, optional Parameter related to the aperature. Defaults to 1. + boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + Boundary condition that defines the behavior for particles hitting the + surface. Defaults to transmissive boundary condition where particles + freely pass through the surface. name : str, optional Name of the cone. If not specified, the name will be the empty string. + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. Attributes ---------- @@ -1766,13 +1763,6 @@ class YCone(Cone): Parameters ---------- - surface_id : int, optional - Unique identifier for the surface. If not specified, an identifier will - automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective'}, optional - Boundary condition that defines the behavior for particles hitting the - surface. Defaults to transmissive boundary condition where particles - freely pass through the surface. x0 : float, optional x-coordinate of the apex. Defaults to 0. y0 : float, optional @@ -1781,8 +1771,15 @@ class YCone(Cone): z-coordinate of the apex. Defaults to 0. r2 : float, optional Parameter related to the aperature. Defaults to 1. + boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + Boundary condition that defines the behavior for particles hitting the + surface. Defaults to transmissive boundary condition where particles + freely pass through the surface. name : str, optional Name of the cone. If not specified, the name will be the empty string. + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. Attributes ---------- @@ -1837,13 +1834,6 @@ class ZCone(Cone): Parameters ---------- - surface_id : int, optional - Unique identifier for the surface. If not specified, an identifier will - automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective'}, optional - Boundary condition that defines the behavior for particles hitting the - surface. Defaults to transmissive boundary condition where particles - freely pass through the surface. x0 : float, optional x-coordinate of the apex. Defaults to 0. y0 : float, optional @@ -1852,8 +1842,15 @@ class ZCone(Cone): z-coordinate of the apex. Defaults to 0. r2 : float, optional Parameter related to the aperature. Defaults to 1. + boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + Boundary condition that defines the behavior for particles hitting the + surface. Defaults to transmissive boundary condition where particles + freely pass through the surface. name : str, optional Name of the cone. If not specified, the name will be the empty string. + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. Attributes ---------- @@ -1908,17 +1905,17 @@ class Quadric(Surface): Parameters ---------- - surface_id : int, optional - Unique identifier for the surface. If not specified, an identifier will - automatically be assigned. + a, b, c, d, e, f, g, h, j, k : float, optional + coefficients for the surface. All default to 0. boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. - a, b, c, d, e, f, g, h, j, k : float, optional - coefficients for the surface. All default to 0. name : str, optional Name of the surface. If not specified, the name will be the empty string. + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. Attributes ---------- @@ -1941,9 +1938,8 @@ class Quadric(Surface): _type = 'quadric' _coeff_keys = ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k') - def __init__(self, surface_id=None, boundary_type='transmission', - a=0., b=0., c=0., d=0., e=0., f=0., g=0., - h=0., j=0., k=0., name=''): + def __init__(self, a=0., b=0., c=0., d=0., e=0., f=0., g=0., h=0., j=0., + k=0., boundary_type='transmission', name='', surface_id=None): super().__init__(surface_id, boundary_type, name=name) self.a = a self.b = b diff --git a/tests/regression_tests/asymmetric_lattice/test.py b/tests/regression_tests/asymmetric_lattice/test.py index d6a0ab9b73..7ed0293ce9 100644 --- a/tests/regression_tests/asymmetric_lattice/test.py +++ b/tests/regression_tests/asymmetric_lattice/test.py @@ -25,12 +25,12 @@ class AsymmetricLatticeTestHarness(PyAPITestHarness): [water, water, water]] # Create bounding surfaces - min_x = openmc.XPlane(x0=-32.13, boundary_type='reflective') - max_x = openmc.XPlane(x0=+32.13, boundary_type='reflective') - min_y = openmc.YPlane(y0=-32.13, boundary_type='reflective') - max_y = openmc.YPlane(y0=+32.13, boundary_type='reflective') - min_z = openmc.ZPlane(z0=0, boundary_type='reflective') - max_z = openmc.ZPlane(z0=+32.13, boundary_type='reflective') + min_x = openmc.XPlane(-32.13, 'reflective') + max_x = openmc.XPlane(+32.13, 'reflective') + min_y = openmc.YPlane(-32.13, 'reflective') + max_y = openmc.YPlane(+32.13, 'reflective') + min_z = openmc.ZPlane(0, 'reflective') + max_z = openmc.ZPlane(+32.13, 'reflective') # Define root universe root_univ = openmc.Universe(universe_id=0, name='root universe') diff --git a/tests/regression_tests/deplete/example_geometry.py b/tests/regression_tests/deplete/example_geometry.py index d4fdf585df..f79044558c 100644 --- a/tests/regression_tests/deplete/example_geometry.py +++ b/tests/regression_tests/deplete/example_geometry.py @@ -158,6 +158,7 @@ def generate_initial_number_density(): return temperature, sab, initial_density, burn + def segment_pin(n_rings, n_wedges, r_fuel, r_gap, r_clad): """ Calculates a segmented pin. @@ -248,6 +249,7 @@ def segment_pin(n_rings, n_wedges, r_fuel, r_gap, r_clad): return fuel_u, v_segment, v_gap, v_clad + def generate_geometry(n_rings, n_wedges): """ Generates example geometry. @@ -296,12 +298,12 @@ def generate_geometry(n_rings, n_wedges): lattice.outer = all_water_u # Bound universe - x_low = openmc.XPlane(x0=-pitch*n_pin/2, boundary_type='reflective') - x_high = openmc.XPlane(x0=pitch*n_pin/2, boundary_type='reflective') - y_low = openmc.YPlane(y0=-pitch*n_pin/2, boundary_type='reflective') - y_high = openmc.YPlane(y0=pitch*n_pin/2, boundary_type='reflective') - z_low = openmc.ZPlane(z0=-10, boundary_type='reflective') - z_high = openmc.ZPlane(z0=10, boundary_type='reflective') + x_low = openmc.XPlane(-pitch*n_pin/2, 'reflective') + x_high = openmc.XPlane(pitch*n_pin/2, 'reflective') + y_low = openmc.YPlane(-pitch*n_pin/2, 'reflective') + y_high = openmc.YPlane(pitch*n_pin/2, 'reflective') + z_low = openmc.ZPlane(-10, 'reflective') + z_high = openmc.ZPlane(10, 'reflective') # Compute bounding box lower_left = [-pitch*n_pin/2, -pitch*n_pin/2, -10] @@ -317,10 +319,11 @@ def generate_geometry(n_rings, n_wedges): v_cool = pitch**2 - (v_gap + v_clad + n_rings * n_wedges * v_segment) # Store volumes for later usage - volume = {'fuel': v_segment, 'gap':v_gap, 'clad':v_clad, 'cool':v_cool} + volume = {'fuel': v_segment, 'gap': v_gap, 'clad': v_clad, 'cool': v_cool} return geometry, volume, mapping, lower_left, upper_right + def generate_problem(n_rings=5, n_wedges=8): """ Merges geometry and materials. diff --git a/tests/regression_tests/periodic/test.py b/tests/regression_tests/periodic/test.py index 5df935b22e..879bd84809 100644 --- a/tests/regression_tests/periodic/test.py +++ b/tests/regression_tests/periodic/test.py @@ -21,16 +21,16 @@ class PeriodicTest(PyAPITestHarness): materials.export_to_xml() # Define geometry - x_min = openmc.XPlane(1, x0=-5., boundary_type='periodic') - x_max = openmc.XPlane(2, x0=5., boundary_type='periodic') + x_min = openmc.XPlane(surface_id=1, x0=-5., boundary_type='periodic') + x_max = openmc.XPlane(surface_id=2, x0=5., boundary_type='periodic') x_max.periodic_surface = x_min - y_min = openmc.YPlane(3, y0=-5., boundary_type='periodic') - y_max = openmc.YPlane(4, y0=5., boundary_type='periodic') + y_min = openmc.YPlane(surface_id=3, y0=-5., boundary_type='periodic') + y_max = openmc.YPlane(surface_id=4, y0=5., boundary_type='periodic') - z_min = openmc.ZPlane(5, z0=-5., boundary_type='reflective') - z_max = openmc.ZPlane(6, z0=5., boundary_type='reflective') - z_cyl = openmc.ZCylinder(7, x0=-2.5, y0=2.5, r=2.0) + z_min = openmc.ZPlane(surface_id=5, z0=-5., boundary_type='reflective') + z_max = openmc.ZPlane(surface_id=6, z0=5., boundary_type='reflective') + z_cyl = openmc.ZCylinder(surface_id=7, x0=-2.5, y0=2.5, r=2.0) outside_cyl = openmc.Cell(1, fill=water, region=( +x_min & -x_max & +y_min & -y_max & +z_min & -z_max & +z_cyl)) diff --git a/tests/regression_tests/photon_production/test.py b/tests/regression_tests/photon_production/test.py index c6a1fc3908..1f645ebb94 100644 --- a/tests/regression_tests/photon_production/test.py +++ b/tests/regression_tests/photon_production/test.py @@ -14,10 +14,10 @@ class SourceTestHarness(PyAPITestHarness): materials = openmc.Materials([mat]) materials.export_to_xml() - cyl = openmc.XCylinder(boundary_type='vacuum', r=1.0) - x_plane_left = openmc.XPlane(boundary_type='vacuum', x0=-1.0) - x_plane_center = openmc.XPlane(boundary_type='transmission', x0=1.0) - x_plane_right = openmc.XPlane(boundary_type='vacuum', x0=1.0e9) + cyl = openmc.XCylinder(r=1.0, boundary_type='vacuum') + x_plane_left = openmc.XPlane(-1.0, 'vacuum') + x_plane_center = openmc.XPlane(1.0) + x_plane_right = openmc.XPlane(1.0e9, 'vacuum') inner_cyl_left = openmc.Cell() inner_cyl_right = openmc.Cell() @@ -31,7 +31,7 @@ class SourceTestHarness(PyAPITestHarness): geometry.export_to_xml() source = openmc.Source() - source.space = openmc.stats.Point((0,0,0)) + source.space = openmc.stats.Point((0, 0, 0)) source.angle = openmc.stats.Monodirectional() source.energy = openmc.stats.Discrete([14.0e6], [1.0]) source.particle = 'neutron' diff --git a/tests/regression_tests/resonance_scattering/test.py b/tests/regression_tests/resonance_scattering/test.py index c83005ff2c..588ba7c977 100644 --- a/tests/regression_tests/resonance_scattering/test.py +++ b/tests/regression_tests/resonance_scattering/test.py @@ -17,7 +17,7 @@ class ResonanceScatteringTestHarness(PyAPITestHarness): mats_file.export_to_xml() # Geometry - dumb_surface = openmc.XPlane(x0=100, boundary_type='reflective') + dumb_surface = openmc.XPlane(100, 'reflective') c1 = openmc.Cell(cell_id=1, fill=mat, region=-dumb_surface) root_univ = openmc.Universe(universe_id=0, cells=[c1]) geometry = openmc.Geometry(root_univ) diff --git a/tests/regression_tests/salphabeta/test.py b/tests/regression_tests/salphabeta/test.py index ab5305126e..f487723dee 100644 --- a/tests/regression_tests/salphabeta/test.py +++ b/tests/regression_tests/salphabeta/test.py @@ -44,11 +44,11 @@ def make_model(): model.materials += [m1, m2, m3, m4] # Geometry - x0 = openmc.XPlane(x0=-10, boundary_type='vacuum') - x1 = openmc.XPlane(x0=-5) - x2 = openmc.XPlane(x0=0) - x3 = openmc.XPlane(x0=5) - x4 = openmc.XPlane(x0=10, boundary_type='vacuum') + x0 = openmc.XPlane(-10, 'vacuum') + x1 = openmc.XPlane(-5) + x2 = openmc.XPlane(0) + x3 = openmc.XPlane(5) + x4 = openmc.XPlane(10, 'vacuum') root_univ = openmc.Universe() diff --git a/tests/regression_tests/triso/test.py b/tests/regression_tests/triso/test.py index aa1bf08289..7bef80ea87 100644 --- a/tests/regression_tests/triso/test.py +++ b/tests/regression_tests/triso/test.py @@ -54,12 +54,12 @@ class TRISOTestHarness(PyAPITestHarness): inner_univ = openmc.Universe(cells=[c1, c2, c3, c4, c5]) # Define box to contain lattice and to pack TRISO particles in - min_x = openmc.XPlane(x0=-0.5, boundary_type='reflective') - max_x = openmc.XPlane(x0=0.5, boundary_type='reflective') - min_y = openmc.YPlane(y0=-0.5, boundary_type='reflective') - max_y = openmc.YPlane(y0=0.5, boundary_type='reflective') - min_z = openmc.ZPlane(z0=-0.5, boundary_type='reflective') - max_z = openmc.ZPlane(z0=0.5, boundary_type='reflective') + min_x = openmc.XPlane(-0.5, 'reflective') + max_x = openmc.XPlane(0.5, 'reflective') + min_y = openmc.YPlane(-0.5, 'reflective') + max_y = openmc.YPlane(0.5, 'reflective') + min_z = openmc.ZPlane(-0.5, 'reflective') + max_z = openmc.ZPlane(0.5, 'reflective') box_region = +min_x & -max_x & +min_y & -max_y & +min_z & -max_z box = openmc.Cell(region=box_region) diff --git a/tests/regression_tests/volume_calc/test.py b/tests/regression_tests/volume_calc/test.py index da8399c1fd..8ea4ab04e0 100644 --- a/tests/regression_tests/volume_calc/test.py +++ b/tests/regression_tests/volume_calc/test.py @@ -25,11 +25,11 @@ class VolumeTest(PyAPITestHarness): materials = openmc.Materials((water, fuel)) materials.export_to_xml() - cyl = openmc.ZCylinder(1, r=1.0, boundary_type='vacuum') - top_sphere = openmc.Sphere(2, z0=5., r=1., boundary_type='vacuum') - top_plane = openmc.ZPlane(3, z0=5.) - bottom_sphere = openmc.Sphere(4, z0=-5., r=1., boundary_type='vacuum') - bottom_plane = openmc.ZPlane(5, z0=-5.) + cyl = openmc.ZCylinder(surface_id=1, r=1.0, boundary_type='vacuum') + top_sphere = openmc.Sphere(surface_id=2, z0=5., r=1., boundary_type='vacuum') + top_plane = openmc.ZPlane(surface_id=3, z0=5.) + bottom_sphere = openmc.Sphere(surface_id=4, z0=-5., r=1., boundary_type='vacuum') + bottom_plane = openmc.ZPlane(surface_id=5, z0=-5.) # Define geometry inside_cyl = openmc.Cell(1, fill=fuel, region=-cyl & -top_plane & +bottom_plane) diff --git a/tests/unit_tests/conftest.py b/tests/unit_tests/conftest.py index aa28be9c3b..27e20173ed 100644 --- a/tests/unit_tests/conftest.py +++ b/tests/unit_tests/conftest.py @@ -97,11 +97,11 @@ def mixed_lattice_model(uo2, water): [empty_univ, u] ] - xmin = openmc.XPlane(x0=-d, boundary_type='periodic') - xmax = openmc.XPlane(x0=d, boundary_type='periodic') + xmin = openmc.XPlane(-d, 'periodic') + xmax = openmc.XPlane(d, 'periodic') xmin.periodic_surface = xmax - ymin = openmc.YPlane(y0=-d, boundary_type='periodic') - ymax = openmc.YPlane(y0=d, boundary_type='periodic') + ymin = openmc.YPlane(-d, 'periodic') + ymax = openmc.YPlane(d, 'periodic') main_cell = openmc.Cell(fill=rect_lattice, region=+xmin & -xmax & +ymin & -ymax) diff --git a/tests/unit_tests/test_complex_cell_capi.py b/tests/unit_tests/test_complex_cell_capi.py index 365ce48081..ffe8045430 100644 --- a/tests/unit_tests/test_complex_cell_capi.py +++ b/tests/unit_tests/test_complex_cell_capi.py @@ -27,21 +27,21 @@ def complex_cell(run_in_tmpdir): model.materials = (u235, u238, zr90, n14) - s1 = openmc.XPlane(x0=-10.0, boundary_type='vacuum') - s2 = openmc.XPlane(x0=-7.0) - s3 = openmc.XPlane(x0=-4.0) - s4 = openmc.XPlane(x0=4.0) - s5 = openmc.XPlane(x0=7.0) - s6 = openmc.XPlane(x0=10.0, boundary_type='vacuum') - s7 = openmc.XPlane(x0=0.0) + s1 = openmc.XPlane(-10.0, 'vacuum') + s2 = openmc.XPlane(-7.0) + s3 = openmc.XPlane(-4.0) + s4 = openmc.XPlane(4.0) + s5 = openmc.XPlane(7.0) + s6 = openmc.XPlane(10.0, 'vacuum') + s7 = openmc.XPlane(0.0) - s11 = openmc.YPlane(y0=-10.0, boundary_type='vacuum') - s12 = openmc.YPlane(y0=-7.0) - s13 = openmc.YPlane(y0=-4.0) - s14 = openmc.YPlane(y0=4.0) - s15 = openmc.YPlane(y0=7.0) - s16 = openmc.YPlane(y0=10.0, boundary_type='vacuum') - s17 = openmc.YPlane(y0=0.0) + s11 = openmc.YPlane(-10.0, 'vacuum') + s12 = openmc.YPlane(-7.0) + s13 = openmc.YPlane(-4.0) + s14 = openmc.YPlane(4.0) + s15 = openmc.YPlane(7.0) + s16 = openmc.YPlane(10.0, 'vacuum') + s17 = openmc.YPlane(0.0) c1 = openmc.Cell(fill=u235) c1.region = ~(-s3 | +s4 | ~(+s13 & -s14)) diff --git a/tests/unit_tests/test_model_triso.py b/tests/unit_tests/test_model_triso.py index 782373414c..c1f8c039e5 100644 --- a/tests/unit_tests/test_model_triso.py +++ b/tests/unit_tests/test_model_triso.py @@ -34,12 +34,12 @@ def centers(request, container): @pytest.fixture(scope='module') def centers_rectangular_prism(): - min_x = openmc.XPlane(x0=0) - max_x = openmc.XPlane(x0=1) - min_y = openmc.YPlane(y0=0) - max_y = openmc.YPlane(y0=1) - min_z = openmc.ZPlane(z0=0) - max_z = openmc.ZPlane(z0=1) + min_x = openmc.XPlane(0) + max_x = openmc.XPlane(1) + min_y = openmc.YPlane(0) + max_y = openmc.YPlane(1) + min_z = openmc.ZPlane(0) + max_z = openmc.ZPlane(1) region = +min_x & -max_x & +min_y & -max_y & +min_z & -max_z return openmc.model.pack_spheres(radius=_RADIUS, region=region, pf=_PACKING_FRACTION, initial_pf=0.2) @@ -48,8 +48,8 @@ def centers_rectangular_prism(): @pytest.fixture(scope='module') def centers_x_cylinder(): cylinder = openmc.XCylinder(r=1, y0=1, z0=2) - min_x = openmc.XPlane(x0=0) - max_x = openmc.XPlane(x0=1) + min_x = openmc.XPlane(0) + max_x = openmc.XPlane(1) region = +min_x & -max_x & -cylinder return openmc.model.pack_spheres(radius=_RADIUS, region=region, pf=_PACKING_FRACTION, initial_pf=0.2) @@ -58,8 +58,8 @@ def centers_x_cylinder(): @pytest.fixture(scope='module') def centers_y_cylinder(): cylinder = openmc.YCylinder(r=1, x0=1, z0=2) - min_y = openmc.YPlane(y0=0) - max_y = openmc.YPlane(y0=1) + min_y = openmc.YPlane(0) + max_y = openmc.YPlane(1) region = +min_y & -max_y & -cylinder return openmc.model.pack_spheres(radius=_RADIUS, region=region, pf=_PACKING_FRACTION, initial_pf=0.2) @@ -68,8 +68,8 @@ def centers_y_cylinder(): @pytest.fixture(scope='module') def centers_z_cylinder(): cylinder = openmc.ZCylinder(r=1, x0=1, y0=2) - min_z = openmc.ZPlane(z0=0) - max_z = openmc.ZPlane(z0=1) + min_z = openmc.ZPlane(0) + max_z = openmc.ZPlane(1) region = +min_z & -max_z & -cylinder return openmc.model.pack_spheres(radius=_RADIUS, region=region, pf=_PACKING_FRACTION, initial_pf=0.2) diff --git a/tests/unit_tests/test_region.py b/tests/unit_tests/test_region.py index d1074fd7d9..f9656ebb4b 100644 --- a/tests/unit_tests/test_region.py +++ b/tests/unit_tests/test_region.py @@ -11,8 +11,8 @@ def reset(): def test_union(reset): - s1 = openmc.XPlane(surface_id=1, x0=5) - s2 = openmc.XPlane(surface_id=2, x0=-5) + s1 = openmc.XPlane(x0=5, surface_id=1) + s2 = openmc.XPlane(x0=-5, surface_id=2) region = +s1 | -s2 assert isinstance(region, openmc.Union) @@ -42,8 +42,8 @@ def test_union(reset): def test_intersection(reset): - s1 = openmc.XPlane(surface_id=1, x0=5) - s2 = openmc.XPlane(surface_id=2, x0=-5) + s1 = openmc.XPlane(x0=5, surface_id=1) + s2 = openmc.XPlane(x0=-5, surface_id=2) region = -s1 & +s2 assert isinstance(region, openmc.Intersection) @@ -75,9 +75,9 @@ def test_intersection(reset): def test_complement(reset): - zcyl = openmc.ZCylinder(surface_id=1, r=1.) - z0 = openmc.ZPlane(surface_id=2, z0=-5.) - z1 = openmc.ZPlane(surface_id=3, z0=5.) + zcyl = openmc.ZCylinder(r=1., surface_id=1) + z0 = openmc.ZPlane(-5., surface_id=2) + z1 = openmc.ZPlane(5., surface_id=3) outside = +zcyl | -z0 | +z1 inside = ~outside outside_equiv = ~(-zcyl & +z0 & -z1) @@ -151,8 +151,8 @@ def test_extend_clone(): def test_from_expression(reset): # Create surface dictionary s1 = openmc.ZCylinder(surface_id=1) - s2 = openmc.ZPlane(surface_id=2, z0=-10.) - s3 = openmc.ZPlane(surface_id=3, z0=10.) + s2 = openmc.ZPlane(-10., surface_id=2) + s3 = openmc.ZPlane(10., surface_id=3) surfs = {1: s1, 2: s2, 3: s3} r = openmc.Region.from_expression('-1 2 -3', surfs) diff --git a/tests/unit_tests/test_surface.py b/tests/unit_tests/test_surface.py index 8ce1040213..703010a059 100644 --- a/tests/unit_tests/test_surface.py +++ b/tests/unit_tests/test_surface.py @@ -55,7 +55,7 @@ def test_plane_from_points(): def test_xplane(): - s = openmc.XPlane(x0=3., boundary_type='reflective') + s = openmc.XPlane(3., 'reflective') assert s.x0 == 3. assert s.boundary_type == 'reflective' From 5f7bf876315fd31ad37ce0d5083282960ce76fe5 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 5 Sep 2019 06:20:38 -0500 Subject: [PATCH 004/158] Update release notes for 0.11.0 --- docs/source/publications.rst | 15 ++++++++++++--- docs/source/releasenotes/0.11.0.rst | 29 ++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/docs/source/publications.rst b/docs/source/publications.rst index f33cf79587..b6b747f451 100644 --- a/docs/source/publications.rst +++ b/docs/source/publications.rst @@ -58,7 +58,8 @@ Coupling and Multi-physics -------------------------- - Miriam A. Kreher, Benoit Forget, and Kord Smith, "Single-Batch Monte Carlo - Multiphysics Coupling," *Proc. M&C*, Portland, Oregon, Aug. 25-29 (2019). + Multiphysics Coupling," *Proc. M&C*, 1789-1797, Portland, Oregon, Aug. 25-29 + (2019). - Ze-Long Zhao, Yongwei Yang, and Shuang Hong, "`Application of FLUKA and OpenMC in coupled physics calculation of target and subcritical reactor for ADS @@ -118,7 +119,7 @@ Geometry and Visualization - Sterling Harper, Paul Romano, Benoit Forget, and Kord Smith, "Efficient dynamic threadsafe neighbor lists for Monte Carlo ray tracing," *Proc. M&C*, - Portland, Oregon, Aug. 25-29 (2019). + 918-926, Portland, Oregon, Aug. 25-29 (2019). - Jin-Yang Li, Long Gu, Hu-Shan Xu, Nadezha Korepanova, Rui Yu, Yan-Lei Zhu, and Chang-Ping Qin, "`CAD modeling study on FLUKA and OpenMC for accelerator @@ -141,6 +142,10 @@ Geometry and Visualization Miscellaneous ------------- +- Shikhar Kumar, Benoit Forget, and Kord Smith, "Analysis of fission source + convergence for a 3-D SMR core using functional expansion tallies," *Proc. + M&C*, 937-947, Portland, Oregon, Aug. 25-29 (2019). + - Faisal Qayyum, Muhammad R. Ali, Awais Zahur, and R. Khan, "`Improvements in methodology to determine feedback reactivity coefficients `_," *Nucl. Sci. Tech.*, **30**: 63 @@ -496,7 +501,7 @@ Depletion - Jose L. Salcedo-Perez, Benoit Forget, Kord Smith, and Paul Romano, "Hybrid tallies to improve performance in depletion Monte Carlo simulations," *Proc. - M&C*, Aug. 25-29 (2019). + M&C*, 927-936, Portland, Oregn, Aug. 25-29 (2019). - Zhao-Qing Liu, Ze-Long Zhao, Yong-Wei Yang, Yu-Cui Gao, Hai-Yan Meng, and Qing-Yu Gao, "`Development and validation of depletion code system IMPC-Burnup @@ -526,6 +531,10 @@ Depletion Sensitivity Analysis -------------------- +- Abdulla Alhajri and Benoit Forget, "Eigenvalue Sensitivity in Monte Carlo + Simulations to Nuclear Data Parameters using the Multipole Formalism," *Proc. + M&C*, 1895-1906, Portland, Oregn, Aug. 25-29 (2019). + - Xingjie Peng, Jingang Liang, Benoit Forget, and Kord Smith, "`Calculation of adjoint-weighted reactor kinetics parameters in OpenMC `_", *Ann. Nucl. Energy*, diff --git a/docs/source/releasenotes/0.11.0.rst b/docs/source/releasenotes/0.11.0.rst index 0bf44be3d4..52656a33c9 100644 --- a/docs/source/releasenotes/0.11.0.rst +++ b/docs/source/releasenotes/0.11.0.rst @@ -17,7 +17,7 @@ This release of OpenMC adds several major new features: :ref:`depletion `, photon transport, and support for CAD geometries through DAGMC. In addition, the core codebase has been rewritten in C++14 (it was previously written in Fortran 2008). This makes compiling the code -cosiderably simpler as no Fortran compiler is needed. +considerably simpler as no Fortran compiler is needed. Functional expansion tallies are now supported through several new tally filters that can be arbitrarily combined: @@ -40,6 +40,7 @@ random, non-overlapping configuration of spheres within the region. New Features ------------ +- Support for rectilinear meshes through :class:`openmc.RectilinearMesh`. - The :class:`Geometry`, :class:`Materials`, and :class:`Settings` classes now have a ``from_xml`` method that will build an instance from an existing XML file. @@ -54,6 +55,32 @@ New Features - The :mod:`openmc.data` module now supports reading and sampling from ENDF File 32 resonance covariance data (`PR 1024 `_). +- Several new convenience functions/methods have been added: + + - The :func:`openmc.model.cylinder_from_points` function creates a cylinder + given two points passing through its center and a radius. + - The :meth:`openmc.Plane.from_points` function creates a plane given three + points that pass through it. + - The :func:`openmc.model.pin` function creates a pin cell universe given a + sequence of radii and materials. + +------------------ +Python API Changes +------------------ + +- All surface classes now have coefficient arguments given as lowercase names. +- The order of arguments in surface classes has been changed so that + coefficients are the first arguments (rather than the optional surface ID). + This means you can now write:: + + x = openmc.XPlane(5.0, 'reflective') + zc = openmc.ZCylinder(0., 0., 10.) + +- The ``Mesh`` class has been renamed :class:`openmc.RegularMesh`. +- The ``get_rectangular_prism`` function has been renamed + :func:`openmc.model.rectangular_prism`. +- The ``get_hexagonal_prism`` function has been renamed + :func:`openmc.model.hexagonal_prism`. --------- Bug Fixes From a3e78915cfacd6c7da667b563785521d5cd883a8 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 5 Sep 2019 07:00:38 -0500 Subject: [PATCH 005/158] Fix __version__ in Python API --- openmc/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/__init__.py b/openmc/__init__.py index 1661c4ac47..1195db725e 100644 --- a/openmc/__init__.py +++ b/openmc/__init__.py @@ -34,4 +34,4 @@ from . import examples # Import a few convencience functions that used to be here from openmc.model import rectangular_prism, hexagonal_prism -__version__ = '0.10.0' +__version__ = '0.11.0-dev' From cb73c87d9c592f5d8602ebc86926320dab44337c Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 5 Sep 2019 16:30:42 -0500 Subject: [PATCH 006/158] Respond to comments on #1330 --- docs/source/publications.rst | 4 ++-- docs/source/releasenotes/0.11.0.rst | 2 +- tests/unit_tests/__init__.py | 7 +++++++ tests/unit_tests/test_data_neutron.py | 6 +----- tests/unit_tests/test_data_thermal.py | 7 ++----- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/source/publications.rst b/docs/source/publications.rst index b6b747f451..32b911e5b8 100644 --- a/docs/source/publications.rst +++ b/docs/source/publications.rst @@ -501,7 +501,7 @@ Depletion - Jose L. Salcedo-Perez, Benoit Forget, Kord Smith, and Paul Romano, "Hybrid tallies to improve performance in depletion Monte Carlo simulations," *Proc. - M&C*, 927-936, Portland, Oregn, Aug. 25-29 (2019). + M&C*, 927-936, Portland, Oregon, Aug. 25-29 (2019). - Zhao-Qing Liu, Ze-Long Zhao, Yong-Wei Yang, Yu-Cui Gao, Hai-Yan Meng, and Qing-Yu Gao, "`Development and validation of depletion code system IMPC-Burnup @@ -533,7 +533,7 @@ Sensitivity Analysis - Abdulla Alhajri and Benoit Forget, "Eigenvalue Sensitivity in Monte Carlo Simulations to Nuclear Data Parameters using the Multipole Formalism," *Proc. - M&C*, 1895-1906, Portland, Oregn, Aug. 25-29 (2019). + M&C*, 1895-1906, Portland, Oregon, Aug. 25-29 (2019). - Xingjie Peng, Jingang Liang, Benoit Forget, and Kord Smith, "`Calculation of adjoint-weighted reactor kinetics parameters in OpenMC diff --git a/docs/source/releasenotes/0.11.0.rst b/docs/source/releasenotes/0.11.0.rst index 52656a33c9..bf4719374a 100644 --- a/docs/source/releasenotes/0.11.0.rst +++ b/docs/source/releasenotes/0.11.0.rst @@ -62,7 +62,7 @@ New Features - The :meth:`openmc.Plane.from_points` function creates a plane given three points that pass through it. - The :func:`openmc.model.pin` function creates a pin cell universe given a - sequence of radii and materials. + sequence of concentric cylinders and materials. ------------------ Python API Changes diff --git a/tests/unit_tests/__init__.py b/tests/unit_tests/__init__.py index 59a520c12a..e97ab13e2a 100644 --- a/tests/unit_tests/__init__.py +++ b/tests/unit_tests/__init__.py @@ -1,7 +1,14 @@ +import shutil + import numpy as np import pytest +# Check if NJOY is available +needs_njoy = pytest.mark.skipif(shutil.which('njoy') is None, + reason="NJOY not installed") + + def assert_unbounded(obj): """Assert that a region/cell is unbounded.""" ll, ur = obj.bounding_box diff --git a/tests/unit_tests/test_data_neutron.py b/tests/unit_tests/test_data_neutron.py index 5313199a71..c4eb6a9083 100644 --- a/tests/unit_tests/test_data_neutron.py +++ b/tests/unit_tests/test_data_neutron.py @@ -1,20 +1,16 @@ from collections.abc import Mapping, Callable import os -import shutil import numpy as np import pandas as pd import pytest import openmc.data +from . import needs_njoy _TEMPERATURES = [300., 600., 900.] _ENDF_DATA = os.environ['OPENMC_ENDF_DATA'] -# Check if NJOY is available -needs_njoy = pytest.mark.skipif(shutil.which('njoy') is None, - reason="NJOY not installed") - @pytest.fixture(scope='module') def pu239(): diff --git a/tests/unit_tests/test_data_thermal.py b/tests/unit_tests/test_data_thermal.py index ef6fb704cb..2d8d7de2c4 100644 --- a/tests/unit_tests/test_data_thermal.py +++ b/tests/unit_tests/test_data_thermal.py @@ -2,19 +2,16 @@ from collections.abc import Callable from math import exp import os import random -import shutil import numpy as np import pytest import openmc.data +from . import needs_njoy + _ENDF_DATA = os.environ['OPENMC_ENDF_DATA'] -# Check if NJOY is available -needs_njoy = pytest.mark.skipif(shutil.which('njoy') is None, - reason="NJOY not installed") - @pytest.fixture(scope='module') def h2o(): From 20ce641eb32f60ddedda92180158f464647a98ac Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 5 Sep 2019 14:05:23 -0500 Subject: [PATCH 007/158] Upgrade pincell depletion python examples Relied on function-based depletion rather than the new classes. Now uses ResultsList.from_hdf5 to obtain the results information --- examples/python/pincell_depletion/restart_depletion.py | 5 +++-- examples/python/pincell_depletion/run_depletion.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/examples/python/pincell_depletion/restart_depletion.py b/examples/python/pincell_depletion/restart_depletion.py index d5a0e2234a..013a2469e0 100644 --- a/examples/python/pincell_depletion/restart_depletion.py +++ b/examples/python/pincell_depletion/restart_depletion.py @@ -61,14 +61,15 @@ op = openmc.deplete.Operator(geometry, settings_file, chain_file, previous_results) # Perform simulation using the predictor algorithm -openmc.deplete.integrator.predictor(op, time_steps, power) +integrator = openmc.deplete.PredictorIntegrator(op, time_steps, power) +integrator.integrate() ############################################################################### # Read depletion calculation results ############################################################################### # Open results file -results = openmc.deplete.ResultsList("depletion_results.h5") +results = openmc.deplete.ResultsList.from_hdf5("depletion_results.h5") # Obtain K_eff as a function of time time, keff = results.get_eigenvalue() diff --git a/examples/python/pincell_depletion/run_depletion.py b/examples/python/pincell_depletion/run_depletion.py index 5a018cda10..9933edd48a 100644 --- a/examples/python/pincell_depletion/run_depletion.py +++ b/examples/python/pincell_depletion/run_depletion.py @@ -132,14 +132,15 @@ settings_file.entropy_mesh = entropy_mesh op = openmc.deplete.Operator(geometry, settings_file, chain_file) # Perform simulation using the predictor algorithm -openmc.deplete.integrator.predictor(op, time_steps, power) +integrator = openmc.deplete.PredictorIntegrator(op, time_steps, power) +integrator.integrate() ############################################################################### # Read depletion calculation results ############################################################################### # Open results file -results = openmc.deplete.ResultsList("depletion_results.h5") +results = openmc.deplete.ResultsList.from_hdf5("depletion_results.h5") # Obtain K_eff as a function of time time, keff = results.get_eigenvalue() From 269b3bf2d192dd5c4c7002f7b111ca9c2ff9db00 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 5 Sep 2019 16:26:29 -0500 Subject: [PATCH 008/158] Update user's guide on depletion Indicate class-based integration, and pull results from file using `ResultsList.from_hdf5` --- docs/source/usersguide/depletion.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/usersguide/depletion.rst b/docs/source/usersguide/depletion.rst index 5dd98406e6..cf8f4c2093 100644 --- a/docs/source/usersguide/depletion.rst +++ b/docs/source/usersguide/depletion.rst @@ -29,8 +29,8 @@ operator class requires a :class:`openmc.Geometry` instance and a op = openmc.deplete.Operator(geom, settings) :mod:`openmc.deplete` supports multiple time-integration methods for determining -material compositions over time. Each method appears as a different function. -For example, :func:`openmc.deplete.integrator.cecm` runs a depletion calculation +material compositions over time. Each method appears as a different class. +For example, :class:`openmc.deplete.CECMIntegrator` runs a depletion calculation using the CE/CM algorithm (deplete over a timestep using the middle-of-step reaction rates). An instance of :class:`openmc.deplete.Operator` is passed to one of these functions along with the power level and timesteps:: @@ -38,7 +38,7 @@ one of these functions along with the power level and timesteps:: power = 1200.0e6 days = 24*60*60 timesteps = [10.0*days, 10.0*days, 10.0*days] - openmc.deplete.cecm(op, power, timesteps) + openmc.deplete.CECMIntegrator(op, power, timesteps).integrate() The coupled transport-depletion problem is executed, and once it is done a ``depletion_results.h5`` file is written. The results can be analyzed using the @@ -46,7 +46,7 @@ The coupled transport-depletion problem is executed, and once it is done a easy retrieval of k-effective, nuclide concentrations, and reaction rates over time:: - results = openmc.deplete.ResultsList("depletion_results.h5") + results = openmc.deplete.ResultsList.from_hdf5("depletion_results.h5") time, keff = results.get_eigenvalue() Note that the coupling between the transport solver and the transmutation solver From 8ca0d35ad6c01eca9b6a885349044b68d66c3a70 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 5 Sep 2019 16:31:46 -0500 Subject: [PATCH 009/158] Add pincell depletion jupyter notebook The basic structure is borrowed from examples/python/pincell_depletion/ and builds a single pin cell to be depleted. The model building is expedited, as this assumes the user has read through the basic user guide on model building. The new openmc.model.pin function is highlighted, both for building a plain pin and for subdividing regions for depletion. The Chain and Operator classes are the focuses of the example, and the PredictorIntegrator is used to deplete the pin out to 6 months using 30 day depletion steps with 174 W power. The openmc-make-depletion-chain scripts are introduced as methods for building initial depletion chains. Use of openmc.data.DataLibrary.register_file is included, as this allows the user to not pass the chain_file argument if the depletion chain is included in OPENMC_CROSS_SECTIONS. The jupyter notebook is included in the documentation similar to other examples. --- docs/source/examples/index.rst | 1 + docs/source/examples/pincell-depletion.rst | 14 + examples/jupyter/pincell_depletion.ipynb | 1028 ++++++++++++++++++++ 3 files changed, 1043 insertions(+) create mode 100644 docs/source/examples/pincell-depletion.rst create mode 100644 examples/jupyter/pincell_depletion.ipynb diff --git a/docs/source/examples/index.rst b/docs/source/examples/index.rst index 1633f9950b..529f9427ba 100644 --- a/docs/source/examples/index.rst +++ b/docs/source/examples/index.rst @@ -24,6 +24,7 @@ General Usage nuclear-data nuclear-data-resonance-covariance cad-geom + pincell-depletion -------- Geometry diff --git a/docs/source/examples/pincell-depletion.rst b/docs/source/examples/pincell-depletion.rst new file mode 100644 index 0000000000..348f3c6b95 --- /dev/null +++ b/docs/source/examples/pincell-depletion.rst @@ -0,0 +1,14 @@ +.. _notebook_depletion: + +================= +Pincell Depletion +================= + + +.. only:: html + + .. notebook:: ../../../examples/jupyter/pincell_depletion.ipynb + +.. only:: latex + + IPython notebooks must be viewed in the online HTML documentation. diff --git a/examples/jupyter/pincell_depletion.ipynb b/examples/jupyter/pincell_depletion.ipynb new file mode 100644 index 0000000000..2599290e3a --- /dev/null +++ b/examples/jupyter/pincell_depletion.ipynb @@ -0,0 +1,1028 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This notebook is intended to introduce the reader to the depletion interface contained in OpenMC. It is recommended that you are moderately familiar with building models using the OpenMC Python API. The earlier examples are excellent starting points, as this notebook will not focus heavily on model building.\n", + "\n", + "If you have a real power reactor, the fuel composition is constantly changing as fission events produce energy, remove some fissile isotopes, and produce fission products. Other reactions, like $(n, \\alpha)$ and $(n, \\gamma)$ will alter the composition as well. Furthermore, some nuclides undergo spontaneous decay with widely ranging frequencies. Depletion is the process of modeling this behavior.\n", + "\n", + "In this notebook, we will model a simple fuel pin in an infinite lattice using the Python API. We will then build and examine some of the necessary components for performing depletion analysis. Then, we will use the depletion interface in OpenMC to simulate the fuel pin producing power over several months. Lastly, we will wrap up with some helpful tips to improve the fidelity of depletion simulations." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "import openmc" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Build the Geometry\n", + "\n", + "Much of this section is borrowed from the \"Modeling a Pin-Cell\" example. If you find yourself not understanding some aspects of this section, feel free to refer to that example, as some details may be glossed over for brevity.\n", + "\n", + "First, we will create our fuel, cladding, and water materials to represent a typical PWR." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "fuel = openmc.Material(name=\"uo2\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "fuel.add_element(\"U\", 1, percent_type=\"ao\", enrichment=4.25)\n", + "fuel.add_element(\"O\", 2)\n", + "fuel.set_density(\"g/cc\", 10.4)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "clad = openmc.Material(name=\"clad\")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "clad.add_element(\"Zr\", 1)\n", + "clad.set_density(\"g/cc\", 6)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "water = openmc.Material(name=\"water\")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "water.add_element(\"O\", 1)\n", + "water.add_element(\"H\", 2)\n", + "water.set_density(\"g/cc\", 1.0)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "water.add_s_alpha_beta(\"c_H_in_H2O\")" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "materials = openmc.Materials([fuel, clad, water])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here, we are going to use the `openmc.model.pin` function to build our pin cell. The `pin` function anticipates concentric cylinders and materials to fill the inner regions. One additional material is needed than the number of cylinders to cover the domain outside the final ring. \n", + "\n", + "To do this, we define two radii for the outer radius of our fuel pin, and the outer radius of the cladding." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "radii = [0.42, 0.45]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Using these radii, we define concentric `ZCylinder` objects. So long as the cylinders are concentric and increasing in radius, any orientation can be used. We also take advantage of the fact that the `openmc.Materials` object is a subclass of the `list` object to assign materials to the regions defined by the surfaces." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "pin_surfaces = [openmc.ZCylinder(r=r) for r in radii]" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "pin_univ = openmc.model.pin(pin_surfaces, materials)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The first material, in our case `fuel`, is placed inside the first cylinder in the inner-most region. The second material, `clad`, fills the space between our cylinders, while `water` is placed outside the last ring. The `pin` function returns an `openmc.Universe` object, and has some additional features we will mention later." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "pin_univ.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "bound_box = openmc.rectangular_prism(0.62, 0.62, boundary_type=\"reflective\")" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "root_cell = openmc.Cell(fill=pin_univ, region=bound_box)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "root_univ = openmc.Universe(cells=[root_cell])" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "geometry = openmc.Geometry(root_univ)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Lastly we construct our settings. For the sake of time, a relatively low number of particles will be used." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "settings = openmc.Settings()" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "settings.particles = 100\n", + "settings.inactive = 10\n", + "settings.batches = 50" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The depletion interface relies on `OpenMC` to perform the transport simulation and obtain reaction rates and other important information. We then have to create the `xml` input files that `openmc` expects, specifically `geometry.xml`, `settings.xml`, and `materials.xml`." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [], + "source": [ + "geometry.export_to_xml()" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [], + "source": [ + "settings.export_to_xml()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Before we write the material file, we must add one bit of information: the volume of our fuel. In order to translate the reaction rates obtained by `openmc` to meaningful units for depletion, we have to normalize them to a correct power. This requires us to know, or be able to calculate, how much fuel is in our problem. Correctly setting the volumes is a critical step, and incorrect answers, as the fuel is over- or under-depleted due to poor normalization.\n", + "\n", + "For our problem, we can assign the \"volume\" to be the cross-sectional area of our fuel. This is identical to modeling our fuel pin inside a box with height of 1 cm." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [], + "source": [ + "fuel.volume = 3.14159 * radii[0] ** 2" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\r\n", + "\r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + "\r\n" + ] + } + ], + "source": [ + "materials.export_to_xml()\n", + "!cat materials.xml" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Setting up for depletion\n", + "\n", + "The OpenMC depletion interface can be accessed from the `openmc.deplete` module, and has a variety of classes that will help us." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [], + "source": [ + "import openmc.deplete" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In order to run the depletion calculation we need the following information:\n", + "\n", + "1. Nuclide decay, fission yield, and reaction data\n", + "2. Operational power or power density\n", + "3. Desired depletion schedule\n", + "4. Desired time integration scheme\n", + "\n", + "The first item is necessary to determine the paths by which nuclides transmute over the depletion simulation. This includes spontaneous decay, fission product yield distributions, and nuclides produced through neutron-reactions. For example,\n", + "* Te129 decays to I129 with a half life of ~70 minutes\n", + "* A fission event for U-235 produces fission products like Xe135 according to a distribution\n", + "* For thermal problems, Am241 will produce metastable Am242 about 8% of the time during an $(n,\\gamma)$ reaction. The other 92% of capture reactions will produce ground state Am242\n", + "\n", + "These data are often distributed with other nuclear data, like incident neutron cross sections with ENDF/B-VII. The `openmc.deplete.Chain` object is responsible for representing this information. Two executables, `openmc-make-depletion-chain` and `openmc-make-depletion-chain-casl` will read in nuclear data using the `OPENMC_ENDF_DATA` environment variable and produce two depletion chain files. The first is a more detailed file containing every reaction and fission product possible, while the second uses data from CASL [Specification for the VERA Depletion Benchmark Suite](https://doi.org/10.2172/1256820) report to reduce the number of isotopes.\n", + "\n", + "For this problem, we will be using a much smaller depletion chain that contains very few nuclides. In a realistic problem, over 1000 isotopes may be included in the depletion chain." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [], + "source": [ + "chain = openmc.deplete.Chain.from_xml(\"./chain_simple.xml\")" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "OrderedDict([('I135', 0),\n", + " ('Xe135', 1),\n", + " ('Xe136', 2),\n", + " ('Cs135', 3),\n", + " ('Gd157', 4),\n", + " ('Gd156', 5),\n", + " ('U234', 6),\n", + " ('U235', 7),\n", + " ('U238', 8)])" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "chain.nuclide_dict" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The primary entry point for depletion is the `openmc.deplete.Operator`. It relies on the `openmc.deplete.Chain` and helper classes to run `openmc`, retrieve and normalize reaction rates, and other perform other tasks. For a thorough description, please see the full API documentation." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will create our Operator using the geometry and settings from above, and our simple chain file. The materials are read in automatically using the `materials.xml` file." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [], + "source": [ + "operator = openmc.deplete.Operator(geometry, settings, \"./chain_simple.xml\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will then simulate our fuel pin operating at linear power of 174 W/cm, or 174 W given a unit height for our problem." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [], + "source": [ + "power = 174" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For this problem, we will take depletion step sizes of 30 days, and instruct OpenMC to re-run a transport simulation every 30 days until we have modeled the problem over a six month cycle. The depletion interface expects the time to be given in seconds, so we will have to convert. Note that these values are not cummulative." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [], + "source": [ + "time_steps = [30 * 24 * 60 * 60] * 6" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And lastly, we will use the basic predictor, or forward Euler, time integration scheme. Other, more advanced methods are provided to the user through `openmc.deplete`" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [], + "source": [ + "integrator = openmc.deplete.PredictorIntegrator(operator, time_steps, power)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To perform the simulation, we use the `integrate` method, and let `openmc` take care of the rest." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [], + "source": [ + "integrator.integrate()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Processing the outputs\n", + "\n", + "The depletion simulation produces a few output files. First, the statepoint files from each individual transport simulation are written to `openmc_simulation_n.h5`, where `` indicates the current depletion step. Any tallies that we defined in `tallies.xml` will be included in these files across our simulations. We have 7 such files, one for each our of 6 depletion steps and the initial state." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "c5g7.h5\t\t\t openmc_simulation_n2.h5 openmc_simulation_n6.h5\r\n", + "depletion_results.h5\t openmc_simulation_n3.h5 statepoint.50.h5\r\n", + "openmc_simulation_n0.h5 openmc_simulation_n4.h5 summary.h5\r\n", + "openmc_simulation_n1.h5 openmc_simulation_n5.h5\r\n" + ] + } + ], + "source": [ + "!ls *.h5" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `depletion_results.h5` file contains information that is aggregated over all time steps through depletion. This includes the multiplication factor, as well as concentrations. We can process this file using the `openmc.deplete.ResultsList` object" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [], + "source": [ + "results = openmc.deplete.ResultsList.from_hdf5(\"./depletion_results.h5\")" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [], + "source": [ + "time, k = results.get_eigenvalue()" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [], + "source": [ + "time /= (24 * 60 * 60) # convert back to days from seconds" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[0.76882937, 0.00982155],\n", + " [0.75724032, 0.00827689],\n", + " [0.75532241, 0.01031746],\n", + " [0.74796854, 0.00919769],\n", + " [0.74066559, 0.01157708],\n", + " [0.7318449 , 0.00971503],\n", + " [0.72072927, 0.00703074]])" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "k" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The first column of `k` is the value of `k-combined` at each point in our simulation, while the second column contains the associated uncertainty. We can plot this using `matplotlib`" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [], + "source": [ + "from matplotlib import pyplot" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "pyplot.errorbar(time, k[:, 0], yerr=k[:, 1])\n", + "pyplot.xlabel(\"Time [d]\")\n", + "pyplot.ylabel(\"$k_{eff}\\pm \\sigma$\");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Due to the low number of particles selected, we have not only a very high uncertainty, but likely a horrendously poor fission source. This pin cell should have $k>1$, but we can still see the decline over time due to fuel consumption." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can then examine concentrations of atoms in each of our materials. This requires knowing the material ID, which can be obtained from the `materials.xml` file." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [], + "source": [ + "_time, u5 = results.get_atoms(\"1\", \"U235\")\n", + "_time, xe135 = results.get_atoms(\"1\", \"Xe135\")" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "pyplot.plot(time, u5, label=\"U235\")\n", + "pyplot.xlabel(\"Time [d]\")\n", + "pyplot.ylabel(\"Number of atoms - U235\");" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "pyplot.plot(time, xe135, label=\"Xe135\")\n", + "pyplot.xlabel(\"Time [d]\")\n", + "pyplot.ylabel(\"Number of atoms - Xe135\");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also examine reaction rates over time using the `ResultsList`" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [], + "source": [ + "_time, u5_fission = results.get_reaction_rate(\"1\", \"U235\", \"fission\")" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "pyplot.plot(time, u5_fission)\n", + "pyplot.xlabel(\"Time [d]\")\n", + "pyplot.ylabel(\"Fission reactions / s\");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Helpful tips\n", + "\n", + "Depletion is a tricky task to get correct. Too short of time steps and you may never get your results due to running many transport simulations. Too long of time steps and you may get incorrect answers. Take the plot of xenon from above. Xenon is a fission product with a thermal absorption cross section on the order of millions of barns, but has a half life of ~9 hours. Taking smaller time steps at the beginning of your simulation to build up some equilibrium in your fission products is highly recommended.\n", + "\n", + "When possible, differentiate materials that reappear in multiple places. If we had built an entire core with the single `fuel` material, every pin would be depleted using the same averaged spectrum and reaction rates which is incorrect. The `Operator` can differentiate these materials using the `diff_burnable_mats` argument, but not that the volumes will be copied from the original material.\n", + "\n", + "Using higher-order integrators, like the `CECMIntegrator`, `EPCRK4Integrator` with a fourth order Runge-Kutta, or the `LEQIIntegrator`, can improve the accuracy of a simulation, or at least allow you to take longer depletion steps between transport simulations with similar accuracy.\n", + "\n", + "Fuel pins with integrated burnable absorbers, like gadolinia, experience strong flux gradients until the absorbers are mostly burned away. This means that the spectrum and magnitude of the flux at the edge of the fuel pin can be vastly different than that in the interior. The helper `pin` function can be used to subdivide regions into equal volume segments, as follows." + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [], + "source": [ + "div_surfs_1 = [openmc.ZCylinder(r=1)]\n", + "div_1 = openmc.model.pin(div_surfs_1, [fuel, water], subdivisions={0: 10})" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 45, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "div_1.plot(width=(2.0, 2.0))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The innermost region has been divided into 10 equal volume regions. We can pass additional arguments to divide multiple regions, except for the region outside the last cylinder." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Register depletion chain\n", + "\n", + "The depletion chain we created can be registered into the OpenMC `cross_sections.xml` file, so we don't have to always pass the `chain_file` argument to the `Operator`. To do this, we create a `DataLibrary` using `openmc.data`. Without any arguments, the `from_xml` method will look for the file located at `OPENMC_CROSS_SECTIONS`. For this example, we will just create a bare library." + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": {}, + "outputs": [], + "source": [ + "data_lib = openmc.data.DataLibrary()" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": {}, + "outputs": [], + "source": [ + "data_lib.register_file(\"./chain_simple.xml\")" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": {}, + "outputs": [], + "source": [ + "data_lib.export_to_xml()" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\r\n", + "\r\n", + " \r\n", + "\r\n" + ] + } + ], + "source": [ + "!cat cross_sections.xml" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This allows us to make an `Operator` simply with the geometry and settings arguments, provided we exported our library to `OPENMC_CROSS_SECTIONS`. For a problem where we built and registered a `Chain` using all the available nuclear data, we might see something like the following." + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": {}, + "outputs": [], + "source": [ + "new_op = openmc.deplete.Operator(geometry, settings)" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "3820" + ] + }, + "execution_count": 51, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(new_op.chain.nuclide_dict)" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'H7', 'He3', 'He4', 'He5']" + ] + }, + "execution_count": 52, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "[nuc.name for nuc in new_op.chain.nuclides[:10]]" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['Ds268',\n", + " 'Ds269',\n", + " 'Ds270',\n", + " 'Ds270_m1',\n", + " 'Ds271',\n", + " 'Ds271_m1',\n", + " 'Ds272',\n", + " 'Ds273',\n", + " 'Ds279_m1',\n", + " 'Rg272']" + ] + }, + "execution_count": 53, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "[nuc.name for nuc in new_op.chain.nuclides[-10:]]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Choice of depletion step size\n", + "\n", + "A general rule of thumb is to use depletion step sizes around 2 MWd/kgHM, where kgHM is really the initial heavy metal mass in kg. If your problem includes integral burnable absorbers, these typically require shorter time steps at or below 1 MWd/kgHM. These are typically valid for the predictor scheme, as the point of recent schemes is to extend this step size. A good convergence study, where the step size is decreased until some convergence metric is satisfied, is a beneficial exercise.\n", + "\n", + "We can use the `Operator` to determine our maximum step size using this recommendation. The `heavy_metal` attribute returns the mass of initial heavy metal in g, which, using our power, can be used to compute this step size. $$\\frac{2\\,MWd}{kgHM} = \\frac{P\\times\\Delta}{hm_{op}}$$" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "5.080334904405777" + ] + }, + "execution_count": 56, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "operator.heavy_metal" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": {}, + "outputs": [], + "source": [ + "max_step = 2 * operator.heavy_metal / power * 1E3" + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\"Maximum\" depletion step: 58.4 [d]\n" + ] + } + ], + "source": [ + "print(\"\\\"Maximum\\\" depletion step: {:5.3} [d]\".format(max_step))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Alternatively, if we were provided the power density of our problem, we can provide this directly with `openmc.deplete.PredictorIntegrator(operator, time_steps, power_density=pdens)`. The values of `power` and `power_density` do not have to be scalars. For problems with variable power, we can provide an iterable with the same number of elements as `time_steps`." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From a9b63e015800e9a8ae4e9672b5b0086a72f6e15c Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 6 Sep 2019 08:44:31 -0500 Subject: [PATCH 010/158] Address reviewer comments in depletion example notebook PR #1332 --- examples/jupyter/pincell_depletion.ipynb | 191 +++++++++-------------- 1 file changed, 78 insertions(+), 113 deletions(-) diff --git a/examples/jupyter/pincell_depletion.ipynb b/examples/jupyter/pincell_depletion.ipynb index 2599290e3a..63b644a9df 100644 --- a/examples/jupyter/pincell_depletion.ipynb +++ b/examples/jupyter/pincell_depletion.ipynb @@ -18,6 +18,7 @@ "outputs": [], "source": [ "%matplotlib inline\n", + "import math\n", "import openmc" ] }, @@ -167,7 +168,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 13, @@ -176,7 +177,7 @@ }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -281,63 +282,27 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Before we write the material file, we must add one bit of information: the volume of our fuel. In order to translate the reaction rates obtained by `openmc` to meaningful units for depletion, we have to normalize them to a correct power. This requires us to know, or be able to calculate, how much fuel is in our problem. Correctly setting the volumes is a critical step, and incorrect answers, as the fuel is over- or under-depleted due to poor normalization.\n", + "Before we write the material file, we must add one bit of information: the volume of our fuel. In order to translate the reaction rates obtained by `openmc` to meaningful units for depletion, we have to normalize them to a correct power. This requires us to know, or be able to calculate, how much fuel is in our problem. Correctly setting the volumes is a critical step, and can lead to incorrect answers, as the fuel is over- or under-depleted due to poor normalization.\n", "\n", "For our problem, we can assign the \"volume\" to be the cross-sectional area of our fuel. This is identical to modeling our fuel pin inside a box with height of 1 cm." ] }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 23, "metadata": {}, "outputs": [], "source": [ - "fuel.volume = 3.14159 * radii[0] ** 2" + "fuel.volume = math.pi * radii[0] ** 2" ] }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 24, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\r\n", - "\r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - "\r\n" - ] - } - ], + "outputs": [], "source": [ - "materials.export_to_xml()\n", - "!cat materials.xml" + "materials.export_to_xml()" ] }, { @@ -351,7 +316,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 25, "metadata": {}, "outputs": [], "source": [ @@ -381,7 +346,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 26, "metadata": {}, "outputs": [], "source": [ @@ -390,7 +355,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 27, "metadata": {}, "outputs": [ { @@ -407,7 +372,7 @@ " ('U238', 8)])" ] }, - "execution_count": 26, + "execution_count": 27, "metadata": {}, "output_type": "execute_result" } @@ -432,7 +397,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 28, "metadata": {}, "outputs": [], "source": [ @@ -448,7 +413,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 29, "metadata": {}, "outputs": [], "source": [ @@ -459,12 +424,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "For this problem, we will take depletion step sizes of 30 days, and instruct OpenMC to re-run a transport simulation every 30 days until we have modeled the problem over a six month cycle. The depletion interface expects the time to be given in seconds, so we will have to convert. Note that these values are not cummulative." + "For this problem, we will take depletion step sizes of 30 days, and instruct OpenMC to re-run a transport simulation every 30 days until we have modeled the problem over a six month cycle. The depletion interface expects the time to be given in seconds, so we will have to convert. Note that these values are not cumulative." ] }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 30, "metadata": {}, "outputs": [], "source": [ @@ -480,7 +445,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 31, "metadata": {}, "outputs": [], "source": [ @@ -496,7 +461,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 32, "metadata": {}, "outputs": [], "source": [ @@ -514,7 +479,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 33, "metadata": {}, "outputs": [ { @@ -541,7 +506,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 34, "metadata": {}, "outputs": [], "source": [ @@ -550,7 +515,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 35, "metadata": {}, "outputs": [], "source": [ @@ -559,7 +524,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 36, "metadata": {}, "outputs": [], "source": [ @@ -568,22 +533,22 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 37, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[0.76882937, 0.00982155],\n", - " [0.75724032, 0.00827689],\n", - " [0.75532241, 0.01031746],\n", - " [0.74796854, 0.00919769],\n", - " [0.74066559, 0.01157708],\n", - " [0.7318449 , 0.00971503],\n", - " [0.72072927, 0.00703074]])" + " [0.75724033, 0.00827689],\n", + " [0.75532242, 0.01031746],\n", + " [0.74796855, 0.00919769],\n", + " [0.74066561, 0.01157708],\n", + " [0.73184492, 0.00971504],\n", + " [0.7207293 , 0.00703074]])" ] }, - "execution_count": 36, + "execution_count": 37, "metadata": {}, "output_type": "execute_result" } @@ -601,7 +566,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 38, "metadata": {}, "outputs": [], "source": [ @@ -610,7 +575,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 39, "metadata": {}, "outputs": [ { @@ -646,7 +611,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 40, "metadata": {}, "outputs": [], "source": [ @@ -656,7 +621,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 41, "metadata": {}, "outputs": [ { @@ -678,7 +643,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 42, "metadata": {}, "outputs": [ { @@ -707,7 +672,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 43, "metadata": {}, "outputs": [], "source": [ @@ -716,7 +681,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 44, "metadata": {}, "outputs": [ { @@ -742,9 +707,9 @@ "source": [ "# Helpful tips\n", "\n", - "Depletion is a tricky task to get correct. Too short of time steps and you may never get your results due to running many transport simulations. Too long of time steps and you may get incorrect answers. Take the plot of xenon from above. Xenon is a fission product with a thermal absorption cross section on the order of millions of barns, but has a half life of ~9 hours. Taking smaller time steps at the beginning of your simulation to build up some equilibrium in your fission products is highly recommended.\n", + "Depletion is a tricky task to get correct. Use too short of time steps and you may never get your results due to running many transport simulations. Use long of time steps and you may get incorrect answers. Consider the xenon plot from above. Xenon-135 is a fission product with a thermal absorption cross section on the order of millions of barns, but has a half life of ~9 hours. Taking smaller time steps at the beginning of your simulation to build up some equilibrium in your fission products is highly recommended.\n", "\n", - "When possible, differentiate materials that reappear in multiple places. If we had built an entire core with the single `fuel` material, every pin would be depleted using the same averaged spectrum and reaction rates which is incorrect. The `Operator` can differentiate these materials using the `diff_burnable_mats` argument, but not that the volumes will be copied from the original material.\n", + "When possible, differentiate materials that reappear in multiple places. If we had built an entire core with the single `fuel` material, every pin would be depleted using the same averaged spectrum and reaction rates which is incorrect. The `Operator` can differentiate these materials using the `diff_burnable_mats` argument, but note that the volumes will be copied from the original material.\n", "\n", "Using higher-order integrators, like the `CECMIntegrator`, `EPCRK4Integrator` with a fourth order Runge-Kutta, or the `LEQIIntegrator`, can improve the accuracy of a simulation, or at least allow you to take longer depletion steps between transport simulations with similar accuracy.\n", "\n", @@ -753,7 +718,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 45, "metadata": {}, "outputs": [], "source": [ @@ -763,22 +728,22 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 46, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 45, + "execution_count": 46, "metadata": {}, "output_type": "execute_result" }, { "data": { - "image/png": "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\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAQ0AAAD8CAYAAABtq/EAAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjEsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8QZhcZAAAf3UlEQVR4nO2df+xlZXngP88MX3/QgeqAw1CQCIUq7FhtwaDt7qbLVqRSW5sGsDQhdLPCuqhJ0RbRsqDbXdS1ki3SukOilqRaOrHYgi5OUqrL1mUWtKzLGmHc0lgI1AwDcYooX5mnf9xz4M793nvPec/7+9znk0zm+z33fu95z6/PfZ73fc95RFUxDMPoy6bcDTAMoy5MGoZhOGHSMAzDCZOGYRhOmDQMw3DCpGEYhhPe0hCRV4rI5hCNMQyjfA7z+WMRORO4AzgKeGZq+VnADkCAu1R1z7xlPus2DCMP4ju5S0T+DniFqn6/+X0zsAd4TfOWvwReP7tMVc/yWrFhGFmI0adxArBPG4B14GWzy0TkpAjrNgwjMl7pyQK2Awemfj8AbJuz7Bjgb2f/WEQuAS4B4HkvPP2wbS+L0ERjmmPX9uVuwlweWT86dxNGzzP7H+Hgk4+Ly9/EkMZjwJap37cA++csm3umqupOYCfA2ktP0xdd/ukITVxdrt5+Y+4mePH+R9+auwmj4omPXuj8N8GkISKbgKNV9QEROUJEWnttUdX75yzbG2rdxmJql8Qss9tjEkmP7+jJGcBLgLOBbwPvBc4HrgTe1bztyqn/Z5cZARmbIPowb5tNJHHxHj2JiaUn3aQUxYN7zwnyOSeecnuQz+mDCWQ5T3z0Qtb//htOfRo2I9QwDCdidIQaCYgRYYSKJHzXEzISafeTRRzhMGlURChRpJLDUBa1z0cm0/vOBOKHSaNwQoiidEn0ZXY7hkrEBOKHSaNAfEUxFkl0EUIiJhB3TBoF4SOLVRHFMqb3gY9ATB7LMWkUwFBZxBTFdy74zWifPY9tN18X9PN8BGLyWI7N08iMqzBCiiK1GIYSUiiuAhm7OIbM0zBpZCCXKGqRRBehJOIikLHKw6RRODlkMRZRLCKEQFY5+jBpFIyLMHxkMXZJdOEjkVWMPEwaBZJCFrlEcf1pP+z1vnd8I09/+1CBrJI87N4TwzCiY5FGJGJHGDGji74RRGhiRiRDoo5ViDgsPSmEvsLILYtccnAlpExiyqNGcZg0CqCPMFxlEUoUtUiii1AScRHIWMVh0shIqbIYiygWEUIgrtFHH4HUIg+TRgZipCI+shi7JLrwkcgqRh7VSENEjlTV73a9r3RphI4uhspi1UWxiKECCS2PksWRVBoichhwNfA14FTgg6p6sHni+FeBg81bf1RVTxGRo4CvAJuBz6jqVV3rKFkaJQgjtix2nXtZ1M9vOe/zN0T9/CHyWBVxpJbG2wBV1Y83P+9X1ZtF5KXAU6q6T0S2AB9Q1ctF5N3Abar6zb7rKFEaodOREmSRSg6uhJZJTHnUmq6klsYfAX+oqneJyOuAt6nqRTPvuQB4XFV3i8iHgF8FHgJ+VVUf61pHadLIHV2EkkWpkugilERc5THmqCO1NL4IXK6q/09EdgC/p6pvmHnPfwPerqrrze+bgd8DDlPVty/43GfLMm568bGnb73qC4PaF5pQwjBRhKFkgdQkjtTS+DTwX1V1j4i8FniHqv761OvPA65X1Utn/m4r8ClV/aWudZQSaeQQRghZjE0UiwghEBd5jEkcqe892Q28qvn5J4HdIrJt6vWfB+5ofxGR5zc/bgPu8livYRgZ8Yk0NgEfAL7ORBq3AFeo6vnN6zcAV6rqd0XkROBWJoWdnwY+qao/6FpHCZFGV5RRUkqyKpFFF0MjjxypSu5oo5p5Gn3JLY3UwqhNFsevn9frfQ+t7YrckvmkkEft4jBpBGSZMEIPp5Yoi75CCEVMscSWR4hh2VziMGkEoIboIqQsUsvBlZAyGSKPsUcdJg1PShdGKFmULopFhBKIqzzGLA6ThgephGGiCEPJAqlJHPa4v4GMVRjHr5/37L+xEWrbXPdxn2PY51zoOqdCFfuOgVVY66Dr4Mbo7PSRxRgF0cX0Ng+JQNr93TfquP60H3ZGHO15sSzqeHDvOYOLWOdk5SMNH6OXJIyxRhSu+OwHl33f95j6PBul1Ghjpfs0fNKS0OmIjyxic/b656J87u61N0f53GmG9n249HX49nPk7N+wjlAHahdGLFnEEkRfYolkiDxWQRzWEWoYRnRWMtLwme2ZO8oIHWHkjiy6CB15uEYcKaMNSD9rdEikYaMnU6QSRu7+i9JFMc10W0MI5Pj185zE4TKy0ndUpfYRlZWLNBZFGaUKYxVF0YdQEUiMyCNmxBE62rA+jQ6GDmHVLIyz1z83OmFAuO1y3cd9jmGoCWDzKGEYdmUijVj9GH37L1yE4SuLMUqiD77Rh0vU0bevY1nUUUL/hkUaAxiTMMYaVfTFd/td9n3fY7rsHOmKNobU+k3B6CONmCMloVOSocLIJYqj7lvv9b7HdqxFbsl8hkYeoSOOrj6OnBGHjZ4EpAZhxJZFXyn4fk4sqbT7x1UeLiMsu869rFMcXaMqXSMqpRE9PRGRI2OvYxEhnr41j7EK46j71g/5l4rY6x2yr0KnKj5Pl192ruboGA1elrF5bUMJRhE5C9gBCHCXqu7pWodPejJUGKVHGCFlkVIMPoSMRFyjjpJSlRhpSuqO0LcCD6vqLcDjwPTZ/xvAL6vqyY0wNgMfBq4Hfh+41mO90VgVYaSOJHwJ2V7XfZg64vC5KzYVPtJ4LXBv8/O9wLlTr70EuE1EvtREHScA+7QBWBeRkzzWvZRYaUkXfYXhevt2qFGR2mQxS6j2u+7PGKMqQyglTfGRxnbgQPPzAeCY9gVVvQJ4OROZvH/mvRveP42IXCIi94jIPQeffMKjeYZhxMBHGo8BW5qftwD7pl9U1WeYFFM6Yea9c98/9Xc7VfUMVT1j04+8yKN5G4ndl9EHizD8yRFxhHzIkU+KUsLcjShlGWdLMKrqA8AR0gBsUdW9HuteyJAwLVVfhqswfBmbLGYJKY++9DmGufo2UqUoPtK4CThBRM5nEk3cB3ysKcH4VRF5J/BzTKrEA1wJvKv5d6XHehcSoy+j6+DuOveyooSRY8g0NyG2OYY4us6LodFr7r6NwdJQ1YOq+juq+qfN/19V1fNV9UFV3aGqv6+qH29rtqrqnar6kebfneE2wQ+f3mqXjs+++Apj1SlJHODXMVrqSMpoZoQOueU9RT9GCmHklMXDF+1f+vpxN21N1JLnaPfHkPkdZ69/rvdcDtdnc8xj6GzR9ryeN3fj6u03Rn2u6GikkZpSUpIUwugSw9C/jS2Uo+5bL0Icfaaa18QoblhLHWWUIIyYsvCRhA8xJTJEHi6zR/tEHMvEEWOmaJ9ow25YC0CoodW+lCCMXJKYZbYdISUyJOpwiTh8ac87l7qxuRjt8zR8SxAsImSUkVsYD1+0vxhhzCN0+4bsO5d5HF3E6hRNPXejemmEHGLqM7zaRcziRaGEUbosZgnZ3phpXQhxhIx0Yw2/Vi+NecSKMkLhOtMz1LyL2mQxS6j2u+7PUp6GVkq0MUppGIYRj6qlUWNq4vqtFTIlGQs5UpW+0eEqpChVS2MeuVKTUoVRe0qyiJCpiguhxDGUElKU0UljCCGijNCEEsbYySGOEKSMNkJTrTRcw65YUUbfW6ZdOz59GGt0sYgQ2xu6YzTkrfSzuJ7LoVOUaqUxjyHhWYooI1Xv+6rJYpaU2x/imMaINlKkKNVJ4+rtNxZRmg7i5K5Do4xVlsUsQ/dFjDQlZv+GCyGvm/LnrHriUyEtdZRRqzDeeM2tc5d/4Zo3JW7Jczx80f5B09BdppuHmGbedTPbortg2/M6R72U0Ugj9VTaUr5BUgpjkRxc359KJkPFEZoQt9C78ODec5aWO/ClqvQkZVpSQ5SRQhhvvObWZ/+V/JmLGLKPUs8WTTk6F+IaGk2kMY8QxZvnETrKKO2JWyku5nnrypnOzDL0WRyL8Ik2lj2oJ0dJxyyRRuhSjSU8oXmW2CMmMaKMVN/+qdcfOyIr5d6UaWJeE4OlISKHich/FJFfEZH3isimqdfeIiJ/LSLfEpGfaZYdJSL3i8i3gN8K0HbDMDLgk548W5ZRRLYzKct4s4i8EHhGVX9WRC4ErgJ+gedKNX7Tu9UR6covS0hNQn9z5owu5tG2J2S6MqRTNGWK0p53NTwWMEZZxnXgs83Pf8OkUBJsLNXoRMgZoLGn6MYMV0MKI3c60kXo9sVMU2KnKMvO2dQzRIOXZVTVH7bV44F/yaTw87xSjXNxLctYYn9GX3J2gJYsi1lytrW0TmoXYl0b0coyNgWev62qX2+XzZRqnEvMsoxdhEhNaogyahJGS6g25442us6hHDdHuhKrLOM24BWq+t9F5AUism22VKPHejvJmZr0wfXJUSHvqahRGC0hxeGyP0uJNkKmKD7EKMt4OPDnwIdF5D7gbuBHmV+qsRel3GtSOzULo2UM21ACPtdUjLKM31PV1zWlGXeo6itVde+8Uo2+hMzZUqUmrt9aFmFsJEeq0jc6LC1FidGvUdU0cmMYYxJGyxi3qRZGJ42S+zNyRBljvrhCbJvrPs7dv1FCv8bopBGD3KMmRl3kfo5obIqXRopO0BKHuSzK6EeOaCMFKc7JoddW8dIwDKMsqr01vraZoClz4VWIMKaJca/KMkLfkxKb0A/lqVYapWB9GcY82vMiVdX5lKxMepJ75MQFnxx71aKMaXy2vcR+jUXkPpdHJY0YQ04193IbZRPj3Eox7DoqaQwhRS917rF9IzwpjmmJo3pg0hgVq5yatNg+iE/R0jh2bV/3mzISoxO0ptx6bMTY96V3lA+5xoqWhmEY5VGdNB7ce051czRSYGH5c9i+2EjI66Y6aRiGkReThmEYTpg0DMNwYvTSuP60Hw6eQRdi8o3N0RgvIY7t0HPM57z2ZfTSqAkbbs2PHYNuTBojwEYLNmL7JB6D73IVkcOAq4GvAacCH2yLJInIWcAOQIC7VHXPvGW+jTcMIz0xarluZlJV7TXN+/5SRF4/uww4y2PdhmFkIkYt1xOAfdrApLbry2aXNRXYNjBdlvEfDzzl0TzDMGIQvJbrzPL2tW1zlh3DHKbLMm454oUezTMMIwYxarlOL29f2z9nWdl3oxmGMZfgtVxV9QHgCGkAtqjq/XOW7fVrutGS6tmYNWH7JB7Ba7k2r10JvKv5d+WSZcYUx920NXcTVh47Bt0MHj1phld/p/n1T5v/z29euxO4c+b9G5al4B3fmGzikNlzD63t8p4V+tiONZsVOlJCPJH8obVdg/6uPa9zYJO7DMNwwqRhGIYTJg3DMJyoThonnnJ70GpRY8FGC57D9sVGQl431UnDMIy8FC2NR9aPzt2EpcQouWdDfvmIse9LL8s45BorWhqGGxaW2z5IwcpL47zP3xB9HTVVGDf6keKYpjg3hzAqaWy7+brgnzl08o1hdBHj3IpxDcwyKmksI+cMOld8cutVDs99tr2mvqTc53I9V1KhtB1dpZffM9JSegeoD9VK48RTbq+q0lrKe1Dab9xVeU5m6uiqtj6q0POaViY9MQwjDMVL4/2PvjX6OkrspQ6RY69C/0aIbSyxPyPFOTn02ipeGiXQp5d7zDms4Uafc6HmUbnRSWPZkFPuXmfXXNiijeXkiDJy92csO4dTDLfCCKVhbGSM4hjjNtVC1dII2SvclUOGSlFyRBswross1La47NvHdqz1OnYhUpOQ/Rkx7gjPJg0RObLve1N0hq4CYxDHGLahBHyuqcHSEJEjReQ/iciviMhvznn9nU3Ro/8rIj/RLDtNRP5WRL4FXDi41R2U3K8B/b+1Wo67aatFHISNMFyjjBIooT8D/CKN9wF3quotwHYRObN9QUROAP6Pqp4BfAZopXIh8LOqerKqftxj3VFIlaIMZZXFkSMlcaW01CQWMcoyAjyqql9ufv4bJgWUAI4H7haRz4rICzzW/Sw1P8Ur5zdYTeLI2dZSoowhxLo2YpRlRFWfnnrf6cANzfKLgVOAg8Db533odC3Xg08+8exy1xwsZ4pSQ7QBk4uxZHmEbl/uKMOHkKmJbx9h59UjIucA75nz0pFMyis+yYIyiyLyU8AdqvpIu0xVnxKRa4GL561PVXcCOwHWXnqadm9CWNrwcNe5l819PUQtlGmG3JNy3E1befii/cHaUNq9KjFENkQYoaOMZalJDWlJS6c0VPV2YEOcIyLXMCnLuJtJWcYvisjzmJRc3C8iP978fKeIvAT4HrDeRCHbgLvCbYZhGKnwSU8+DPxrEbkAOND0YbwBeJ+IHAP8BfCHInIf8CdMBPO/ReTfAscx6SANQon9GrHD1Rihdu50Jdb6Y99bUuItBDGvCZ+yjN8DrphZdivQxrj/bM6fvXro+oaw7ebr+M4FG0aDgUmOOKRUI5SRosRk+sKNnbKU2qeSMjXpopSh1paqZoSmnOQVIsd0+QYacpKmuDuz/fYPeXHH+MxFxO7LCBFlpOzPCHEN5Z/pFIjUD+UJHW0MJXSn6DIWXeSLopHcUUQpt7ynvqM1dro+Gmksog3f5qUpXSnKeZ+/YeEoSl92r72596MAh6YpKcUxj9xymMdQYZQWZSxKTXKkJS1VpScwCa9KuRclxjfI0Fy6lG/VEkghjL6U8tyMkNdNddJYxpCwrGuiV+q+DR9C3qNSIym3P2eUsYwUI4nVSiPkDFEfHlrbFfyeFN9vvFWTR4jtDZ2W9D0vhpB6Bugs1UojJCmiDVdChMqrII4Q25jj/pIYUUYqRieNZeFZzM6jGHfAhhLHGOURartc93Hu538uO4dTTXKsWhohw64Q0Uap4oBxRR2htsU1JQkljJRRRoxBg6qlYRhGekYpjVwpSl/6fmu1uD7paxG1pyohU5LU8zFCUEJqAiOQRo0pylBCpio1ySNke2N2eq5CagIjkMYiYkUbIcUx5Bss5ElfujxCt2/Ivut7jEIIYxmlRBmwAtPIXWlNP/QOWFdcppm3hL4rdvbCzDUlPabAYgojBCUPsc4iqskfjtWbtZeepi+6/NO93nv19hvnLl92E9ui2+Zblomjzz0pLje0uYqjJcUt9bEkkiLKGRqZuQjDN8roEsaQKKNvavLERy9k/e+/Ib3e3FCP3gqjz81sLnfCDok4IM2zOJZd3F1CyZn+1CCMGhlNpAGLow1YHHHEjjYgTcQBaaKOGvDp9wktDIgTZSzrx3DpAB0SaYy2I7QvsTtFwW1ExSePrvlx+6GoSRhdlDA9YB5JpOFSgtGHZYYd2sPcZxg29FCsrzhCzeuohRDbHCMliTW8GirKGErMsowbSjCKyFtE5N+IyHuap5UHZ8hO6zJ6n4Nbkjhaxi6PUNuXow/Dp/NzEameMxOlLGPDISUYRWQrcKmqfoJJ8aTksZfP3I1QQ2Ku4jB5bCSkLFzmYYSauBdjtCQlscoywsYSjK8C7gdQ1QPAySIS5UyOkab0waWPw+ThTg5ZgJvoY46U5E5LWqKUZYS5JRin3w+T4klHz37oorKMhmGUQac0ROQcEfnS7D+eK8sIC8oyqupTwLXACUyKQG+ZevlwYMMgv6ruVNUzVPWMTT/yIucN8iFV30aLa7gbaoZibRFHyPa67sPQUUaMvozUDJ6n0ZRl/Iqq7haR/wx8EfhfTMTwOLCmqk83tWC3An8OfE5VXy8iW4A/U9Wzl63DdZ7GLEPmbbQsm7/RZ4q5y1PMh5RC8JnPsYhS5nnEENoQ4aYURpcsYqUmqedpLCzLCLyOmRKMqvoksFNELgHeDbzTY929iNW3kTvigDj3RUwPXaaMRGKvt3RhdFFKX0bLqGaEziNWtAFlRBwQJ+roQ9/IJFcqNFSspaUkMaUxJNIYvTRahtzQBv5pCqQRB+STR2n4RGExRkpipCWhIgybRj6ArjRl2UHtG3LGTlVaQg3N1orv9tcijNysTKQBw1OVrjQFwqcq4Bd1tIw9+gghSVdRh+rDGDqJK2Q/hkUaHQzd2X2GwUJ3jkKYRweONfoItV0lCmMZJZQkXalIoyVG/0ZLjIijJVSV+lqjj1DyGyLjEOlIS+5+jGnsITyenHjK7UvFse3m6zrF0VWJHg49AV0E4vJQn2VMX3ylCyR0lBQjsmiJKYySWMlIA5b3b0CaPo6WIZFHqKhjltwSiZVKxYwuIG4fBsRLS6xPwzCM6KxspAH1RxsQL+KYJlb0kaKDdmhn8ipEGWCTuwYRWxwwHnnURCmygHKFAZaeDMLnoPQdNnO572Do8xhCPiSmZnz2Q2phdFHC8Oo8bPSkgz4jKtAddfQZVWlpT94hUcf0BbMq0YevLF1FHWoeRg0jJfNY+UgDuo3e5+CGmgA2je9ToNpv3TFGIKG2rVRhlBplgPVpHIJP/0ZL334OcC/9OLS/Y5ZaI5BQ8oshipbahGEdoQEoXRwQTh5QvkBCRklDIrcxCwNMGkHxeQ5HS4zRlWlCymOW1DKJmUINTfNCd3aW9jAdMGkEp4aoA+LKYxl9xZKrTyW2LKDO6GIak0YEUosD6pNHaaSQBdQvDKhMGiJypKp+d9l7SpAGdIsDyok6plkViYSoNZI6uoD8woDEk7uWlWUUkSNE5Ftt/RIR+R/N8g2lGg3DqAufEgYfAv5KVW9vfv4zVd3TvHYq8KCqfl9ETgHerKr/RUR+F7hBVR/ps45SIg3IE21AmIgDxhd1hKpkliMlgTKiDEicnojIl4ELVPVREfk14FRV/Q9z3vfbTOqdPCAinwJ+HtgD/Lqqfn/ZOkqSBoQTR4sJxI2SRdFSkzAgvTTuB35aVZ8UkV8E3qSql8553ydV9Temfn8hcBOwR1U/Muf9lwCXAGx68bGnb73qC4PaF4s+4oDww7LThJJHS6kSCV0XdUjtkRDDqdOUJAyIJI2mQtp75rz0cuDVqvoPIvIW4JWq+r6Zv/0x4LI5y38auFhVlxZMKi3SmCZ31AHh5TFLKpnELJoMcWUB9UUX06SONK5hQVlGVd3fvOffA19V1T0iIsyUalTVpUYoWRpQhjggvjxqZWhVs1URBqSXxuHA1cDXgJNU9VoReRPwc6r6ruY9f8QkolAR+RngD4CPAQp8QjtWXro0IHy6AsPlASYQn/KHoWUBZQsDKpun0YcapNESOuoAP3m0jF0iPpJocX3mRe3RxTQmjQIoVR4wHoGEEAWsZmQxi0mjEGKkLC2hBAL1SCSUJGDYk7TGKgwwaRRFX3FAfnnMkksmIeUwS0xZQJ3CAJNGkcSWB8QVyDL6yiWmDJYx9PmcqyCLFnuwsGEY0bFIIxEpIg7IF3WUgs/Tv1cpwmix9KRwXMQBfvJoGbtEfCTR4vpU8LEIA0wa1ZBDHjAegYQQBaxmZDGLSaNCcgkE6pFIKEnAakcV8zBpVIqrOFpCCmSW1EIJKYZZhhYlGrswwKRRPUPlAXEFUiM+1ctWQRYtQ6RhZRkLoj1Zh8hj+iJZVYH4ljlcJVn4YNIokOmT11cgMF6JhKiFaqJwx6RROL4CgfFIJFTBZBOFHyaNigghEFh88ZUikxjV1E0U4TBpVIpP/8ciui7WUFKJIYVFmCzCY/eeGIbhhEUalRMqZelDygjBB4su4uItDRF5tareG6Ixhh/zLpbYIsmNCSI9XtJo6p3cCBw757W3AIcD24Bdqvr/5y3zWb/RzexFVbtETBL58ZKGqt7WlCY4BBHZClyqqv9KRI4A/lhELp5dBvySz/oNd2qKRkwQZRKrT+NVwP0AqnpARE4GTp9dJiJrqroeqQ1GT7ouzlhSMSnUSSxpbAcOTP3+PeDoBcsOKQY9XZYR+MG+y3/qvkhtzMnRwL7cjejLO/q/1XG7/sC5LRmp6pg58HLXP+iUxpKyjJeq6v0L/uwxYMvU74cD+xcsOwRV3QnsbNZ9j6qe0dXG2rDtqo+xbpuI3OP6N53SUNXbgV5jbSLyPCZi+Gvgt5plW4CHgP8JvHt6mar+wLXBhmHkxXf05I3Ai0XkNap6N/AGmrKMIrKzSTV+DHhnU13+kGXerTcMIzm+oydfAJ4/9futwK3Nz7vmvH/Dsg52+rSvYGy76mOs2+a8XUU/hMcwjPKwe08Mw3CiWGmIyKtztyEXInJk7jYYy6n1GInIK0Vks89nFHnDmuv09MTNG0Rzkl0B3AO8TFWvm3n9NOA24CDwEeDjyRvZExE5DLga+BpwKvBBVT3YvHYWsAMQ4C5V3ZOtoQPo2LajgK8Am4HPAFflaucQRORM4A7gKOCZqeVux0xVi/wHPDpn2Vbgr5qfjwD+Inc7HbbnQ8A5Uz+fOfP67wLH5m5nz215G/Dvpn6+oPl5MxMpSvPvjtxtDbVtze/vBl6Ru42e2/d3wAumfnc+ZsWmJws4ZHo6cLKIrOVtUm9eC7R3A98LnDvz+vHA3SLyWRF5QdKWubNoW04A9mkDsC4iJ+VooAfLjtNLgNtE5EtN1DEGnI9ZbdJYND29BqbbfgA4ZvpFVb0YOIVJevL2pC1zZ9G2zB6fDdtZAQuPk6pewWTa9b3A+9M3LQrOxyxbn0bg6enFsGS7jmTS9ieb/zfcx6CqT4nItcDFMdsYgOnjML0ts8dn7nYWzqJtA0BVnxGRDwCfStyuWDgfs2zS0EDT07WwqeiLtktErmGSXu0GfhL44tR2PQ6sqerTTDp470rW4GHsZrIte5hsy24R2aaqD4jIEVOPS9iiqnuztXIYi7btOyLy/OZ8q+EYLUVENgFHDzlmRU7uaqan3wL8c1W9W0TexHPT088DXsxkKvqfqOo3c7a1LyJyOM/1yp+kqte22wV8lsktnx8DFPiElnhgGpoT7gPA15lcWLcAV6jq+SLyL4Azm7fuUdU7MzVzEIu2rfl3K5MZlE8DnyztC6sLETkD+DLwa8C3gfcOOWZFSsMwjHKprSPUMIzMmDQMw3DCpGEYhhMmDcMwnDBpGIbhhEnDMAwnTBqGYThh0jAMw4l/AucVBtn6j9yMAAAAAElFTkSuQmCC\n", "text/plain": [ "
" ] @@ -809,7 +774,7 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 47, "metadata": {}, "outputs": [], "source": [ @@ -818,7 +783,7 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 48, "metadata": {}, "outputs": [], "source": [ @@ -827,7 +792,7 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 49, "metadata": {}, "outputs": [], "source": [ @@ -836,7 +801,7 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 50, "metadata": {}, "outputs": [ { @@ -863,33 +828,13 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 51, "metadata": {}, "outputs": [], "source": [ "new_op = openmc.deplete.Operator(geometry, settings)" ] }, - { - "cell_type": "code", - "execution_count": 51, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "3820" - ] - }, - "execution_count": 51, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "len(new_op.chain.nuclide_dict)" - ] - }, { "cell_type": "code", "execution_count": 52, @@ -898,7 +843,7 @@ { "data": { "text/plain": [ - "['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'H7', 'He3', 'He4', 'He5']" + "3820" ] }, "execution_count": 52, @@ -906,13 +851,33 @@ "output_type": "execute_result" } ], + "source": [ + "len(new_op.chain.nuclide_dict)" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'H7', 'He3', 'He4', 'He5']" + ] + }, + "execution_count": 53, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "[nuc.name for nuc in new_op.chain.nuclides[:10]]" ] }, { "cell_type": "code", - "execution_count": 53, + "execution_count": 54, "metadata": {}, "outputs": [ { @@ -930,7 +895,7 @@ " 'Rg272']" ] }, - "execution_count": 53, + "execution_count": 54, "metadata": {}, "output_type": "execute_result" } @@ -952,16 +917,16 @@ }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 55, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "5.080334904405777" + "5.080339195584719" ] }, - "execution_count": 56, + "execution_count": 55, "metadata": {}, "output_type": "execute_result" } @@ -972,7 +937,7 @@ }, { "cell_type": "code", - "execution_count": 57, + "execution_count": 56, "metadata": {}, "outputs": [], "source": [ @@ -981,7 +946,7 @@ }, { "cell_type": "code", - "execution_count": 59, + "execution_count": 57, "metadata": {}, "outputs": [ { From 7bb16adc567d1ee021e984d93c75b27ed5dd818b Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 6 Sep 2019 08:46:43 -0500 Subject: [PATCH 011/158] Document units for operator.heavy_metal Attribute contains the initial heavy metal inventory in grams --- openmc/deplete/operator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index 01bf92dcc9..528681db75 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -128,7 +128,7 @@ class Operator(TransportOperator): burnable_mats : list of str All burnable material IDs heavy_metal : float - Initial heavy metal inventory + Initial heavy metal inventory [g] local_mats : list of str All burnable material IDs being managed by a single process prev_res : ResultsList or None From 3aedd6a337c4e76b6634a16de85b260b54264d7b Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 6 Sep 2019 14:08:24 -0500 Subject: [PATCH 012/158] Better conditional for call to MPI_Type_free (thanks Cliff Dugal) --- src/finalize.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/finalize.cpp b/src/finalize.cpp index 7c39a7c198..3de20d6d09 100644 --- a/src/finalize.cpp +++ b/src/finalize.cpp @@ -127,9 +127,7 @@ int openmc_finalize() // Free all MPI types #ifdef OPENMC_MPI - int init_called; - MPI_Initialized(&init_called); - if (init_called) MPI_Type_free(&mpi::bank); + if (mpi::bank != MPI_DATATYPE_NULL) MPI_Type_free(&mpi::bank); #endif return 0; From d5723793994a2d9bc19083dd324e17af8705baa5 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 6 Sep 2019 16:26:44 -0500 Subject: [PATCH 013/158] Fix reading of Debye-Waller for incoherent elastic --- include/openmc/thermal.h | 5 ----- src/secondary_thermal.cpp | 2 +- src/thermal.cpp | 14 ++------------ 3 files changed, 3 insertions(+), 18 deletions(-) diff --git a/include/openmc/thermal.h b/include/openmc/thermal.h index a084c18884..cb6a7ce7bd 100644 --- a/include/openmc/thermal.h +++ b/include/openmc/thermal.h @@ -76,11 +76,6 @@ private: std::unique_ptr distribution; //!< Secondary angle-energy distribution }; - //! Upper threshold for incoherent inelastic scattering (usually ~4 eV) - double threshold_inelastic_; - //! Upper threshold for coherent/incoherent elastic scattering - double threshold_elastic_ {0.0}; - // Inelastic scattering data Reaction elastic_; Reaction inelastic_; diff --git a/src/secondary_thermal.cpp b/src/secondary_thermal.cpp index 696907a17e..8c9cc69676 100644 --- a/src/secondary_thermal.cpp +++ b/src/secondary_thermal.cpp @@ -61,7 +61,7 @@ CoherentElasticAE::sample(double E_in, double& E_out, double& mu) const IncoherentElasticAE::IncoherentElasticAE(hid_t group) { - read_attribute(group, "debye_waller", debye_waller_); + read_dataset(group, "debye_waller", debye_waller_); } void diff --git a/src/thermal.cpp b/src/thermal.cpp index 5cc395e13c..49b5223763 100644 --- a/src/thermal.cpp +++ b/src/thermal.cpp @@ -210,22 +210,15 @@ ThermalData::ThermalData(hid_t group) if (temp == "coherent_elastic") { auto xs = dynamic_cast(elastic_.xs.get()); elastic_.distribution = std::make_unique(*xs); - - // Set threshold energy - threshold_elastic_ = xs->bragg_edges().back(); - } else { - auto xs = dynamic_cast(elastic_.xs.get()); if (temp == "incoherent_elastic") { elastic_.distribution = std::make_unique(dgroup); } else if (temp == "incoherent_elastic_discrete") { + auto xs = dynamic_cast(elastic_.xs.get()); elastic_.distribution = std::make_unique( dgroup, xs->x() ); } - - // Set threshold energy - threshold_elastic_ = xs->x().back(); } close_group(elastic_group); @@ -239,10 +232,6 @@ ThermalData::ThermalData(hid_t group) // Read inelastic cross section inelastic_.xs = read_function(inelastic_group, "xs"); - // Set inelastic threshold - auto xs = dynamic_cast(inelastic_.xs.get()); - threshold_inelastic_ = xs->x().back(); - // Read angle-energy distribution hid_t dgroup = open_group(inelastic_group, "distribution"); std::string temp; @@ -250,6 +239,7 @@ ThermalData::ThermalData(hid_t group) if (temp == "incoherent_inelastic") { inelastic_.distribution = std::make_unique(dgroup); } else if (temp == "incoherent_inelastic_discrete") { + auto xs = dynamic_cast(inelastic_.xs.get()); inelastic_.distribution = std::make_unique( dgroup, xs->x() ); From 3040d4d3bb9ed0581c6cb8be9535a4ac26a1accf Mon Sep 17 00:00:00 2001 From: rockfool Date: Mon, 9 Sep 2019 15:08:38 -0400 Subject: [PATCH 014/158] add example for white boundary --- examples/python/white/build-hemisphere.py | 73 ++++++++++++++++++++ examples/python/white/build-pin.py | 75 +++++++++++++++++++++ examples/python/white/build-plane.py | 81 +++++++++++++++++++++++ 3 files changed, 229 insertions(+) create mode 100644 examples/python/white/build-hemisphere.py create mode 100644 examples/python/white/build-pin.py create mode 100644 examples/python/white/build-plane.py diff --git a/examples/python/white/build-hemisphere.py b/examples/python/white/build-hemisphere.py new file mode 100644 index 0000000000..e62182708d --- /dev/null +++ b/examples/python/white/build-hemisphere.py @@ -0,0 +1,73 @@ +import numpy as np +import openmc + +############################################################################### +# Simulation Input File Parameters +############################################################################### + +# OpenMC simulation parameters +batches = 500 +inactive = 100 +particles = 10000 + + +############################################################################### +# Exporting to OpenMC materials.xml file +############################################################################### + +# Instantiate a Material and register the Nuclide +fuel = openmc.Material(material_id=1, name='fuel') +fuel.set_density('g/cc', 4.5) +fuel.add_nuclide('U235', 1.) + +# Instantiate a Materials collection and export to XML +materials_file = openmc.Materials([fuel]) +materials_file.export_to_xml() + + +############################################################################### +# Exporting to OpenMC geometry.xml file +############################################################################### + +# Instantiate Surfaces +surf1 = openmc.ZPlane(surface_id=1, z0=0.0, name='surf 1') +surf2 = openmc.Sphere(surface_id=2, x0=0.0, y0=0.0, z0=0.0, r=0.39218, name='surf 2') + +surf1.boundary_type = 'vacuum' +surf2.boundary_type = 'white' + +# Instantiate Cell +cell = openmc.Cell(cell_id=1, name='cell 1') + +# Use surface half-spaces to define region +cell.region = -surf1 & -surf2 + +# Register Material with Cell +cell.fill = fuel + +# Instantiate Universes +root = openmc.Universe(universe_id=0, name='root universe') + +# Register Cell with Universe +root.add_cell(cell) + +# Instantiate a Geometry, register the root Universe, and export to XML +geometry = openmc.Geometry(root) +geometry.export_to_xml() + + +############################################################################### +# Exporting to OpenMC settings.xml file +############################################################################### + +# Instantiate a Settings object, set all runtime parameters, and export to XML +settings_file = openmc.Settings() +settings_file.batches = batches +settings_file.inactive = inactive +settings_file.particles = particles + +# Create an initial uniform spatial source distribution over fissionable zones +uniform_dist = openmc.stats.Point(xyz=(0, 0, -0.1)) +settings_file.source = openmc.source.Source(space=uniform_dist) + +settings_file.export_to_xml() diff --git a/examples/python/white/build-pin.py b/examples/python/white/build-pin.py new file mode 100644 index 0000000000..bd02f2c67d --- /dev/null +++ b/examples/python/white/build-pin.py @@ -0,0 +1,75 @@ +import numpy as np +import openmc + +############################################################################### +# Simulation Input File Parameters +############################################################################### + +# OpenMC simulation parameters +batches = 500 +inactive = 100 +particles = 10000 + + +############################################################################### +# Exporting to OpenMC materials.xml file +############################################################################### + +# Instantiate a Material and register the Nuclide +fuel = openmc.Material(material_id=1, name='fuel') +fuel.set_density('g/cc', 4.5) +fuel.add_nuclide('U235', 1.) + +# Instantiate a Materials collection and export to XML +materials_file = openmc.Materials([fuel]) +materials_file.export_to_xml() + + +############################################################################### +# Exporting to OpenMC geometry.xml file +############################################################################### + +# Instantiate Surfaces +surf1 = openmc.ZCylinder(surface_id=1, x0=0, y0=0, r=0.39218, name='surf 1') +surf2 = openmc.ZPlane(surface_id=2, z0=-1, name='surf 2') +surf3 = openmc.ZPlane(surface_id=3, z0=+1, name='surf 3') + +surf1.boundary_type = 'white' +surf2.boundary_type = 'vacuum' +surf3.boundary_type = 'vacuum' + +# Instantiate Cell +cell = openmc.Cell(cell_id=1, name='cell 1') + +# Use surface half-spaces to define region +cell.region = -surf1 & +surf2 & -surf3 + +# Register Material with Cell +cell.fill = fuel + +# Instantiate Universes +root = openmc.Universe(universe_id=0, name='root universe') + +# Register Cell with Universe +root.add_cell(cell) + +# Instantiate a Geometry, register the root Universe, and export to XML +geometry = openmc.Geometry(root) +geometry.export_to_xml() + + +############################################################################### +# Exporting to OpenMC settings.xml file +############################################################################### + +# Instantiate a Settings object, set all runtime parameters, and export to XML +settings_file = openmc.Settings() +settings_file.batches = batches +settings_file.inactive = inactive +settings_file.particles = particles + +# Create an initial uniform spatial source distribution over fissionable zones +uniform_dist = openmc.stats.Point(xyz=(0, 0, 0)) +settings_file.source = openmc.source.Source(space=uniform_dist) + +settings_file.export_to_xml() diff --git a/examples/python/white/build-plane.py b/examples/python/white/build-plane.py new file mode 100644 index 0000000000..184cbf346b --- /dev/null +++ b/examples/python/white/build-plane.py @@ -0,0 +1,81 @@ +import numpy as np +import openmc + +############################################################################### +# Simulation Input File Parameters +############################################################################### + +# OpenMC simulation parameters +batches = 500 +inactive = 100 +particles = 10000 + + +############################################################################### +# Exporting to OpenMC materials.xml file +############################################################################### + +# Instantiate a Material and register the Nuclide +fuel = openmc.Material(material_id=1, name='fuel') +fuel.set_density('g/cc', 4.5) +fuel.add_nuclide('U235', 1.) + +# Instantiate a Materials collection and export to XML +materials_file = openmc.Materials([fuel]) +materials_file.export_to_xml() + + +############################################################################### +# Exporting to OpenMC geometry.xml file +############################################################################### + +# Instantiate Surfaces +surf1 = openmc.XPlane(surface_id=1, x0=-1, name='surf 1') +surf2 = openmc.XPlane(surface_id=2, x0=+1, name='surf 2') +surf3 = openmc.YPlane(surface_id=3, y0=-1, name='surf 3') +surf4 = openmc.YPlane(surface_id=4, y0=+1, name='surf 4') +surf5 = openmc.ZPlane(surface_id=5, z0=-1, name='surf 5') +surf6 = openmc.ZPlane(surface_id=6, z0=+1, name='surf 6') + +surf1.boundary_type = 'vacuum' +surf2.boundary_type = 'vacuum' +surf3.boundary_type = 'white' +surf4.boundary_type = 'white' +surf5.boundary_type = 'white' +surf6.boundary_type = 'white' + +# Instantiate Cell +cell = openmc.Cell(cell_id=1, name='cell 1') + +# Use surface half-spaces to define region +cell.region = +surf1 & -surf2 & +surf3 & -surf4 & +surf5 & -surf6 + +# Register Material with Cell +cell.fill = fuel + +# Instantiate Universes +root = openmc.Universe(universe_id=0, name='root universe') + +# Register Cell with Universe +root.add_cell(cell) + +# Instantiate a Geometry, register the root Universe, and export to XML +geometry = openmc.Geometry(root) +geometry.export_to_xml() + + +############################################################################### +# Exporting to OpenMC settings.xml file +############################################################################### + +# Instantiate a Settings object, set all runtime parameters, and export to XML +settings_file = openmc.Settings() +settings_file.batches = batches +settings_file.inactive = inactive +settings_file.particles = particles + +# Create an initial uniform spatial source distribution over fissionable zones +uniform_dist = openmc.stats.Point(xyz=(0, 0, 0)) +settings_file.source = openmc.source.Source(space=uniform_dist) + +settings_file.export_to_xml() From 5f394b281614fda5a9aa5a3695db4513c53e5cb7 Mon Sep 17 00:00:00 2001 From: rockfool Date: Mon, 9 Sep 2019 16:22:25 -0400 Subject: [PATCH 015/158] add white boundary condition --- include/openmc/surface.h | 3 ++ openmc/surface.py | 56 ++++++++++++++++++------------------ src/particle.cpp | 50 ++++++++++++++++++++++++++++++++ src/surface.cpp | 61 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 141 insertions(+), 29 deletions(-) diff --git a/include/openmc/surface.h b/include/openmc/surface.h index d61ae07cef..5272dab3fb 100644 --- a/include/openmc/surface.h +++ b/include/openmc/surface.h @@ -25,6 +25,7 @@ extern "C" const int BC_TRANSMIT; extern "C" const int BC_VACUUM; extern "C" const int BC_REFLECT; extern "C" const int BC_PERIODIC; +extern "C" const int BC_WHITE; //============================================================================== // Global variables @@ -115,6 +116,8 @@ public: //! \return Outgoing direction of the ray virtual Direction reflect(Position r, Direction u) const; + virtual Direction diffuse_reflect(Position r, Direction u) const; + //! Evaluate the equation describing the surface. //! //! Surfaces can be described by some function f(x, y, z) = 0. This member diff --git a/openmc/surface.py b/openmc/surface.py index dc251485d2..ec5b833048 100644 --- a/openmc/surface.py +++ b/openmc/surface.py @@ -12,7 +12,7 @@ from openmc.region import Region, Intersection, Union from openmc.mixin import IDManagerMixin -_BOUNDARY_TYPES = ['transmission', 'vacuum', 'reflective', 'periodic'] +_BOUNDARY_TYPES = ['transmission', 'vacuum', 'reflective', 'periodic', 'white'] _WARNING_UPPER = """\ "{}(...) accepts an argument named '{}', not '{}'. Future versions of OpenMC \ @@ -32,7 +32,7 @@ class Surface(IDManagerMixin, metaclass=ABCMeta): surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'}, optional + boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. Note that periodic boundary conditions @@ -535,7 +535,7 @@ class XPlane(Plane): surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'}, optional + boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. Only axis-aligned periodicity is @@ -549,7 +549,7 @@ class XPlane(Plane): ---------- x0 : float Location of the plane - boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'} + boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'} Boundary condition that defines the behavior for particles hitting the surface. periodic_surface : openmc.Surface @@ -660,7 +660,7 @@ class YPlane(Plane): surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'}, optional + boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. Only axis-aligned periodicity is @@ -674,7 +674,7 @@ class YPlane(Plane): ---------- y0 : float Location of the plane - boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'} + boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'} Boundary condition that defines the behavior for particles hitting the surface. periodic_surface : openmc.Surface @@ -786,7 +786,7 @@ class ZPlane(Plane): surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'}, optional + boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. Only axis-aligned periodicity is @@ -800,7 +800,7 @@ class ZPlane(Plane): ---------- z0 : float Location of the plane - boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'} + boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'} Boundary condition that defines the behavior for particles hitting the surface. periodic_surface : openmc.Surface @@ -912,7 +912,7 @@ class Cylinder(Surface): surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. @@ -926,7 +926,7 @@ class Cylinder(Surface): ---------- r : float Radius of the cylinder - boundary_type : {'transmission, 'vacuum', 'reflective'} + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. coefficients : dict @@ -963,7 +963,7 @@ class XCylinder(Cylinder): surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. @@ -983,7 +983,7 @@ class XCylinder(Cylinder): y-coordinate of the center of the cylinder z0 : float z-coordinate of the center of the cylinder - boundary_type : {'transmission, 'vacuum', 'reflective'} + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. coefficients : dict @@ -1110,7 +1110,7 @@ class YCylinder(Cylinder): surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. @@ -1130,7 +1130,7 @@ class YCylinder(Cylinder): x-coordinate of the center of the cylinder z0 : float z-coordinate of the center of the cylinder - boundary_type : {'transmission, 'vacuum', 'reflective'} + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. coefficients : dict @@ -1257,7 +1257,7 @@ class ZCylinder(Cylinder): surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. @@ -1277,7 +1277,7 @@ class ZCylinder(Cylinder): x-coordinate of the center of the cylinder y0 : float y-coordinate of the center of the cylinder - boundary_type : {'transmission, 'vacuum', 'reflective'} + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. coefficients : dict @@ -1403,7 +1403,7 @@ class Sphere(Surface): surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. @@ -1428,7 +1428,7 @@ class Sphere(Surface): z-coordinate of the center of the sphere r : float Radius of the sphere - boundary_type : {'transmission, 'vacuum', 'reflective'} + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. coefficients : dict @@ -1577,7 +1577,7 @@ class Cone(Surface): surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. @@ -1602,7 +1602,7 @@ class Cone(Surface): z-coordinate of the apex r2 : float Parameter related to the aperature - boundary_type : {'transmission, 'vacuum', 'reflective'} + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. coefficients : dict @@ -1698,7 +1698,7 @@ class XCone(Cone): surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. @@ -1723,7 +1723,7 @@ class XCone(Cone): z-coordinate of the apex r2 : float Parameter related to the aperature - boundary_type : {'transmission, 'vacuum', 'reflective'} + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. coefficients : dict @@ -1769,7 +1769,7 @@ class YCone(Cone): surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. @@ -1794,7 +1794,7 @@ class YCone(Cone): z-coordinate of the apex r2 : float Parameter related to the aperature - boundary_type : {'transmission, 'vacuum', 'reflective'} + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. coefficients : dict @@ -1840,7 +1840,7 @@ class ZCone(Cone): surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. @@ -1865,7 +1865,7 @@ class ZCone(Cone): z-coordinate of the apex r2 : float Parameter related to the aperature - boundary_type : {'transmission, 'vacuum', 'reflective'} + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. coefficients : dict @@ -1911,7 +1911,7 @@ class Quadric(Surface): surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'}, optional + boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. @@ -1924,7 +1924,7 @@ class Quadric(Surface): ---------- a, b, c, d, e, f, g, h, j, k : float coefficients for the surface - boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'} + boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'} Boundary condition that defines the behavior for particles hitting the surface. coefficients : dict diff --git a/src/particle.cpp b/src/particle.cpp index 1fb1eadb09..b6a3f5dee1 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -490,6 +490,56 @@ Particle::cross_surface() } return; + } else if (surf->bc_ == BC_WHITE && settings::run_mode != RUN_MODE_PLOTTING) { + // ======================================================================= + // WHITE BOUNDARY + + // Do not handle white boundary conditions on lower universes + if (n_coord_ != 1) { + this->mark_as_lost("Cannot diffuse reflect particle " + std::to_string(id_) + + " off surface in a lower universe."); + return; + } + + if (!model::active_surface_tallies.empty()) { + score_surface_tally(this, model::active_surface_tallies); + } + + if (!model::active_meshsurf_tallies.empty()) { + Position r {this->r()}; + this->r() -= TINY_BIT * this->u(); + score_surface_tally(this, model::active_meshsurf_tallies); + this->r() = r; + } + + // Diffuse Reflect particle off surface + Direction u = surf->diffuse_reflect(this->r(), this->u()); + + // Make sure new particle direction is normalized + this->u() = u / u.norm(); + + // Reassign particle's cell and surface + coord_[0].cell = cell_last_[n_coord_last_ - 1]; + surface_ = -surface_; + + if (!settings::dagmc) { + n_coord_ = 1; + if (!find_cell(this, true)) { + this->mark_as_lost("Couldn't find particle after diffuse reflecting from surface " + + std::to_string(surf->id_) + "."); + return; + } + } + + // Set previous coordinate going slightly past surface crossing + // r_last_current_ = this->r() + TINY_BIT*this->u(); + + // Diagnostic message + if (settings::verbosity >= 10 || simulation::trace) { + write_message(" Diffuse reflected from surface " + std::to_string(surf->id_)); + } + return; + } else if (surf->bc_ == BC_PERIODIC && settings::run_mode != RUN_MODE_PLOTTING) { // ======================================================================= // PERIODIC BOUNDARY diff --git a/src/surface.cpp b/src/surface.cpp index 14d077cfff..f759e7752b 100644 --- a/src/surface.cpp +++ b/src/surface.cpp @@ -11,6 +11,7 @@ #include "openmc/settings.h" #include "openmc/string_utils.h" #include "openmc/xml_interface.h" +#include "openmc/random_lcg.h" namespace openmc { @@ -22,7 +23,7 @@ extern "C" const int BC_TRANSMIT {0}; extern "C" const int BC_VACUUM {1}; extern "C" const int BC_REFLECT {2}; extern "C" const int BC_PERIODIC {3}; - +extern "C" const int BC_WHITE {4}; //============================================================================== // Global variables //============================================================================== @@ -147,6 +148,8 @@ Surface::Surface(pugi::xml_node surf_node) } else if (surf_bc == "reflective" || surf_bc == "reflect" || surf_bc == "reflecting") { bc_ = BC_REFLECT; + } else if (surf_bc == "white" || surf_bc == "diffuse") { + bc_ = BC_WHITE; } else if (surf_bc == "periodic") { bc_ = BC_PERIODIC; } else { @@ -192,6 +195,59 @@ Surface::reflect(Position r, Direction u) const return u -= (2.0 * projection / magnitude) * n; } +Direction +Surface::diffuse_reflect(Position r, Direction u) const +{ + // Diffuse reflect direction according to the normal. + // cosine distribution + + Direction n = normal(r); + n = n/n.norm(); + const double projection = std::max(-1.0, std::min(1.0, n.dot(u))); + + // sample from inverse function, u=sqrt(rand) since p(u)=2u, so F(u)=u^2 + const double cosine = (projection>=0.0) ? + -std::sqrt(prn()) : std::sqrt(prn()); + + // handle special case for very large cosine + if(std::fabs(cosine)>1.0001){ + std::cout<<"cosine="<<"\n"; + exit(-1); + }else if(std::fabs(cosine)>=1.000){ + return u = cosine * n; + } + + // sample azimuthal distribution uniformly + double t1, t2, rr, ss, tt; + for(;;){ + t1 = 2.0 * prn() - 1.0; + t2 = 2.0 * prn() - 1.0; + rr = t1 * t1 + t2 * t2; + if(rr<=1.0) break; + } + rr = std::sqrt((1.0 - cosine * cosine)/rr); + t1 = t1 * rr; + t2 = t2 * rr; + + // determine the direction off surface relative to norm + if (std::fabs(n[2]<=0.9)) { // for the general normal + tt = (std::sqrt(n[0]*n[0]+n[1]*n[1])); + ss = 1.0/tt; + u[0] = n[0] * cosine + (t1*n[0]*n[2]-t2*n[1])*ss; + u[1] = n[1] * cosine + (t1*n[1]*n[2]+t2*n[0])*ss; + u[2] = n[2] * cosine - t1*tt; + } else { // for the normal is almost along with z-axis + tt = (std::sqrt(n[0]*n[0]+n[2]*n[2])); + ss = 1.0/tt; + u[0] = n[0] * cosine + (t1*n[0]*n[1]+t2*n[2])*ss; + u[1] = n[1] * cosine - t1*tt; + u[2] = n[2] * cosine + (t1*n[1]*n[2]-t2*n[0])*ss; + } + + // normalize the direction + return u = u/u.norm(); +} + CSGSurface::CSGSurface() : Surface{} {}; CSGSurface::CSGSurface(pugi::xml_node surf_node) : Surface{surf_node} {}; @@ -213,6 +269,9 @@ CSGSurface::to_hdf5(hid_t group_id) const case BC_REFLECT : write_string(surf_group, "boundary_type", "reflective", false); break; + case BC_WHITE : + write_string(surf_group, "boundary_type", "white", false); + break; case BC_PERIODIC : write_string(surf_group, "boundary_type", "periodic", false); break; From 71825fb35a6ea1d42261f9e19ece22dfdbbb4b75 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 10 Sep 2019 09:01:18 -0500 Subject: [PATCH 016/158] Mark fission heating as redundant cross section Fission heating should not contribute to total cross section, and marking the reaction as redundant ensures this. MT=318 for fission heating is already included in the redundant reactions to keep when exported to HDF5 Related to #1340 --- openmc/data/neutron.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openmc/data/neutron.py b/openmc/data/neutron.py index 9024b932e4..32de3385f4 100644 --- a/openmc/data/neutron.py +++ b/openmc/data/neutron.py @@ -834,6 +834,7 @@ class IncidentNeutron(EqualityMixin): non_fission_heating = Reaction(999) non_fission_heating.redundant = True fission_heating = Reaction(318) + fission_heating.redundant = True heatr_evals = get_evaluations(kwargs["heatr"]) for heatr in heatr_evals: From e0d4a80f9ac36d92c694d7f4ee54cafb24b80343 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 10 Sep 2019 12:00:11 -0500 Subject: [PATCH 017/158] Use same energy grid for fission heating as total heating The fission heating data generated with IncidentNeutron.from_njoy does not have the same energy grid as other reactions. Previously, the energy grid was maintained and the non-fission heating was generated by evaluating total heating at each point in the fission heating grid and taking total_xs(fission_h.x) - fission_h.y This led to the crash in #1340 where openmc expected cross sections to use the same grid, or at least same number of grid points when computing the total cross section. While fission heating is not included in the total cross section [since 71825fb35], a consistent energy grid is helpful. Both fission heating and non-fission heating now use the energy grid from the total heating. --- openmc/data/neutron.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/openmc/data/neutron.py b/openmc/data/neutron.py index 32de3385f4..dfd44a4d07 100644 --- a/openmc/data/neutron.py +++ b/openmc/data/neutron.py @@ -842,15 +842,17 @@ class IncidentNeutron(EqualityMixin): f318 = StringIO(heatr.section[3, 318]) get_head_record(f318) _params, fission_kerma = get_tab1_record(f318) - fission_heating.xs[temp] = fission_kerma total_heating_xs = data.reactions[301].xs.get(temp) if total_heating_xs is None: + fission_heating.xs[temp] = fission_kerma continue + # Cast fission heating to same grid as total heating + new_fission_heat_xs = fission_kerma(total_heating_xs.x) + fission_heating.xs[temp] = Tabulated1D( + total_heating_xs.x, new_fission_heat_xs) non_fission_heating.xs[temp] = Tabulated1D( - fission_kerma.x, - total_heating_xs(fission_kerma.x) - fission_kerma.y, - breakpoints=fission_kerma.breakpoints, - interpolation=fission_kerma.interpolation) + total_heating_xs.x, + total_heating_xs.y - new_fission_heat_xs) data.reactions[318] = fission_heating data.reactions[999] = non_fission_heating From 2c965ef7f810c1945cbe379852922bf0efe9832a Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 11 Sep 2019 10:26:45 -0500 Subject: [PATCH 018/158] Use defaultdict(dict) when weighting fission yields The Chain.form_matrix method looks for information and fission yields for all isotopes with a fission reaction. However, the AveragedFissionYieldHelper and FissionYieldCutoffHelper are not guaranteed to return yields to all isotopes with fission reactions. Instead, the union on two sets of yields are returned: 1) yields from isotopes with a single set of fission yields 2) weighted yields computed for isotopes with both multiple sets of fission yields **and** non-zero densities. This later item can cause some isotopes that the Chain anticipates having fission yields, since they have a fission reaction, to not exist in the yields library. An isotope with multiple sets of yields but that is not physically present in the problem will not have reaction rates and also not have fission yields computed by these helpers. This will cause a KeyError during Chain.form_matrix when fission yields for these isotopes are requested. This commit changes the return type from weighted_yields on ConstantFissionYieldHelper, AveragedFissionYieldHelper, and FissionYieldCutoffHelper to be defaultdict(dict). This resolves the above issue by returning an "empty" set of yields for isotopes that potentially *should* have fission yields, but have zero density across the problem, and therefore do not have tallied reaction rates nor fission yields. Documentation for the changes to these classes have been updated --- openmc/deplete/abc.py | 22 ++++++++++++++-------- openmc/deplete/helpers.py | 29 ++++++++++++++++++++--------- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index fb638e5c21..3d4c93ac83 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -5,6 +5,7 @@ to run a full depletion simulation. """ from collections import namedtuple +from collections import defaultdict from collections.abc import Iterable import os from pathlib import Path @@ -376,14 +377,17 @@ class FissionYieldHelper(ABC): Attributes ---------- - constant_yields : dict of str to :class:`openmc.deplete.FissionYield` + constant_yields : collections.defaultdict Fission yields for all nuclides that only have one set of - fission yield data. Can be accessed as ``{parent: {product: yield}}`` + fission yield data. Dictionary of form ``{str: {str: float}}`` + representing yields for ``{parent: {product: yield}}``. Default + return object is an empty dictionary + """ def __init__(self, chain_nuclides): self._chain_nuclides = {} - self._constant_yields = {} + self._constant_yields = defaultdict(dict) # Get all nuclides with fission yield data for nuc in chain_nuclides: @@ -407,14 +411,16 @@ class FissionYieldHelper(ABC): Parameters ---------- local_mat_index : int - Index for material tracked on this process that - exists in :attr:`local_mat_index` and fits within - the first axis in :attr:`results` + Index for the material with requested fission yields. + Should correspond to the material represented in + ``mat_indexes[local_mat_index]`` during + :meth:`generate_tallies`. Returns ------- - library : dict - Dictionary of ``{parent: {product: fyield}}`` + library : collections.abc.Mapping + Dictionary-like object mapping ``{str: {str: float}``. + This reflects fission yields for ``{parent: {product: fyield}}``. """ @staticmethod diff --git a/openmc/deplete/helpers.py b/openmc/deplete/helpers.py index 56644a3bba..f6e6bb04da 100644 --- a/openmc/deplete/helpers.py +++ b/openmc/deplete/helpers.py @@ -5,6 +5,7 @@ from copy import deepcopy from itertools import product from numbers import Real import bisect +from collections import defaultdict from numpy import dot, zeros, newaxis @@ -179,9 +180,11 @@ class ConstantFissionYieldHelper(FissionYieldHelper): Attributes ---------- - constant_yields : dict of str to :class:`openmc.deplete.FissionYield` + constant_yields : collections.defaultdict Fission yields for all nuclides that only have one set of - fission yield data. Can be accessed as ``{parent: {product: yield}}`` + fission yield data. Dictionary of form ``{str: {str: float}}`` + representing yields for ``{parent: {product: yield}}``. Default + return object is an empty dictionary energy : float Energy of fission yield libraries. """ @@ -237,7 +240,7 @@ class ConstantFissionYieldHelper(FissionYieldHelper): Returns ------- - library : dict + library : collections.defaultdict Dictionary of ``{parent: {product: fyield}}`` """ return self.constant_yields @@ -282,6 +285,11 @@ class FissionYieldCutoffHelper(TalliedFissionYieldHelper): fast_yields : dict Dictionary of the form ``{parent: {product: yield}}`` with fast yields + constant_yields : collections.defaultdict + Fission yields for all nuclides that only have one set of + fission yield data. Dictionary of form ``{str: {str: float}}`` + representing yields for ``{parent: {product: yield}}``. Default + return object is an empty dictionary results : numpy.ndarray Array of fission rate fractions with shape ``(n_mats, 2, n_nucs)``. ``results[:, 0]`` @@ -417,7 +425,7 @@ class FissionYieldCutoffHelper(TalliedFissionYieldHelper): Returns ------- - library : dict + library : collections.defaultdict Dictionary of ``{parent: {product: fyield}}`` """ yields = self.constant_yields @@ -469,9 +477,11 @@ class AveragedFissionYieldHelper(TalliedFissionYieldHelper): Attributes ---------- - constant_yields : dict of str to :class:`openmc.deplete.FissionYield` + constant_yields : collections.defaultdict Fission yields for all nuclides that only have one set of - fission yield data. Can be accessed as ``{parent: {product: yield}}`` + fission yield data. Dictionary of form ``{str: {str: float}}`` + representing yields for ``{parent: {product: yield}}``. Default + return object is an empty dictionary results : None or numpy.ndarray If tallies have been generated and unpacked, then the array will have shape ``(n_mats, n_tnucs)``, where ``n_mats`` is the number @@ -569,12 +579,13 @@ class AveragedFissionYieldHelper(TalliedFissionYieldHelper): Returns ------- - library : dict - Dictionary of ``{parent: {product: fyield}}`` + library : collections.defaultdict + Dictionary of ``{parent: {product: fyield}}``. Default return + value is an empty dictionary """ if not self._tally_nucs: return self.constant_yields - mat_yields = {} + mat_yields = defaultdict(dict) average_energies = self.results[local_mat_index] for avg_e, nuc in zip(average_energies, self._tally_nucs): nuc_energies = nuc.yield_energies From 1a4bbb90685cab9a674f4b460d05595f9949da2c Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 11 Sep 2019 10:41:58 -0500 Subject: [PATCH 019/158] Chain.get_thermal_fission_yields -> Chain.get_default_fission_yields --- openmc/deplete/chain.py | 8 ++++---- tests/dummy_operator.py | 2 +- tests/unit_tests/test_deplete_chain.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index c2b5b75b31..5814427852 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -398,7 +398,7 @@ class Chain(object): clean_indentation(root_elem) tree.write(str(filename), encoding='utf-8') - def get_thermal_fission_yields(self): + def get_default_fission_yields(self): """Return fission yields at lowest incident neutron energy Used as the default set of fission yields for :meth:`form_matrix` @@ -440,13 +440,13 @@ class Chain(object): See Also -------- - :meth:`get_thermal_fission_yields` + :meth:`get_default_fission_yields` """ matrix = defaultdict(float) reactions = set() if fission_yields is None: - fission_yields = self.get_thermal_fission_yields() + fission_yields = self.get_default_fission_yields() for i, nuc in enumerate(self.nuclides): @@ -721,7 +721,7 @@ class Chain(object): @property def fission_yields(self): if self._fission_yields is None: - self._fission_yields = [self.get_thermal_fission_yields()] + self._fission_yields = [self.get_default_fission_yields()] return self._fission_yields @fission_yields.setter diff --git a/tests/dummy_operator.py b/tests/dummy_operator.py index 85c689743a..35a526c30d 100644 --- a/tests/dummy_operator.py +++ b/tests/dummy_operator.py @@ -83,7 +83,7 @@ class TestChain(object): fission_yields = [None] @staticmethod - def get_thermal_fission_yields(): + def get_default_fission_yields(): return None def form_matrix(self, rates, _fission_yields=None): diff --git a/tests/unit_tests/test_deplete_chain.py b/tests/unit_tests/test_deplete_chain.py index 3dcd3ad750..d5a3857a52 100644 --- a/tests/unit_tests/test_deplete_chain.py +++ b/tests/unit_tests/test_deplete_chain.py @@ -385,13 +385,13 @@ def test_set_alpha_branches(): def test_simple_fission_yields(simple_chain): """Check the default fission yields that can be used to form the matrix """ - fission_yields = simple_chain.get_thermal_fission_yields() + fission_yields = simple_chain.get_default_fission_yields() assert fission_yields == {"C": {"A": 0.0292737, "B": 0.002566345}} def test_fission_yield_attribute(simple_chain): """Test the fission_yields property""" - thermal_yields = simple_chain.get_thermal_fission_yields() + thermal_yields = simple_chain.get_default_fission_yields() # generate default with property assert simple_chain.fission_yields[0] == thermal_yields empty_chain = Chain() From 99dfa1f05304b4e41b6f58a5ec3ed489ad54c790 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 11 Sep 2019 10:42:16 -0500 Subject: [PATCH 020/158] Return defaultdict(dict) from Chain.get_default_fission_yields --- openmc/deplete/chain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index 5814427852..510112c7c9 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -412,7 +412,7 @@ class Chain(object): names of nuclides with yield data and ``f_yield`` is a float for the fission yield. """ - out = {} + out = defaultdict(dict) for nuc in self.nuclides: if nuc.yield_data is None: continue From d630047f4cfd32e8d01b76da4eb383facfe93ea5 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 11 Sep 2019 15:38:59 -0500 Subject: [PATCH 021/158] Allow IncidentNeutron.from_endf to work on heatr output files --- openmc/data/neutron.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openmc/data/neutron.py b/openmc/data/neutron.py index dfd44a4d07..9a796e9b42 100644 --- a/openmc/data/neutron.py +++ b/openmc/data/neutron.py @@ -777,9 +777,10 @@ class IncidentNeutron(EqualityMixin): for mt, rx in data.reactions.items(): if mt in (19, 20, 21, 38): if (5, mt) not in ev.section: - neutron = data.reactions[18].products[0] - rx.products[0].applicability = neutron.applicability - rx.products[0].distribution = neutron.distribution + if rx.products: + neutron = data.reactions[18].products[0] + rx.products[0].applicability = neutron.applicability + rx.products[0].distribution = neutron.distribution # Read fission energy release (requires that we already know nu for # fission) From fe7a968819bfb831185c11295ba2d9b39b4d41e8 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 12 Sep 2019 07:24:12 -0500 Subject: [PATCH 022/158] Run HEATR twice to get kermas including local photon energy deposition --- openmc/data/neutron.py | 83 +++++++++++++++++++++++++++++------------- openmc/data/njoy.py | 18 +++++++-- 2 files changed, 72 insertions(+), 29 deletions(-) diff --git a/openmc/data/neutron.py b/openmc/data/neutron.py index 9a796e9b42..2b3345e571 100644 --- a/openmc/data/neutron.py +++ b/openmc/data/neutron.py @@ -830,33 +830,66 @@ class IncidentNeutron(EqualityMixin): # Add fission energy release data ev = evaluation if evaluation is not None else Evaluation(filename) if (1, 458) in ev.section: - data.fission_energy = FissionEnergyRelease.from_endf(ev, data) - # Add 318 fission heating data from heatr - non_fission_heating = Reaction(999) - non_fission_heating.redundant = True - fission_heating = Reaction(318) - fission_heating.redundant = True + data.fission_energy = f = FissionEnergyRelease.from_endf(ev, data) + else: + f = None - heatr_evals = get_evaluations(kwargs["heatr"]) - for heatr in heatr_evals: - temp = "{}K".format(round(heatr.target["temperature"])) - f318 = StringIO(heatr.section[3, 318]) - get_head_record(f318) - _params, fission_kerma = get_tab1_record(f318) - total_heating_xs = data.reactions[301].xs.get(temp) - if total_heating_xs is None: - fission_heating.xs[temp] = fission_kerma - continue - # Cast fission heating to same grid as total heating - new_fission_heat_xs = fission_kerma(total_heating_xs.x) - fission_heating.xs[temp] = Tabulated1D( - total_heating_xs.x, new_fission_heat_xs) - non_fission_heating.xs[temp] = Tabulated1D( - total_heating_xs.x, - total_heating_xs.y - new_fission_heat_xs) + # For energy deposition, we want to store two different KERMAs: + # one calculated assuming outgoing photons deposit their energy + # locally, and one calculated assuming they carry their energy + # away. This requires two HEATR runs (which make_ace does by + # default). Here, we just need to correct for the fact that NJOY + # uses a fission heating number of h = EFR, whereas we want: + # + # 1) h = EFR + EGP + EGD + EB (for local case) + # 2) h = EFR + EB (for non-local case) + # + # The best way to handle this is to subtract off the fission + # KERMA that NJOY calculates and add back exactly what we want. - data.reactions[318] = fission_heating - data.reactions[999] = non_fission_heating + heating_local = Reaction(901) + heating_local.redundant = True + + # Helper function to get a cross section from an ENDF file on a + # given energy grid + def get_file3_xs(ev, mt, E): + file_obj = StringIO(ev.section[3, mt]) + get_head_record(file_obj) + _, xs = get_tab1_record(file_obj) + return xs(E) + + heatr_evals = get_evaluations(kwargs["heatr"]) + heatr_local_evals = get_evaluations(kwargs["heatr"] + "_local") + for ev, ev_local in zip(heatr_evals, heatr_local_evals): + temp = "{}K".format(round(ev.target["temperature"])) + + # Get total KERMA (originally from ACE file) and energy grid + kerma = data.reactions[301].xs[temp] + E = kerma.x + + if f is not None: + # Replace fission KERMA with (EFR + EB)*sigma_f + fission = data.reactions[18].xs[temp] + kerma_fission = get_file3_xs(ev, 318, E) + kerma.y = kerma.y - kerma_fission + ( + f.fragments(E) + f.betas(E)) * fission.y + + # For local KERMA, we first need to get the values from the + # HEATR run with photon energy deposited locally and put + # them on the same energy grid + kerma_local = get_file3_xs(ev_local, 301, E) + + if f is not None: + # When photons deposit their energy locally, we replace the + # fission KERMA with (EFR + EGP + EGD + EB)*sigma_f + kerma_fission_local = get_file3_xs(ev_local, 318, E) + kerma_local = kerma_local - kerma_fission_local + ( + f.fragments(E) + f.prompt_photons(E) + + f.delayed_photons(E) + f.betas(E))*fission.y + + heating_local.xs[temp] = Tabulated1D(E, kerma_local) + + data.reactions[901] = heating_local # Add 0K elastic scattering cross section if '0K' not in data.energy: diff --git a/openmc/data/njoy.py b/openmc/data/njoy.py index 7993ec4004..4983098f73 100644 --- a/openmc/data/njoy.py +++ b/openmc/data/njoy.py @@ -71,7 +71,14 @@ broadr / %%%%%%%%%%%%%%%%%%%%%%% Doppler broaden XS %%%%%%%%%%%%%%%%%%%%%%%%%%%% _TEMPLATE_HEATR = """ heatr / %%%%%%%%%%%%%%%%%%%%%%%%% Add heating kerma %%%%%%%%%%%%%%%%%%%%%%%%%%%% {nendf} {nheatr_in} {nheatr} / -{mat} 4 / +{mat} 4 0 0 0 / +302 318 402 444 / +""" + +_TEMPLATE_HEATR_LOCAL = """ +heatr / %%%%%%%%%%%%%%%%% Add heating kerma (local photons) %%%%%%%%%%%%%%%%%%%% +{nendf} {nheatr_in} {nheatr_local} / +{mat} 4 0 0 1 / 302 318 402 444 / """ @@ -216,8 +223,7 @@ def make_pendf(filename, pendf='pendf', error=0.001, stdout=False): def make_ace(filename, temperatures=None, acer=True, xsdir=None, output_dir=None, pendf=False, error=0.001, broadr=True, - heatr=True, gaspr=True, purr=True, evaluation=None, - **kwargs): + heatr=True, gaspr=True, purr=True, evaluation=None, **kwargs): """Generate incident neutron ACE file from an ENDF file File names can be passed to @@ -320,7 +326,11 @@ def make_ace(filename, temperatures=None, acer=True, xsdir=None, # heatr if heatr: nheatr_in = nlast - nheatr = nheatr_in + 1 + nheatr_local = nheatr_in + 1 + tapeout[nheatr_local] = (output_dir / "heatr_local") if heatr is True \ + else heatr + '_local' + commands += _TEMPLATE_HEATR_LOCAL + nheatr = nheatr_local + 1 tapeout[nheatr] = (output_dir / "heatr") if heatr is True else heatr commands += _TEMPLATE_HEATR nlast = nheatr From d84c37900713494b8b4f53dcbfb55055715f889c Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 13 Sep 2019 07:51:08 -0500 Subject: [PATCH 023/158] Fix up heating tally scores --- docs/source/usersguide/tallies.rst | 5 + include/openmc/constants.h | 4 +- openmc/capi/tally.py | 2 +- openmc/data/neutron.py | 16 +-- openmc/data/reaction.py | 3 +- src/output.cpp | 1 - src/reaction.cpp | 196 +++++++++++++------------- src/tallies/tally.cpp | 5 +- src/tallies/tally_scoring.cpp | 10 +- tests/unit_tests/test_data_neutron.py | 28 ++-- 10 files changed, 130 insertions(+), 140 deletions(-) diff --git a/docs/source/usersguide/tallies.rst b/docs/source/usersguide/tallies.rst index f005b50606..1676417c66 100644 --- a/docs/source/usersguide/tallies.rst +++ b/docs/source/usersguide/tallies.rst @@ -263,6 +263,11 @@ The following tables show all valid scores: | |deposition (analog estimator) or pre-generated | | |photon heating number. | +----------------------+---------------------------------------------------+ + |heating-local |Total nuclear heating in units of eV per source | + | |particle assuming energy from secondary photons is | + | |deposited locally. Note that this score should only| + | |be used for incident neutrons. | + +----------------------+---------------------------------------------------+ |kappa-fission |The recoverable energy production rate due to | | |fission. The recoverable energy is defined as the | | |fission product kinetic energy, prompt and delayed | diff --git a/include/openmc/constants.h b/include/openmc/constants.h index 394f90740f..51df59accf 100644 --- a/include/openmc/constants.h +++ b/include/openmc/constants.h @@ -232,7 +232,7 @@ constexpr int N_XD {204}; constexpr int N_XT {205}; constexpr int N_X3HE {206}; constexpr int N_XA {207}; -constexpr int NEUTRON_HEATING {301}; +constexpr int HEATING {301}; constexpr int DAMAGE_ENERGY {444}; constexpr int COHERENT {502}; constexpr int INCOHERENT {504}; @@ -252,6 +252,7 @@ constexpr int N_A0 {800}; constexpr int N_AC {849}; constexpr int N_2N0 {875}; constexpr int N_2NC {891}; +constexpr int HEATING_LOCAL {901}; constexpr std::array DEPLETION_RX {N_GAMMA, N_P, N_A, N_2N, N_3N, N_4N}; @@ -369,7 +370,6 @@ constexpr int SCORE_INVERSE_VELOCITY {-13}; // flux-weighted inverse velocity constexpr int SCORE_FISS_Q_PROMPT {-14}; // prompt fission Q-value constexpr int SCORE_FISS_Q_RECOV {-15}; // recoverable fission Q-value constexpr int SCORE_DECAY_RATE {-16}; // delayed neutron precursor decay rate -constexpr int SCORE_HEATING {-17}; // nuclear heating (neutron or photon) // Tally map bin finding constexpr int NO_BIN_FOUND {-1}; diff --git a/openmc/capi/tally.py b/openmc/capi/tally.py index 88c14e4494..546a8cad40 100644 --- a/openmc/capi/tally.py +++ b/openmc/capi/tally.py @@ -89,7 +89,7 @@ _SCORES = { -5: 'absorption', -6: 'fission', -7: 'nu-fission', -8: 'kappa-fission', -9: 'current', -10: 'events', -11: 'delayed-nu-fission', -12: 'prompt-nu-fission', -13: 'inverse-velocity', -14: 'fission-q-prompt', - -15: 'fission-q-recoverable', -16: 'decay-rate', -17: 'heating' + -15: 'fission-q-recoverable', -16: 'decay-rate' } _ESTIMATORS = { 1: 'analog', 2: 'tracklength', 3: 'collision' diff --git a/openmc/data/neutron.py b/openmc/data/neutron.py index 2b3345e571..ad7ae17326 100644 --- a/openmc/data/neutron.py +++ b/openmc/data/neutron.py @@ -452,7 +452,7 @@ class IncidentNeutron(EqualityMixin): if rx.redundant: photon_rx = any(p.particle == 'photon' for p in rx.products) keep_mts = (4, 16, 103, 104, 105, 106, 107, - 203, 204, 205, 206, 207, 301, 318, 444) + 203, 204, 205, 206, 207, 301, 444, 901) if not (photon_rx or rx.mt in keep_mts): continue @@ -553,20 +553,6 @@ class IncidentNeutron(EqualityMixin): fer_group = group['fission_energy_release'] data.fission_energy = FissionEnergyRelease.from_hdf5(fer_group) - # Rebuild non-fission heating - total_heating = data.reactions.get(301) - fission_heating = data.reactions.get(318) - if total_heating is not None and fission_heating is not None: - non_fission_heating = Reaction(999) - non_fission_heating.redundant = True - for strT, total in total_heating.xs.items(): - fission = fission_heating.xs.get(strT) - if fission is None: - continue - non_fission_heating.xs[strT] = Tabulated1D( - total.x, total.y - fission(total.x)) - data.reactions[999] = non_fission_heating - return data @classmethod diff --git a/openmc/data/reaction.py b/openmc/data/reaction.py index 696310ccaf..83e773f204 100644 --- a/openmc/data/reaction.py +++ b/openmc/data/reaction.py @@ -54,9 +54,8 @@ REACTION_NAME = {1: '(n,total)', 2: '(n,elastic)', 4: '(n,level)', 198: '(n,n3p)', 199: '(n,3n2pa)', 200: '(n,5n2p)', 203: '(n,Xp)', 204: '(n,Xd)', 205: '(n,Xt)', 206: '(n,X3He)', 207: '(n,Xa)', 301: 'heating', 444: 'damage-energy', - 318: "fission-heating", 999: "non-fission-heating", 649: '(n,pc)', 699: '(n,dc)', 749: '(n,tc)', 799: '(n,3Hec)', - 849: '(n,ac)', 891: '(n,2nc)'} + 849: '(n,ac)', 891: '(n,2nc)', 901: 'heating-local'} REACTION_NAME.update({i: '(n,n{})'.format(i - 50) for i in range(50, 91)}) REACTION_NAME.update({i: '(n,p{})'.format(i - 600) for i in range(600, 649)}) REACTION_NAME.update({i: '(n,d{})'.format(i - 650) for i in range(650, 699)}) diff --git a/src/output.cpp b/src/output.cpp index 6516b48b61..40cd1dbfe9 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -620,7 +620,6 @@ const std::unordered_map score_names = { {SCORE_FISS_Q_PROMPT, "Prompt fission power"}, {SCORE_FISS_Q_RECOV, "Recoverable fission power"}, {SCORE_CURRENT, "Current"}, - {SCORE_HEATING, "Heating"}, }; //! Create an ASCII output file showing all tally results. diff --git a/src/reaction.cpp b/src/reaction.cpp index 31bf8efd33..f7f1e87acb 100644 --- a/src/reaction.cpp +++ b/src/reaction.cpp @@ -88,193 +88,189 @@ Reaction::Reaction(hid_t group, const std::vector& temperatures) std::string reaction_name(int mt) { if (mt == SCORE_FLUX) { - return "flux"; + return "flux"; } else if (mt == SCORE_TOTAL) { - return "total"; + return "total"; } else if (mt == SCORE_SCATTER) { - return "scatter"; + return "scatter"; } else if (mt == SCORE_NU_SCATTER) { - return "nu-scatter"; + return "nu-scatter"; } else if (mt == SCORE_ABSORPTION) { - return "absorption"; + return "absorption"; } else if (mt == SCORE_FISSION) { - return "fission"; + return "fission"; } else if (mt == SCORE_NU_FISSION) { - return "nu-fission"; + return "nu-fission"; } else if (mt == SCORE_DECAY_RATE) { - return "decay-rate"; + return "decay-rate"; } else if (mt == SCORE_DELAYED_NU_FISSION) { - return "delayed-nu-fission"; + return "delayed-nu-fission"; } else if (mt == SCORE_PROMPT_NU_FISSION) { - return "prompt-nu-fission"; + return "prompt-nu-fission"; } else if (mt == SCORE_KAPPA_FISSION) { - return "kappa-fission"; + return "kappa-fission"; } else if (mt == SCORE_CURRENT) { - return "current"; + return "current"; } else if (mt == SCORE_EVENTS) { - return "events"; + return "events"; } else if (mt == SCORE_INVERSE_VELOCITY) { - return "inverse-velocity"; + return "inverse-velocity"; } else if (mt == SCORE_FISS_Q_PROMPT) { - return "fission-q-prompt"; + return "fission-q-prompt"; } else if (mt == SCORE_FISS_Q_RECOV) { - return "fission-q-recoverable"; - } else if (mt == SCORE_HEATING) { - return "heating"; + return "fission-q-recoverable"; // Normal ENDF-based reactions } else if (mt == TOTAL_XS) { - return "(n,total)"; + return "(n,total)"; } else if (mt == ELASTIC) { - return "(n,elastic)"; + return "(n,elastic)"; } else if (mt == N_LEVEL) { - return "(n,level)"; + return "(n,level)"; } else if (mt == N_2ND) { - return "(n,2nd)"; + return "(n,2nd)"; } else if (mt == N_2N) { - return "(n,2n)"; + return "(n,2n)"; } else if (mt == N_3N) { - return "(n,3n)"; + return "(n,3n)"; } else if (mt == N_FISSION) { - return "(n,fission)"; + return "(n,fission)"; } else if (mt == N_F) { - return "(n,f)"; + return "(n,f)"; } else if (mt == N_NF) { - return "(n,nf)"; + return "(n,nf)"; } else if (mt == N_2NF) { - return "(n,2nf)"; + return "(n,2nf)"; } else if (mt == N_NA) { - return "(n,na)"; + return "(n,na)"; } else if (mt == N_N3A) { - return "(n,n3a)"; + return "(n,n3a)"; } else if (mt == N_2NA) { - return "(n,2na)"; + return "(n,2na)"; } else if (mt == N_3NA) { - return "(n,3na)"; + return "(n,3na)"; } else if (mt == N_NP) { - return "(n,np)"; + return "(n,np)"; } else if (mt == N_N2A) { - return "(n,n2a)"; + return "(n,n2a)"; } else if (mt == N_2N2A) { - return "(n,2n2a)"; + return "(n,2n2a)"; } else if (mt == N_ND) { - return "(n,nd)"; + return "(n,nd)"; } else if (mt == N_NT) { - return "(n,nt)"; + return "(n,nt)"; } else if (mt == N_N3HE) { - return "(n,nHe-3)"; + return "(n,nHe-3)"; } else if (mt == N_ND2A) { - return "(n,nd2a)"; + return "(n,nd2a)"; } else if (mt == N_NT2A) { - return "(n,nt2a)"; + return "(n,nt2a)"; } else if (mt == N_4N) { - return "(n,4n)"; + return "(n,4n)"; } else if (mt == N_3NF) { - return "(n,3nf)"; + return "(n,3nf)"; } else if (mt == N_2NP) { - return "(n,2np)"; + return "(n,2np)"; } else if (mt == N_3NP) { - return "(n,3np)"; + return "(n,3np)"; } else if (mt == N_N2P) { - return "(n,n2p)"; + return "(n,n2p)"; } else if (mt == N_NPA) { - return "(n,npa)"; + return "(n,npa)"; } else if (N_N1 <= mt && mt <= N_N40) { - return "(n,n" + std::to_string(mt-50) + ")"; + return "(n,n" + std::to_string(mt-50) + ")"; } else if (mt == N_NC) { - return "(n,nc)"; + return "(n,nc)"; } else if (mt == N_DISAPPEAR) { - return "(n,disappear)"; + return "(n,disappear)"; } else if (mt == N_GAMMA) { - return "(n,gamma)"; + return "(n,gamma)"; } else if (mt == N_P) { - return "(n,p)"; + return "(n,p)"; } else if (mt == N_D) { - return "(n,d)"; + return "(n,d)"; } else if (mt == N_T) { - return "(n,t)"; + return "(n,t)"; } else if (mt == N_3HE) { - return "(n,3He)"; + return "(n,3He)"; } else if (mt == N_A) { - return "(n,a)"; + return "(n,a)"; } else if (mt == N_2A) { - return "(n,2a)"; + return "(n,2a)"; } else if (mt == N_3A) { - return "(n,3a)"; + return "(n,3a)"; } else if (mt == N_2P) { - return "(n,2p)"; + return "(n,2p)"; } else if (mt == N_PA) { - return "(n,pa)"; + return "(n,pa)"; } else if (mt == N_T2A) { - return "(n,t2a)"; + return "(n,t2a)"; } else if (mt == N_D2A) { - return "(n,d2a)"; + return "(n,d2a)"; } else if (mt == N_PD) { - return "(n,pd)"; + return "(n,pd)"; } else if (mt == N_PT) { - return "(n,pt)"; + return "(n,pt)"; } else if (mt == N_DA) { - return "(n,da)"; + return "(n,da)"; } else if (mt == 201) { - return "(n,Xn)"; + return "(n,Xn)"; } else if (mt == 202) { - return "(n,Xgamma)"; - } else if (mt == 203) { - return "(n,Xp)"; - } else if (mt == 204) { - return "(n,Xd)"; - } else if (mt == 205) { - return "(n,Xt)"; - } else if (mt == 206) { - return "(n,X3He)"; - } else if (mt == 207) { - return "(n,Xa)"; - } else if (mt == 301) { + return "(n,Xgamma)"; + } else if (mt == N_XP) { + return "(n,Xp)"; + } else if (mt == N_XD) { + return "(n,Xd)"; + } else if (mt == N_XT) { + return "(n,Xt)"; + } else if (mt == N_X3HE) { + return "(n,X3He)"; + } else if (mt == N_XA) { + return "(n,Xa)"; + } else if (mt == HEATING) { return "heating"; - } else if (mt == 318) { - return "fission-heating"; - } else if (mt == 999) { - return "non-fission-heating"; - } else if (mt == 444) { - return "damage-energy"; + } else if (mt == DAMAGE_ENERGY) { + return "damage-energy"; } else if (mt == COHERENT) { - return "coherent scatter"; + return "coherent scatter"; } else if (mt == INCOHERENT) { - return "incoherent scatter"; + return "incoherent scatter"; } else if (mt == PAIR_PROD_ELEC) { - return "pair production, electron"; + return "pair production, electron"; } else if (mt == PAIR_PROD) { - return "pair production"; + return "pair production"; } else if (mt == PAIR_PROD_NUC) { - return "pair production, nuclear"; + return "pair production, nuclear"; } else if (mt == PHOTOELECTRIC) { - return "photoelectric"; + return "photoelectric"; } else if (534 <= mt && mt <= 572) { std::stringstream name; name << "photoelectric, " << SUBSHELLS[mt - 534] << " subshell"; return name.str(); } else if (600 <= mt && mt <= 648) { - return "(n,p" + std::to_string(mt-600) + ")"; + return "(n,p" + std::to_string(mt-600) + ")"; } else if (mt == 649) { - return "(n,pc)"; + return "(n,pc)"; } else if (650 <= mt && mt <= 698) { - return "(n,d" + std::to_string(mt-650) + ")"; + return "(n,d" + std::to_string(mt-650) + ")"; } else if (mt == 699) { - return "(n,dc)"; + return "(n,dc)"; } else if (700 <= mt && mt <= 748) { - return "(n,t" + std::to_string(mt-700) + ")"; + return "(n,t" + std::to_string(mt-700) + ")"; } else if (mt == 749) { - return "(n,tc)"; + return "(n,tc)"; } else if (750 <= mt && mt <= 798) { - return "(n,3He" + std::to_string(mt-750) + ")"; + return "(n,3He" + std::to_string(mt-750) + ")"; } else if (mt == 799) { - return "(n,3Hec)"; + return "(n,3Hec)"; } else if (800 <= mt && mt <= 848) { - return "(n,a" + std::to_string(mt-800) + ")"; + return "(n,a" + std::to_string(mt-800) + ")"; } else if (mt == 849) { - return "(n,ac)"; + return "(n,ac)"; + } else if (mt == HEATING_LOCAL) { + return "heating-local"; } else { - return "MT=" + std::to_string(mt); + return "MT=" + std::to_string(mt); } } diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 27d6cc58c0..563beee766 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -111,7 +111,10 @@ score_str_to_int(std::string score_str) return SCORE_FISS_Q_RECOV; if (score_str == "heating") - return SCORE_HEATING; + return HEATING; + + if (score_str == "heating-local") + return HEATING_LOCAL; if (score_str == "current") return SCORE_CURRENT; diff --git a/src/tallies/tally_scoring.cpp b/src/tallies/tally_scoring.cpp index 1f6a8010a5..37310070fe 100644 --- a/src/tallies/tally_scoring.cpp +++ b/src/tallies/tally_scoring.cpp @@ -192,7 +192,7 @@ double get_nuc_fission_q(const Nuclide& nuc, const Particle* p, int score_bin) //! Pulled out to support both the fission_q scores and energy deposition //! score -double score_fission_q(const Particle* p, int score_bin, const Tally& tally, +double score_fission_q(const Particle* p, int score_bin, const Tally& tally, double flux, int i_nuclide, double atom_density) { if (tally.estimator_ == ESTIMATOR_ANALOG) { @@ -231,7 +231,7 @@ double score_fission_q(const Particle* p, int score_bin, const Tally& tally, auto j_nuclide = material.nuclide_[i]; auto atom_density = material.atom_density_(i); const Nuclide& nuc {*data::nuclides[j_nuclide]}; - score += get_nuc_fission_q(nuc, p, score_bin) * atom_density + score += get_nuc_fission_q(nuc, p, score_bin) * atom_density * p->neutron_xs_[j_nuclide].fission; } return score * flux; @@ -268,7 +268,7 @@ double score_neutron_heating(const Particle* p, const Tally& tally, double flux, // Get heating macroscopic "cross section" double heating_xs; if (i_nuclide >= 0) { - const Nuclide& nuc {*data::nuclides[i_nuclide]}; + const Nuclide& nuc {*data::nuclides[i_nuclide]}; heating_xs = get_nuclide_neutron_heating(p, nuc, rxn_bin, i_nuclide); if (tally.estimator_ == ESTIMATOR_ANALOG) { heating_xs /= p->neutron_xs_[i_nuclide].total; @@ -1210,10 +1210,10 @@ score_general_ce(Particle* p, int i_tally, int start_index, break; - case SCORE_HEATING: + case HEATING: score = 0.; if (p->type_ == Particle::Type::neutron) { - score = score_neutron_heating(p, tally, flux, NEUTRON_HEATING, + score = score_neutron_heating(p, tally, flux, HEATING, i_nuclide, atom_density); } else if (p->type_ == Particle::Type::photon) { if (tally.estimator_ == ESTIMATOR_ANALOG) { diff --git a/tests/unit_tests/test_data_neutron.py b/tests/unit_tests/test_data_neutron.py index ed83956031..d8639cc5a1 100644 --- a/tests/unit_tests/test_data_neutron.py +++ b/tests/unit_tests/test_data_neutron.py @@ -205,20 +205,22 @@ def test_derived_products(am244): assert total_neutron.yield_(6e6) == pytest.approx(4.2558) -def test_heating(run_in_tmpdir, am244): - assert 318 in am244.reactions - assert 999 in am244.reactions - # compare values in 999 reaction - total_heating = am244.reactions[301].xs["294K"] - fission_heating = am244.reactions[318].xs["294K"] - expected_y = total_heating.y - fission_heating(total_heating.x) - assert am244.reactions[999].xs["294K"].y == pytest.approx(expected_y) +def test_kerma(run_in_tmpdir, am244, h2): + # Make sure kerma w/ local photon is >= regular kerma + for nuc in (am244, h2): + assert 301 in nuc + assert 901 in nuc + for T in nuc.temperatures: + k, k_local = nuc[301].xs[T], nuc[901].xs[T] + assert np.all(k.x == k_local.x) + assert np.all(k_local.y >= k.y) - am244.export_to_hdf5("am244.h5") - read_in = openmc.data.IncidentNeutron.from_hdf5("am244.h5") - assert 318 in read_in.reactions - assert 999 in read_in.reactions - assert read_in.reactions[999].xs["294K"].y == pytest.approx(expected_y) + # Make sure 301/901 get exported/imported correctly + h2.export_to_hdf5("H2.h5") + read_in = openmc.data.IncidentNeutron.from_hdf5("H2.h5") + assert 301 in read_in + assert 901 in read_in + assert np.all(read_in[901].xs['300K'].y == h2[901].xs['300K'].y) def test_urr(pu239): From fe731b7f1960f9f68031bb9a89c53c00aa9e1d2d Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 13 Sep 2019 07:54:30 -0500 Subject: [PATCH 024/158] Fix bug with IncidentNeutron.temperatures for ENDF files --- openmc/data/neutron.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/data/neutron.py b/openmc/data/neutron.py index ad7ae17326..c63142d141 100644 --- a/openmc/data/neutron.py +++ b/openmc/data/neutron.py @@ -730,7 +730,7 @@ class IncidentNeutron(EqualityMixin): # Instantiate incident neutron data data = cls(name, atomic_number, mass_number, metastable, - atomic_weight_ratio, temperature) + atomic_weight_ratio, [temperature]) if (2, 151) in ev.section: data.resonances = res.Resonances.from_endf(ev) From bbb0b7838331341dd38841ba31d6aa5204fbedf0 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 5 Sep 2019 07:31:13 -0500 Subject: [PATCH 025/158] Rename openmc.capi -> openmc.lib --- CMakeLists.txt | 2 +- docs/source/capi/index.rst | 16 +- docs/source/pythonapi/capi.rst | 8 +- openmc/cmfd.py | 118 ++++----- openmc/deplete/abc.py | 8 +- openmc/deplete/helpers.py | 14 +- openmc/deplete/operator.py | 18 +- openmc/{capi => lib}/__init__.py | 16 +- openmc/{capi => lib}/cell.py | 2 +- openmc/{capi => lib}/core.py | 32 +-- openmc/{capi => lib}/error.py | 0 openmc/{capi => lib}/filter.py | 0 openmc/{capi => lib}/material.py | 2 +- openmc/{capi => lib}/math.py | 0 openmc/{capi => lib}/mesh.py | 2 +- openmc/{capi => lib}/nuclide.py | 2 +- openmc/{capi => lib}/plot.py | 8 +- openmc/{capi => lib}/settings.py | 0 openmc/{capi => lib}/tally.py | 2 +- openmc/model/model.py | 2 +- openmc/polynomial.py | 6 +- setup.py | 2 +- tests/regression_tests/complex_cell/test.py | 3 - tests/regression_tests/dagmc/legacy/test.py | 4 +- tests/regression_tests/dagmc/refl/test.py | 4 +- tests/regression_tests/dagmc/uwuw/test.py | 4 +- tests/unit_tests/dagmc/test.py | 10 +- tests/unit_tests/test_capi.py | 244 +++++++++--------- tests/unit_tests/test_complex_cell_capi.py | 10 +- .../unit_tests/test_deplete_fission_yields.py | 22 +- tests/unit_tests/test_math.py | 42 +-- 31 files changed, 300 insertions(+), 303 deletions(-) rename openmc/{capi => lib}/__init__.py (69%) rename openmc/{capi => lib}/cell.py (99%) rename openmc/{capi => lib}/core.py (94%) rename openmc/{capi => lib}/error.py (100%) rename openmc/{capi => lib}/filter.py (100%) rename openmc/{capi => lib}/material.py (99%) rename openmc/{capi => lib}/math.py (100%) rename openmc/{capi => lib}/mesh.py (99%) rename openmc/{capi => lib}/nuclide.py (98%) rename openmc/{capi => lib}/plot.py (97%) rename openmc/{capi => lib}/settings.py (100%) rename openmc/{capi => lib}/tally.py (99%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 381f0cd7b7..626aadae82 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -336,7 +336,7 @@ set_target_properties( add_custom_command(TARGET libopenmc POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ - ${CMAKE_CURRENT_SOURCE_DIR}/openmc/capi/$ + ${CMAKE_CURRENT_SOURCE_DIR}/openmc/lib/$ COMMENT "Copying libopenmc to Python module directory") #=============================================================================== diff --git a/docs/source/capi/index.rst b/docs/source/capi/index.rst index 90bef1909c..0ce1c234d1 100644 --- a/docs/source/capi/index.rst +++ b/docs/source/capi/index.rst @@ -1,17 +1,17 @@ .. _capi: -===== -C API -===== +========= +C/C++ API +========= The libopenmc shared library that is built when installing OpenMC exports a number of C interoperable functions and global variables that can be used for -in-memory coupling. While it is possible to directly use the C API as documented -here for coupling, most advanced users will find it easier to work with the -Python bindings in the :py:mod:`openmc.capi` module. +in-memory coupling. While it is possible to directly use the C/C++ API as +documented here for coupling, most advanced users will find it easier to work +with the Python bindings in the :py:mod:`openmc.lib` module. -.. warning:: The C API is still experimental and may undergo substantial changes - in future releases. +.. warning:: The C/C++ API is still experimental and may undergo substantial + changes in future releases. ---------------- Type Definitions diff --git a/docs/source/pythonapi/capi.rst b/docs/source/pythonapi/capi.rst index cacc5472e1..44094ffd41 100644 --- a/docs/source/pythonapi/capi.rst +++ b/docs/source/pythonapi/capi.rst @@ -1,8 +1,8 @@ --------------------------------------------------- -:mod:`openmc.capi` -- Python bindings to the C API --------------------------------------------------- +------------------------------------------------------ +:mod:`openmc.lib` -- Python bindings to the C/C++ API +------------------------------------------------------ -.. automodule:: openmc.capi +.. automodule:: openmc.lib Functions --------- diff --git a/openmc/cmfd.py b/openmc/cmfd.py index 742cdeabfd..b215d20584 100644 --- a/openmc/cmfd.py +++ b/openmc/cmfd.py @@ -22,7 +22,7 @@ import numpy as np from scipy import sparse import h5py -import openmc.capi +import openmc.lib from openmc.checkvalue import (check_type, check_length, check_value, check_greater_than, check_less_than) from openmc.exceptions import OpenMCError @@ -701,7 +701,7 @@ class CMFDRun(object): ---------- **kwargs All keyword arguments are passed to - :func:`openmc.capi.run_in_memory`. + :func:`openmc.lib.run_in_memory`. """ with self.run_in_memory(**kwargs): @@ -726,7 +726,7 @@ class CMFDRun(object): Parameters ---------- **kwargs - All keyword arguments passed to :func:`openmc.capi.run_in_memory`. + All keyword arguments passed to :func:`openmc.lib.run_in_memory`. """ # Store intracomm for part of CMFD routine where MPI reduce and @@ -737,7 +737,7 @@ class CMFDRun(object): self._intracomm = MPI.COMM_WORLD # Run and pass arguments to C API run_in_memory function - with openmc.capi.run_in_memory(**kwargs): + with openmc.lib.run_in_memory(**kwargs): self.init() yield self.finalize() @@ -759,7 +759,7 @@ class CMFDRun(object): def init(self): """ Initialize CMFDRun instance by setting up CMFD parameters and - calling :func:`openmc.capi.simulation_init` + calling :func:`openmc.lib.simulation_init` """ # Configure CMFD parameters and tallies @@ -780,10 +780,10 @@ class CMFDRun(object): self._initialize_linsolver() # Initialize simulation - openmc.capi.simulation_init() + openmc.lib.simulation_init() # Set cmfd_run variable to True through C API - openmc.capi.settings.cmfd_run = True + openmc.lib.settings.cmfd_run = True def next_batch(self): """ Run next batch for CMFDRun. @@ -799,29 +799,29 @@ class CMFDRun(object): self._cmfd_init_batch() # Run next batch - status = openmc.capi.next_batch() + status = openmc.lib.next_batch() # Perform CMFD calculation if on if self._cmfd_on: self._execute_cmfd() # Write CMFD output if CMFD on for current batch - if openmc.capi.master(): + if openmc.lib.master(): self._write_cmfd_output() # Write CMFD data to statepoint - if openmc.capi.is_statepoint_batch(): + if openmc.lib.is_statepoint_batch(): self.statepoint_write() return status def finalize(self): """ Finalize simulation by calling - :func:`openmc.capi.simulation_finalize` and print out CMFD timing + :func:`openmc.lib.simulation_finalize` and print out CMFD timing information. """ # Finalize simuation - openmc.capi.simulation_finalize() + openmc.lib.simulation_finalize() # Print out CMFD timing statistics self._write_cmfd_timing_stats() @@ -836,13 +836,13 @@ class CMFDRun(object): """ if filename is None: - batch_str_len = len(str(openmc.capi.settings.batches)) - batch_str = str(openmc.capi.current_batch()).zfill(batch_str_len) + batch_str_len = len(str(openmc.lib.settings.batches)) + batch_str = str(openmc.lib.current_batch()).zfill(batch_str_len) filename = 'statepoint.{}.h5'.format(batch_str) # Call C API statepoint_write to save source distribution with CMFD # feedback - openmc.capi.statepoint_write(filename=filename) + openmc.lib.statepoint_write(filename=filename) # Append CMFD data to statepoint file using h5py self._write_cmfd_statepoint(filename) @@ -856,10 +856,10 @@ class CMFDRun(object): Filename of statepoint """ - if openmc.capi.master(): + if openmc.lib.master(): with h5py.File(filename, 'a') as f: if 'cmfd' not in f: - if openmc.capi.settings.verbosity >= 5: + if openmc.lib.settings.verbosity >= 5: print(' Writing CMFD data to {}...'.format(filename)) sys.stdout.flush() cmfd_group = f.create_group("cmfd") @@ -922,7 +922,7 @@ class CMFDRun(object): args = temp_loss.indptr, len(temp_loss.indptr), \ temp_loss.indices, len(temp_loss.indices), n, \ self._spectral, self._indices, coremap - return openmc.capi._dll.openmc_initialize_linsolver(*args) + return openmc.lib._dll.openmc_initialize_linsolver(*args) def _write_cmfd_output(self): """Write CMFD output to buffer at the end of each batch""" @@ -948,7 +948,7 @@ class CMFDRun(object): def _write_cmfd_timing_stats(self): """Write CMFD timing stats to buffer after finalizing simulation""" - if openmc.capi.master(): + if openmc.lib.master(): outstr = ("=====================> " "CMFD TIMING STATISTICS <====================\n\n" " Time in CMFD = {:.5E} seconds\n" @@ -961,7 +961,7 @@ class CMFDRun(object): def _configure_cmfd(self): """Initialize CMFD parameters and set CMFD input variables""" # Check if restarting simulation from statepoint file - if not openmc.capi.settings.restart_run: + if not openmc.lib.settings.restart_run: # Read in cmfd input defined in Python self._read_cmfd_input() @@ -990,13 +990,13 @@ class CMFDRun(object): else: # Reset CMFD parameters from statepoint file - path_statepoint = openmc.capi.settings.path_statepoint + path_statepoint = openmc.lib.settings.path_statepoint self._reset_cmfd(path_statepoint) def _read_cmfd_input(self): """Sets values of additional instance variables based on user input""" # Print message to user and flush output to stdout - if openmc.capi.settings.verbosity >= 7 and openmc.capi.master(): + if openmc.lib.settings.verbosity >= 7 and openmc.lib.master(): print(' Configuring CMFD parameters for simulation') sys.stdout.flush() @@ -1010,7 +1010,7 @@ class CMFDRun(object): self._indices[i] = n # Check if in continuous energy mode - if not openmc.capi.settings.run_CE: + if not openmc.lib.settings.run_CE: raise OpenMCError('CMFD must be run in continuous energy mode') # Set number of energy groups @@ -1065,8 +1065,8 @@ class CMFDRun(object): 'file {}'.format(filename)) else: # Overwrite CMFD values from statepoint - if (openmc.capi.master() and - openmc.capi.settings.verbosity >= 5): + if (openmc.lib.master() and + openmc.lib.settings.verbosity >= 5): print(' Loading CMFD data from {}...'.format(filename)) sys.stdout.flush() cmfd_group = f['cmfd'] @@ -1126,7 +1126,7 @@ class CMFDRun(object): # Allocate dimensions for each mesh cell self._hxyz = np.zeros((nx, ny, nz, 3)) - self._hxyz[:] = openmc.capi.meshes[self._mesh_id].width + self._hxyz[:] = openmc.lib.meshes[self._mesh_id].width # Allocate flux, cross sections and diffusion coefficient self._flux = np.zeros((nx, ny, nz, ng)) @@ -1167,7 +1167,7 @@ class CMFDRun(object): """Handles CMFD options at the beginning of each batch""" # Get current batch through C API # Add 1 as next_batch has not been called yet - current_batch = openmc.capi.current_batch() + 1 + current_batch = openmc.lib.current_batch() + 1 # Check to activate CMFD diffusion and possible feedback # Check to activate CMFD tallies @@ -1182,7 +1182,7 @@ class CMFDRun(object): def _execute_cmfd(self): """Runs CMFD calculation on master node""" # Run CMFD on single processor on master - if openmc.capi.master(): + if openmc.lib.master(): # Start CMFD timer time_start_cmfd = time.time() @@ -1196,7 +1196,7 @@ class CMFDRun(object): self._k_cmfd.append(self._keff) # Check to perform adjoint on last batch - if (openmc.capi.current_batch() == openmc.capi.settings.batches + if (openmc.lib.current_batch() == openmc.lib.settings.batches and self._run_adjoint): self._cmfd_solver_execute(adjoint=True) @@ -1207,20 +1207,20 @@ class CMFDRun(object): self._cmfd_reweight(True) # Stop CMFD timer - if openmc.capi.master(): + if openmc.lib.master(): time_stop_cmfd = time.time() self._time_cmfd += time_stop_cmfd - time_start_cmfd def _cmfd_tally_reset(self): """Resets all CMFD tallies in memory""" # Print message - if (openmc.capi.settings.verbosity >= 6 and openmc.capi.master() and + if (openmc.lib.settings.verbosity >= 6 and openmc.lib.master() and not self._reset_every): print(' CMFD tallies reset') sys.stdout.flush() # Reset CMFD tallies - tallies = openmc.capi.tallies + tallies = openmc.lib.tallies for tally_id in self._tally_ids: tallies[tally_id].reset() @@ -1407,7 +1407,7 @@ class CMFDRun(object): self._cmfd_src = cmfd_src / np.sum(cmfd_src) # Compute entropy - if openmc.capi.settings.entropy_on: + if openmc.lib.settings.entropy_on: # Compute source times log_2(source) source = self._cmfd_src[self._cmfd_src > 0] \ * np.log(self._cmfd_src[self._cmfd_src > 0])/np.log(2) @@ -1441,12 +1441,12 @@ class CMFDRun(object): outside = self._count_bank_sites() # Check and raise error if source sites exist outside of CMFD mesh - if openmc.capi.master() and outside: + if openmc.lib.master() and outside: raise OpenMCError('Source sites outside of the CMFD mesh') # Have master compute weight factors, ignore any zeros in # sourcecounts or cmfd_src - if openmc.capi.master(): + if openmc.lib.master(): # Compute normalization factor norm = np.sum(self._sourcecounts) / np.sum(self._cmfd_src) @@ -1471,7 +1471,7 @@ class CMFDRun(object): dtype=np.float32)) if (not self._feedback - or openmc.capi.current_batch() < self._feedback_begin): + or openmc.lib.current_batch() < self._feedback_begin): return # Broadcast weight factors to all procs @@ -1479,13 +1479,13 @@ class CMFDRun(object): self._weightfactors = self._intracomm.bcast( self._weightfactors) - m = openmc.capi.meshes[self._mesh_id] + m = openmc.lib.meshes[self._mesh_id] energy = self._egrid ng = self._indices[3] # Get locations and energies of all particles in source bank - source_xyz = openmc.capi.source_bank()['r'] - source_energies = openmc.capi.source_bank()['E'] + source_xyz = openmc.lib.source_bank()['r'] + source_energies = openmc.lib.source_bank()['E'] # Convert xyz location to the CMFD mesh index mesh_ijk = np.floor((source_xyz-m.lower_left)/m.width).astype(int) @@ -1503,13 +1503,13 @@ class CMFDRun(object): # Determine weight factor of each particle based on its mesh index # and energy bin and updates its weight - openmc.capi.source_bank()['wgt'] *= self._weightfactors[ + openmc.lib.source_bank()['wgt'] *= self._weightfactors[ mesh_ijk[:,0], mesh_ijk[:,1], mesh_ijk[:,2], energy_bins] - if openmc.capi.master() and np.any(source_energies < energy[0]): + if openmc.lib.master() and np.any(source_energies < energy[0]): print(' WARNING: Source pt below energy grid') sys.stdout.flush() - if openmc.capi.master() and np.any(source_energies > energy[-1]): + if openmc.lib.master() and np.any(source_energies > energy[-1]): print(' WARNING: Source pt above energy grid') sys.stdout.flush() @@ -1523,8 +1523,8 @@ class CMFDRun(object): """ # Initialize variables - m = openmc.capi.meshes[self._mesh_id] - bank = openmc.capi.source_bank() + m = openmc.lib.meshes[self._mesh_id] + bank = openmc.lib.source_bank() energy = self._egrid sites_outside = np.zeros(1, dtype=bool) nxnynz = np.prod(self._indices[0:3]) @@ -1535,8 +1535,8 @@ class CMFDRun(object): count = np.zeros(self._sourcecounts.shape) # Get location and energy of each particle in source bank - source_xyz = openmc.capi.source_bank()['r'] - source_energies = openmc.capi.source_bank()['E'] + source_xyz = openmc.lib.source_bank()['r'] + source_energies = openmc.lib.source_bank()['E'] # Convert xyz location to mesh index and ravel index to scalar mesh_locations = np.floor((source_xyz - m.lower_left) / m.width) @@ -1760,7 +1760,7 @@ class CMFDRun(object): s_o = np.zeros((n,)) # Set initial guess - k_n = openmc.capi.keff()[0] + k_n = openmc.lib.keff()[0] k_o = k_n dw = self._w_shift k_s = k_o + dw @@ -1791,7 +1791,7 @@ class CMFDRun(object): s_o /= k_lo # Compute new flux with C++ solver - innerits = openmc.capi._dll.openmc_run_linsolver(loss.data, s_o, + innerits = openmc.lib._dll.openmc_run_linsolver(loss.data, s_o, phi_n, toli) # Compute new source vector @@ -1863,7 +1863,7 @@ class CMFDRun(object): iconv = kerr < self._cmfd_ktol and serr < self._stol # Print out to user - if self._power_monitor and openmc.capi.master(): + if self._power_monitor and openmc.lib.master(): str1 = ' {:d}:'.format(iter) str2 = 'k-eff: {:0.8f}'.format(k_n) str3 = 'k-error: {:.5E}'.format(kerr) @@ -1904,7 +1904,7 @@ class CMFDRun(object): """ # Update window size for expanding window if necessary - num_cmfd_batches = openmc.capi.current_batch() - self._tally_begin + 1 + num_cmfd_batches = openmc.lib.current_batch() - self._tally_begin + 1 if (self._window_type == 'expanding' and num_cmfd_batches == self._window_size * 2): self._window_size *= 2 @@ -1925,7 +1925,7 @@ class CMFDRun(object): nx, ny, nz, ng = self._indices # Get tallies in-memory - tallies = openmc.capi.tallies + tallies = openmc.lib.tallies # Ravel coremap as 1d array similar to how tally data is arranged coremap = np.ravel(self._coremap.swapaxes(0, 2)) @@ -2181,7 +2181,7 @@ class CMFDRun(object): num_accel = self._mat_dim # Get openmc k-effective - keff = openmc.capi.keff()[0] + keff = openmc.lib.keff()[0] # Define leakage in each mesh cell and energy group leakage = (((self._current[:,:,:,_CURRENTS['out_right'],:] - @@ -2973,7 +2973,7 @@ class CMFDRun(object): def _create_cmfd_tally(self): """Creates all tallies in-memory that are used to solve CMFD problem""" # Create Mesh object based on CMFDMesh, stored internally - cmfd_mesh = openmc.capi.RegularMesh() + cmfd_mesh = openmc.lib.RegularMesh() # Store id of mesh object self._mesh_id = cmfd_mesh.id # Set dimension and parameters of mesh object @@ -2983,29 +2983,29 @@ class CMFDRun(object): width=self._mesh.width) # Create mesh Filter object, stored internally - mesh_filter = openmc.capi.MeshFilter() + mesh_filter = openmc.lib.MeshFilter() # Set mesh for Mesh Filter mesh_filter.mesh = cmfd_mesh # Set up energy filters, if applicable if self._energy_filters: # Create Energy Filter object, stored internally - energy_filter = openmc.capi.EnergyFilter() + energy_filter = openmc.lib.EnergyFilter() # Set bins for Energy Filter energy_filter.bins = self._egrid # Create Energy Out Filter object, stored internally - energyout_filter = openmc.capi.EnergyoutFilter() + energyout_filter = openmc.lib.EnergyoutFilter() # Set bins for Energy Filter energyout_filter.bins = self._egrid # Create Mesh Surface Filter object, stored internally - meshsurface_filter = openmc.capi.MeshSurfaceFilter() + meshsurface_filter = openmc.lib.MeshSurfaceFilter() # Set mesh for Mesh Surface Filter meshsurface_filter.mesh = cmfd_mesh # Create Legendre Filter object, stored internally - legendre_filter = openmc.capi.LegendreFilter() + legendre_filter = openmc.lib.LegendreFilter() # Set order for Legendre Filter legendre_filter.order = 1 @@ -3013,7 +3013,7 @@ class CMFDRun(object): n_tallies = 4 self._tally_ids = [] for i in range(n_tallies): - cmfd_tally = openmc.capi.Tally() + cmfd_tally = openmc.lib.Tally() # Set nuclide bins cmfd_tally.nuclides = ['total'] self._tally_ids.append(cmfd_tally.id) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index fb638e5c21..b1c854da6c 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -17,7 +17,7 @@ from numpy import nonzero, empty, asarray from uncertainties import ufloat from openmc.data import DataLibrary, JOULE_PER_EV -from openmc.capi import MaterialFilter, Tally +from openmc.lib import MaterialFilter, Tally from openmc.checkvalue import check_type, check_greater_than from .results import Results from .chain import Chain @@ -438,7 +438,7 @@ class FissionYieldHelper(ABC): Parameters ---------- materials : iterable of C-API materials - Materials to be used in :class:`openmc.capi.MaterialFilter` + Materials to be used in :class:`openmc.lib.MaterialFilter` mat_indexes : iterable of int Indices of tallied materials that will have their fission yields computed by this helper. Necessary as the @@ -520,8 +520,8 @@ class TalliedFissionYieldHelper(FissionYieldHelper): Parameters ---------- - materials : iterable of :class:`openmc.capi.Material` - Materials to be used in :class:`openmc.capi.MaterialFilter` + materials : iterable of :class:`openmc.lib.Material` + Materials to be used in :class:`openmc.lib.MaterialFilter` mat_indexes : iterable of int Indices of tallied materials that will have their fission yields computed by this helper. Necessary as the diff --git a/openmc/deplete/helpers.py b/openmc/deplete/helpers.py index 56644a3bba..5d71334673 100644 --- a/openmc/deplete/helpers.py +++ b/openmc/deplete/helpers.py @@ -9,7 +9,7 @@ import bisect from numpy import dot, zeros, newaxis from openmc.checkvalue import check_type, check_greater_than -from openmc.capi import ( +from openmc.lib import ( Tally, MaterialFilter, EnergyFilter, EnergyFunctionFilter) from .abc import ( ReactionRateHelper, EnergyHelper, FissionYieldHelper, @@ -44,7 +44,7 @@ class DirectReactionRateHelper(ReactionRateHelper): def generate_tallies(self, materials, scores): """Produce one-group reaction rate tally - Uses the :mod:`openmc.capi` to generate a tally + Uses the :mod:`openmc.lib` to generate a tally of relevant reactions across all burnable materials. Parameters @@ -362,13 +362,13 @@ class FissionYieldCutoffHelper(TalliedFissionYieldHelper): def generate_tallies(self, materials, mat_indexes): """Use C API to produce a fission rate tally in burnable materials - Include a :class:`openmc.capi.EnergyFilter` to tally fission rates + Include a :class:`openmc.lib.EnergyFilter` to tally fission rates above and below cutoff energy. Parameters ---------- - materials : iterable of :class:`openmc.capi.Material` - Materials to be used in :class:`openmc.capi.MaterialFilter` + materials : iterable of :class:`openmc.lib.Material` + Materials to be used in :class:`openmc.lib.MaterialFilter` mat_indexes : iterable of int Indices of tallied materials that will have their fission yields computed by this helper. Necessary as the @@ -490,8 +490,8 @@ class AveragedFissionYieldHelper(TalliedFissionYieldHelper): Parameters ---------- - materials : iterable of :class:`openmc.capi.Material` - Materials to be used in :class:`openmc.capi.MaterialFilter` + materials : iterable of :class:`openmc.lib.Material` + Materials to be used in :class:`openmc.lib.MaterialFilter` mat_indexes : iterable of int Indices of tallied materials that will have their fission yields computed by this helper. Necessary as the diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index 01bf92dcc9..86dbeb7550 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -19,7 +19,7 @@ import numpy as np from uncertainties import ufloat import openmc -import openmc.capi +import openmc.lib from . import comm from .abc import TransportOperator, OperatorResult from .atom_number import AtomNumber @@ -245,8 +245,8 @@ class Operator(TransportOperator): self._yield_helper.update_tally_nuclides(nuclides) # Run OpenMC - openmc.capi.reset() - openmc.capi.run() + openmc.lib.reset() + openmc.lib.run() time_openmc = time.time() @@ -264,7 +264,7 @@ class Operator(TransportOperator): step : int Current depletion step including restarts """ - openmc.capi.statepoint_write( + openmc.lib.statepoint_write( "openmc_simulation_n{}.h5".format(step), write_source=False) @@ -438,10 +438,10 @@ class Operator(TransportOperator): # Initialize OpenMC library comm.barrier() - openmc.capi.init(intracomm=comm) + openmc.lib.init(intracomm=comm) # Generate tallies in memory - materials = [openmc.capi.materials[int(i)] + materials = [openmc.lib.materials[int(i)] for i in self.burnable_mats] self._rate_helper.generate_tallies(materials, self.chain.reactions) self._energy_helper.prepare( @@ -456,7 +456,7 @@ class Operator(TransportOperator): def finalize(self): """Finalize a depletion simulation and release resources.""" - openmc.capi.finalize() + openmc.lib.finalize() def _update_materials(self): """Updates material compositions in OpenMC on all processes.""" @@ -491,7 +491,7 @@ class Operator(TransportOperator): number_i[mat, nuc] = 0.0 # Update densities on C API side - mat_internal = openmc.capi.materials[int(mat)] + mat_internal = openmc.lib.materials[int(mat)] mat_internal.set_densities(nuclides, densities) #TODO Update densities on the Python side, otherwise the @@ -581,7 +581,7 @@ class Operator(TransportOperator): rates.fill(0.0) # Get k and uncertainty - k_combined = ufloat(*openmc.capi.keff()) + k_combined = ufloat(*openmc.lib.keff()) # Extract tally bins nuclides = self._rate_helper.nuclides diff --git a/openmc/capi/__init__.py b/openmc/lib/__init__.py similarity index 69% rename from openmc/capi/__init__.py rename to openmc/lib/__init__.py index 5ca867d67e..92fd1730d3 100644 --- a/openmc/capi/__init__.py +++ b/openmc/lib/__init__.py @@ -1,14 +1,14 @@ """ -This module provides bindings to C functions defined by OpenMC shared library. -When the :mod:`openmc` package is imported, the OpenMC shared library is -automatically loaded. Calls to the OpenMC library can then be via functions or -objects in the :mod:`openmc.capi` subpackage, for example: +This module provides bindings to C/C++ functions defined by OpenMC shared +library. When the :mod:`openmc.lib` package is imported, the OpenMC shared +library is automatically loaded. Calls to the OpenMC library can then be via +functions or objects in :mod:`openmc.lib`, for example: .. code-block:: python - openmc.capi.init() - openmc.capi.run() - openmc.capi.finalize() + openmc.lib.init() + openmc.lib.run() + openmc.lib.finalize() """ @@ -33,7 +33,7 @@ if os.environ.get('READTHEDOCS', None) != 'True': else: # For documentation builds, we don't actually have the shared library # available. Instead, we create a mock object so that when the modules - # within the openmc.capi package try to configure arguments and return + # within the openmc.lib package try to configure arguments and return # values for symbols, no errors occur from unittest.mock import Mock _dll = Mock() diff --git a/openmc/capi/cell.py b/openmc/lib/cell.py similarity index 99% rename from openmc/capi/cell.py rename to openmc/lib/cell.py index 784ebd087b..6ed4bca1ea 100644 --- a/openmc/capi/cell.py +++ b/openmc/lib/cell.py @@ -63,7 +63,7 @@ class Cell(_FortranObjectWithID): This class exposes a cell that is stored internally in the OpenMC library. To obtain a view of a cell with a given ID, use the - :data:`openmc.capi.cells` mapping. + :data:`openmc.lib.cells` mapping. Parameters ---------- diff --git a/openmc/capi/core.py b/openmc/lib/core.py similarity index 94% rename from openmc/capi/core.py rename to openmc/lib/core.py index a470f0665b..0bfdb457b4 100644 --- a/openmc/capi/core.py +++ b/openmc/lib/core.py @@ -11,7 +11,7 @@ from numpy.ctypeslib import as_array from openmc.exceptions import AllocationError from . import _dll from .error import _error_handler -import openmc.capi +import openmc.lib class _Bank(Structure): @@ -127,7 +127,7 @@ def find_cell(xyz): Returns ------- - openmc.capi.Cell + openmc.lib.Cell Cell containing the point int If the cell at the given point is repeated in the geometry, this @@ -137,7 +137,7 @@ def find_cell(xyz): index = c_int32() instance = c_int32() _dll.openmc_find_cell((c_double*3)(*xyz), index, instance) - return openmc.capi.Cell(index=index.value), instance.value + return openmc.lib.Cell(index=index.value), instance.value def find_material(xyz): @@ -150,7 +150,7 @@ def find_material(xyz): Returns ------- - openmc.capi.Material or None + openmc.lib.Material or None Material containing the point, or None is no material is found """ @@ -158,8 +158,8 @@ def find_material(xyz): instance = c_int32() _dll.openmc_find_cell((c_double*3)(*xyz), index, instance) - mats = openmc.capi.Cell(index=index.value).fill - if isinstance(mats, (openmc.capi.Material, type(None))): + mats = openmc.lib.Cell(index=index.value).fill + if isinstance(mats, (openmc.lib.Material, type(None))): return mats else: return mats[instance.value] @@ -225,21 +225,21 @@ def iter_batches(): This function returns a generator-iterator that allows Python code to be run between batches in an OpenMC simulation. It should be used in conjunction - with :func:`openmc.capi.simulation_init` and - :func:`openmc.capi.simulation_finalize`. For example: + with :func:`openmc.lib.simulation_init` and + :func:`openmc.lib.simulation_finalize`. For example: .. code-block:: Python - with openmc.capi.run_in_memory(): - openmc.capi.simulation_init() - for _ in openmc.capi.iter_batches(): + with openmc.lib.run_in_memory(): + openmc.lib.simulation_init() + for _ in openmc.lib.iter_batches(): # Look at convergence of tallies, for example ... - openmc.capi.simulation_finalize() + openmc.lib.simulation_finalize() See Also -------- - openmc.capi.next_batch + openmc.lib.next_batch """ while True: @@ -365,11 +365,11 @@ def run_in_memory(**kwargs): block, all memory that was allocated during the block is freed. For example:: - with openmc.capi.run_in_memory(): + with openmc.lib.run_in_memory(): for i in range(n_iters): - openmc.capi.reset() + openmc.lib.reset() do_stuff() - openmc.capi.run() + openmc.lib.run() Parameters ---------- diff --git a/openmc/capi/error.py b/openmc/lib/error.py similarity index 100% rename from openmc/capi/error.py rename to openmc/lib/error.py diff --git a/openmc/capi/filter.py b/openmc/lib/filter.py similarity index 100% rename from openmc/capi/filter.py rename to openmc/lib/filter.py diff --git a/openmc/capi/material.py b/openmc/lib/material.py similarity index 99% rename from openmc/capi/material.py rename to openmc/lib/material.py index f0ecac7610..0fbce72458 100644 --- a/openmc/capi/material.py +++ b/openmc/lib/material.py @@ -66,7 +66,7 @@ class Material(_FortranObjectWithID): This class exposes a material that is stored internally in the OpenMC library. To obtain a view of a material with a given ID, use the - :data:`openmc.capi.materials` mapping. + :data:`openmc.lib.materials` mapping. Parameters ---------- diff --git a/openmc/capi/math.py b/openmc/lib/math.py similarity index 100% rename from openmc/capi/math.py rename to openmc/lib/math.py diff --git a/openmc/capi/mesh.py b/openmc/lib/mesh.py similarity index 99% rename from openmc/capi/mesh.py rename to openmc/lib/mesh.py index a1161f48f3..bf51bdfe98 100644 --- a/openmc/capi/mesh.py +++ b/openmc/lib/mesh.py @@ -50,7 +50,7 @@ class RegularMesh(_FortranObjectWithID): This class exposes a mesh that is stored internally in the OpenMC library. To obtain a view of a mesh with a given ID, use the - :data:`openmc.capi.meshes` mapping. + :data:`openmc.lib.meshes` mapping. Parameters ---------- diff --git a/openmc/capi/nuclide.py b/openmc/lib/nuclide.py similarity index 98% rename from openmc/capi/nuclide.py rename to openmc/lib/nuclide.py index 4a21a6d2b4..81ef7e648e 100644 --- a/openmc/capi/nuclide.py +++ b/openmc/lib/nuclide.py @@ -43,7 +43,7 @@ class Nuclide(_FortranObject): This class exposes a nuclide that is stored internally in the OpenMC solver. To obtain a view of a nuclide with a given name, use the - :data:`openmc.capi.nuclides` mapping. + :data:`openmc.lib.nuclides` mapping. Parameters ---------- diff --git a/openmc/capi/plot.py b/openmc/lib/plot.py similarity index 97% rename from openmc/capi/plot.py rename to openmc/lib/plot.py index c4bff4cb3f..c51000e0de 100644 --- a/openmc/capi/plot.py +++ b/openmc/lib/plot.py @@ -52,9 +52,9 @@ class _PlotBase(Structure): C-Type Attributes ----------------- - origin : openmc.capi.plot._Position + origin : openmc.lib.plot._Position A position defining the origin of the plot. - width_ : openmc.capi.plot._Position + width_ : openmc.lib.plot._Position The width of the plot along the x, y, and z axes, respectively basis_ : c_int The axes basis of the plot view. @@ -222,7 +222,7 @@ def id_map(plot): Parameters ---------- - plot : openmc.capi.plot._PlotBase + plot : openmc.lib.plot._PlotBase Object describing the slice of the model to be generated Returns @@ -250,7 +250,7 @@ def property_map(plot): Parameters ---------- - plot : openmc.capi.plot._PlotBase + plot : openmc.lib.plot._PlotBase Object describing the slice of the model to be generated Returns diff --git a/openmc/capi/settings.py b/openmc/lib/settings.py similarity index 100% rename from openmc/capi/settings.py rename to openmc/lib/settings.py diff --git a/openmc/capi/tally.py b/openmc/lib/tally.py similarity index 99% rename from openmc/capi/tally.py rename to openmc/lib/tally.py index 88c14e4494..0001b2c4b6 100644 --- a/openmc/capi/tally.py +++ b/openmc/lib/tally.py @@ -143,7 +143,7 @@ class Tally(_FortranObjectWithID): This class exposes a tally that is stored internally in the OpenMC library. To obtain a view of a tally with a given ID, use the - :data:`openmc.capi.tallies` mapping. + :data:`openmc.lib.tallies` mapping. Parameters ---------- diff --git a/openmc/model/model.py b/openmc/model/model.py index 2f515ca78f..33a38ef224 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -147,7 +147,7 @@ class Model(object): """ # Import the depletion module. This is done here rather than the module - # header to delay importing openmc.capi (through openmc.deplete) which + # header to delay importing openmc.lib (through openmc.deplete) which # can be tough to install properly. import openmc.deplete as dep diff --git a/openmc/polynomial.py b/openmc/polynomial.py index ac1ad675d6..abcfd8057e 100644 --- a/openmc/polynomial.py +++ b/openmc/polynomial.py @@ -73,9 +73,9 @@ class ZernikeRadial(Polynomial): return self._order def __call__(self, r): - import openmc.capi as capi + import openmc.lib as lib if isinstance(r, Iterable): - return [np.sum(self._norm_coef * capi.calc_zn_rad(self.order, r_i)) + return [np.sum(self._norm_coef * lib.calc_zn_rad(self.order, r_i)) for r_i in r] else: - return np.sum(self._norm_coef * capi.calc_zn_rad(self.order, r)) + return np.sum(self._norm_coef * lib.calc_zn_rad(self.order, r)) diff --git a/setup.py b/setup.py index 712244efb4..aff7834d7b 100755 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ kwargs = { # Data files and librarries 'package_data': { - 'openmc.capi': ['libopenmc.{}'.format(suffix)], + 'openmc.lib': ['libopenmc.{}'.format(suffix)], 'openmc.data': ['mass16.txt', 'BREMX.DAT', '*.h5'] }, diff --git a/tests/regression_tests/complex_cell/test.py b/tests/regression_tests/complex_cell/test.py index b43ccd72f9..77cbd6cb7d 100755 --- a/tests/regression_tests/complex_cell/test.py +++ b/tests/regression_tests/complex_cell/test.py @@ -1,8 +1,5 @@ from tests.testing_harness import TestHarness -import sys - -import openmc.capi def test_complex_cell(): harness = TestHarness('statepoint.10.h5') diff --git a/tests/regression_tests/dagmc/legacy/test.py b/tests/regression_tests/dagmc/legacy/test.py index db062d36eb..963e73226e 100644 --- a/tests/regression_tests/dagmc/legacy/test.py +++ b/tests/regression_tests/dagmc/legacy/test.py @@ -1,11 +1,11 @@ import openmc -import openmc.capi +import openmc.lib import pytest from tests.testing_harness import PyAPITestHarness pytestmark = pytest.mark.skipif( - not openmc.capi._dagmc_enabled(), + not openmc.lib._dagmc_enabled(), reason="DAGMC CAD geometry is not enabled.") def test_dagmc(): diff --git a/tests/regression_tests/dagmc/refl/test.py b/tests/regression_tests/dagmc/refl/test.py index 93104f1d0d..c451b61250 100644 --- a/tests/regression_tests/dagmc/refl/test.py +++ b/tests/regression_tests/dagmc/refl/test.py @@ -1,12 +1,12 @@ import openmc -import openmc.capi +import openmc.lib from openmc.stats import Box import pytest from tests.testing_harness import PyAPITestHarness pytestmark = pytest.mark.skipif( - not openmc.capi._dagmc_enabled(), + not openmc.lib._dagmc_enabled(), reason="DAGMC CAD geometry is not enabled.") class UWUWTest(PyAPITestHarness): diff --git a/tests/regression_tests/dagmc/uwuw/test.py b/tests/regression_tests/dagmc/uwuw/test.py index 885b83766f..b4391d8e77 100644 --- a/tests/regression_tests/dagmc/uwuw/test.py +++ b/tests/regression_tests/dagmc/uwuw/test.py @@ -1,12 +1,12 @@ import openmc -import openmc.capi +import openmc.lib from openmc.stats import Box import pytest from tests.testing_harness import PyAPITestHarness pytestmark = pytest.mark.skipif( - not openmc.capi._dagmc_enabled(), + not openmc.lib._dagmc_enabled(), reason="DAGMC CAD geometry is not enabled.") class UWUWTest(PyAPITestHarness): diff --git a/tests/unit_tests/dagmc/test.py b/tests/unit_tests/dagmc/test.py index bfe054dd82..f7b2844f6a 100644 --- a/tests/unit_tests/dagmc/test.py +++ b/tests/unit_tests/dagmc/test.py @@ -4,12 +4,12 @@ import numpy as np import pytest import openmc -import openmc.capi +import openmc.lib from tests import cdtemp pytestmark = pytest.mark.skipif( - not openmc.capi._dagmc_enabled(), + not openmc.lib._dagmc_enabled(), reason="DAGMC CAD geometry is not enabled.") @@ -60,15 +60,15 @@ def dagmc_model(request): with cdtemp(): shutil.copyfile(dagmc_file, "./dagmc.h5m") model.export_to_xml() - openmc.capi.init() + openmc.lib.init() yield - openmc.capi.finalize() + openmc.lib.finalize() @pytest.mark.parametrize("cell_id,exp_temp", ((1, 320.0), # assigned by material (2, 300.0), # assigned in dagmc file (3, 293.6))) # assigned by default def test_dagmc_temperatures(cell_id, exp_temp): - cell = openmc.capi.cells[cell_id] + cell = openmc.lib.cells[cell_id] assert np.isclose(cell.get_temperature(), exp_temp) diff --git a/tests/unit_tests/test_capi.py b/tests/unit_tests/test_capi.py index c8ad0907c5..2f39f0b0d3 100644 --- a/tests/unit_tests/test_capi.py +++ b/tests/unit_tests/test_capi.py @@ -5,7 +5,7 @@ import numpy as np import pytest import openmc import openmc.exceptions as exc -import openmc.capi +import openmc.lib from tests import cdtemp @@ -51,42 +51,42 @@ def pincell_model(): @pytest.fixture(scope='module') def capi_init(pincell_model, mpi_intracomm): - openmc.capi.init(intracomm=mpi_intracomm) + openmc.lib.init(intracomm=mpi_intracomm) yield - openmc.capi.finalize() + openmc.lib.finalize() @pytest.fixture(scope='module') def capi_simulation_init(capi_init): - openmc.capi.simulation_init() + openmc.lib.simulation_init() yield @pytest.fixture(scope='module') def capi_run(capi_simulation_init): - openmc.capi.run() + openmc.lib.run() def test_cell_mapping(capi_init): - cells = openmc.capi.cells + cells = openmc.lib.cells assert isinstance(cells, Mapping) assert len(cells) == 3 for cell_id, cell in cells.items(): - assert isinstance(cell, openmc.capi.Cell) + assert isinstance(cell, openmc.lib.Cell) assert cell_id == cell.id def test_cell(capi_init): - cell = openmc.capi.cells[1] - assert isinstance(cell.fill, openmc.capi.Material) - cell.fill = openmc.capi.materials[1] + cell = openmc.lib.cells[1] + assert isinstance(cell.fill, openmc.lib.Material) + cell.fill = openmc.lib.materials[1] assert str(cell) == 'Cell[0]' assert cell.name == "Fuel" cell.name = "Not fuel" assert cell.name == "Not fuel" def test_cell_temperature(capi_init): - cell = openmc.capi.cells[1] + cell = openmc.lib.cells[1] cell.set_temperature(100.0, 0) assert cell.get_temperature(0) == 100.0 cell.set_temperature(200) @@ -95,23 +95,23 @@ def test_cell_temperature(capi_init): def test_new_cell(capi_init): with pytest.raises(exc.AllocationError): - openmc.capi.Cell(1) - new_cell = openmc.capi.Cell() - new_cell_with_id = openmc.capi.Cell(10) - assert len(openmc.capi.cells) == 5 + openmc.lib.Cell(1) + new_cell = openmc.lib.Cell() + new_cell_with_id = openmc.lib.Cell(10) + assert len(openmc.lib.cells) == 5 def test_material_mapping(capi_init): - mats = openmc.capi.materials + mats = openmc.lib.materials assert isinstance(mats, Mapping) assert len(mats) == 3 for mat_id, mat in mats.items(): - assert isinstance(mat, openmc.capi.Material) + assert isinstance(mat, openmc.lib.Material) assert mat_id == mat.id def test_material(capi_init): - m = openmc.capi.materials[3] + m = openmc.lib.materials[3] assert m.nuclides == ['H1', 'O16', 'B10', 'B11'] old_dens = m.densities @@ -137,7 +137,7 @@ def test_material(capi_init): assert m.name == "Not hot borated water" def test_material_add_nuclide(capi_init): - m = openmc.capi.materials[3] + m = openmc.lib.materials[3] m.add_nuclide('Xe135', 1e-12) assert m.nuclides[-1] == 'Xe135' assert m.densities[-1] == 1e-12 @@ -145,23 +145,23 @@ def test_material_add_nuclide(capi_init): def test_new_material(capi_init): with pytest.raises(exc.AllocationError): - openmc.capi.Material(1) - new_mat = openmc.capi.Material() - new_mat_with_id = openmc.capi.Material(10) - assert len(openmc.capi.materials) == 5 + openmc.lib.Material(1) + new_mat = openmc.lib.Material() + new_mat_with_id = openmc.lib.Material(10) + assert len(openmc.lib.materials) == 5 def test_nuclide_mapping(capi_init): - nucs = openmc.capi.nuclides + nucs = openmc.lib.nuclides assert isinstance(nucs, Mapping) assert len(nucs) == 13 for name, nuc in nucs.items(): - assert isinstance(nuc, openmc.capi.Nuclide) + assert isinstance(nuc, openmc.lib.Nuclide) assert name == nuc.name def test_settings(capi_init): - settings = openmc.capi.settings + settings = openmc.lib.settings assert settings.batches == 10 settings.batches = 10 assert settings.inactive == 5 @@ -176,17 +176,17 @@ def test_settings(capi_init): def test_tally_mapping(capi_init): - tallies = openmc.capi.tallies + tallies = openmc.lib.tallies assert isinstance(tallies, Mapping) assert len(tallies) == 3 for tally_id, tally in tallies.items(): - assert isinstance(tally, openmc.capi.Tally) + assert isinstance(tally, openmc.lib.Tally) assert tally_id == tally.id def test_energy_function_filter(capi_init): """Test special __new__ and __init__ for EnergyFunctionFilter""" - efunc = openmc.capi.EnergyFunctionFilter([0.0, 1.0], [0.0, 2.0]) + efunc = openmc.lib.EnergyFunctionFilter([0.0, 1.0], [0.0, 2.0]) assert len(efunc.energy) == 2 assert (efunc.energy == [0.0, 1.0]).all() assert len(efunc.y) == 2 @@ -194,17 +194,17 @@ def test_energy_function_filter(capi_init): def test_tally(capi_init): - t = openmc.capi.tallies[1] + t = openmc.lib.tallies[1] assert t.type == 'volume' assert len(t.filters) == 2 - assert isinstance(t.filters[0], openmc.capi.MaterialFilter) - assert isinstance(t.filters[1], openmc.capi.EnergyFilter) + assert isinstance(t.filters[0], openmc.lib.MaterialFilter) + assert isinstance(t.filters[1], openmc.lib.EnergyFilter) # Create new filter and replace existing with pytest.raises(exc.AllocationError): - openmc.capi.MaterialFilter(uid=1) - mats = openmc.capi.materials - f = openmc.capi.MaterialFilter([mats[2], mats[1]]) + openmc.lib.MaterialFilter(uid=1) + mats = openmc.lib.materials + f = openmc.lib.MaterialFilter([mats[2], mats[1]]) assert f.bins[0] == mats[2] assert f.bins[1] == mats[1] t.filters = [f] @@ -221,17 +221,17 @@ def test_tally(capi_init): t.scores = new_scores assert t.scores == new_scores - t2 = openmc.capi.tallies[2] + t2 = openmc.lib.tallies[2] assert len(t2.filters) == 2 - assert isinstance(t2.filters[0], openmc.capi.ZernikeFilter) - assert isinstance(t2.filters[1], openmc.capi.CellFilter) + assert isinstance(t2.filters[0], openmc.lib.ZernikeFilter) + assert isinstance(t2.filters[1], openmc.lib.CellFilter) assert len(t2.filters[1].bins) == 3 assert t2.filters[0].order == 5 - t3 = openmc.capi.tallies[3] + t3 = openmc.lib.tallies[3] assert len(t3.filters) == 1 t3_f = t3.filters[0] - assert isinstance(t3_f, openmc.capi.EnergyFunctionFilter) + assert isinstance(t3_f, openmc.lib.EnergyFunctionFilter) assert len(t3_f.energy) == 2 assert len(t3_f.y) == 2 t3_f.set_data([0.0, 1.0, 2.0], [0.0, 1.0, 4.0]) @@ -241,144 +241,144 @@ def test_tally(capi_init): def test_new_tally(capi_init): with pytest.raises(exc.AllocationError): - openmc.capi.Material(1) - new_tally = openmc.capi.Tally() + openmc.lib.Material(1) + new_tally = openmc.lib.Tally() new_tally.scores = ['flux'] - new_tally_with_id = openmc.capi.Tally(10) + new_tally_with_id = openmc.lib.Tally(10) new_tally_with_id.scores = ['flux'] - assert len(openmc.capi.tallies) == 5 + assert len(openmc.lib.tallies) == 5 def test_tally_activate(capi_simulation_init): - t = openmc.capi.tallies[1] + t = openmc.lib.tallies[1] assert not t.active t.active = True assert t.active def test_tally_results(capi_run): - t = openmc.capi.tallies[1] + t = openmc.lib.tallies[1] assert t.num_realizations == 10 # t was made active in test_tally assert np.all(t.mean >= 0) nonzero = (t.mean > 0.0) assert np.all(t.std_dev[nonzero] >= 0) assert np.all(t.ci_width()[nonzero] >= 1.95*t.std_dev[nonzero]) - t2 = openmc.capi.tallies[2] + t2 = openmc.lib.tallies[2] n = 5 assert t2.mean.size == (n + 1) * (n + 2) // 2 * 3 # Number of Zernike coeffs * 3 cells def test_global_tallies(capi_run): - assert openmc.capi.num_realizations() == 5 - gt = openmc.capi.global_tallies() + assert openmc.lib.num_realizations() == 5 + gt = openmc.lib.global_tallies() for mean, std_dev in gt: assert mean >= 0 def test_statepoint(capi_run): - openmc.capi.statepoint_write('test_sp.h5') + openmc.lib.statepoint_write('test_sp.h5') assert os.path.exists('test_sp.h5') def test_source_bank(capi_run): - source = openmc.capi.source_bank() + source = openmc.lib.source_bank() assert np.all(source['E'] > 0.0) assert np.all(source['wgt'] == 1.0) assert np.allclose(np.linalg.norm(source['u'], axis=1), 1.0) def test_by_batch(capi_run): - openmc.capi.hard_reset() + openmc.lib.hard_reset() # Running next batch before simulation is initialized should raise an # exception with pytest.raises(exc.AllocationError): - openmc.capi.next_batch() + openmc.lib.next_batch() - openmc.capi.simulation_init() + openmc.lib.simulation_init() try: - for _ in openmc.capi.iter_batches(): + for _ in openmc.lib.iter_batches(): # Make sure we can get k-effective during inactive/active batches - mean, std_dev = openmc.capi.keff() + mean, std_dev = openmc.lib.keff() assert 0.0 < mean < 2.5 assert std_dev > 0.0 - assert openmc.capi.num_realizations() == 5 + assert openmc.lib.num_realizations() == 5 for i in range(3): - openmc.capi.next_batch() - assert openmc.capi.num_realizations() == 8 + openmc.lib.next_batch() + assert openmc.lib.num_realizations() == 8 finally: - openmc.capi.simulation_finalize() + openmc.lib.simulation_finalize() def test_reset(capi_run): # Init and run 10 batches. - openmc.capi.hard_reset() - openmc.capi.simulation_init() + openmc.lib.hard_reset() + openmc.lib.simulation_init() try: for i in range(10): - openmc.capi.next_batch() + openmc.lib.next_batch() # Make sure there are 5 realizations for the 5 active batches. - assert openmc.capi.num_realizations() == 5 - assert openmc.capi.tallies[2].num_realizations == 5 - _, keff_sd1 = openmc.capi.keff() - tally_sd1 = openmc.capi.tallies[2].std_dev[0] + assert openmc.lib.num_realizations() == 5 + assert openmc.lib.tallies[2].num_realizations == 5 + _, keff_sd1 = openmc.lib.keff() + tally_sd1 = openmc.lib.tallies[2].std_dev[0] # Reset and run 3 more batches. Check the number of realizations. - openmc.capi.reset() + openmc.lib.reset() for i in range(3): - openmc.capi.next_batch() - assert openmc.capi.num_realizations() == 3 - assert openmc.capi.tallies[2].num_realizations == 3 + openmc.lib.next_batch() + assert openmc.lib.num_realizations() == 3 + assert openmc.lib.tallies[2].num_realizations == 3 # Check the tally std devs to make sure results were cleared. - _, keff_sd2 = openmc.capi.keff() - tally_sd2 = openmc.capi.tallies[2].std_dev[0] + _, keff_sd2 = openmc.lib.keff() + tally_sd2 = openmc.lib.tallies[2].std_dev[0] assert keff_sd2 > keff_sd1 assert tally_sd2 > tally_sd1 finally: - openmc.capi.simulation_finalize() + openmc.lib.simulation_finalize() def test_reproduce_keff(capi_init): # Get k-effective after run - openmc.capi.hard_reset() - openmc.capi.run() - keff0 = openmc.capi.keff() + openmc.lib.hard_reset() + openmc.lib.run() + keff0 = openmc.lib.keff() # Reset, run again, and get k-effective again. they should match - openmc.capi.hard_reset() - openmc.capi.run() - keff1 = openmc.capi.keff() + openmc.lib.hard_reset() + openmc.lib.run() + keff1 = openmc.lib.keff() assert keff0 == pytest.approx(keff1) def test_find_cell(capi_init): - cell, instance = openmc.capi.find_cell((0., 0., 0.)) - assert cell is openmc.capi.cells[1] - cell, instance = openmc.capi.find_cell((0.4, 0., 0.)) - assert cell is openmc.capi.cells[2] + cell, instance = openmc.lib.find_cell((0., 0., 0.)) + assert cell is openmc.lib.cells[1] + cell, instance = openmc.lib.find_cell((0.4, 0., 0.)) + assert cell is openmc.lib.cells[2] with pytest.raises(exc.GeometryError): - openmc.capi.find_cell((100., 100., 100.)) + openmc.lib.find_cell((100., 100., 100.)) def test_find_material(capi_init): - mat = openmc.capi.find_material((0., 0., 0.)) - assert mat is openmc.capi.materials[1] - mat = openmc.capi.find_material((0.4, 0., 0.)) - assert mat is openmc.capi.materials[2] + mat = openmc.lib.find_material((0., 0., 0.)) + assert mat is openmc.lib.materials[1] + mat = openmc.lib.find_material((0.4, 0., 0.)) + assert mat is openmc.lib.materials[2] def test_mesh(capi_init): - mesh = openmc.capi.RegularMesh() + mesh = openmc.lib.RegularMesh() mesh.dimension = (2, 3, 4) assert mesh.dimension == (2, 3, 4) with pytest.raises(exc.AllocationError): - mesh2 = openmc.capi.RegularMesh(mesh.id) + mesh2 = openmc.lib.RegularMesh(mesh.id) # Make sure each combination of parameters works ll = (0., 0., 0.) @@ -394,47 +394,47 @@ def test_mesh(capi_init): assert mesh.upper_right == pytest.approx(ur) assert mesh.width == pytest.approx(width) - meshes = openmc.capi.meshes + meshes = openmc.lib.meshes assert isinstance(meshes, Mapping) assert len(meshes) == 1 for mesh_id, mesh in meshes.items(): - assert isinstance(mesh, openmc.capi.RegularMesh) + assert isinstance(mesh, openmc.lib.RegularMesh) assert mesh_id == mesh.id - mf = openmc.capi.MeshFilter(mesh) + mf = openmc.lib.MeshFilter(mesh) assert mf.mesh == mesh - msf = openmc.capi.MeshSurfaceFilter(mesh) + msf = openmc.lib.MeshSurfaceFilter(mesh) assert msf.mesh == mesh def test_restart(capi_init, mpi_intracomm): # Finalize and re-init to make internal state consistent with XML. - openmc.capi.hard_reset() - openmc.capi.finalize() - openmc.capi.init(intracomm=mpi_intracomm) - openmc.capi.simulation_init() + openmc.lib.hard_reset() + openmc.lib.finalize() + openmc.lib.init(intracomm=mpi_intracomm) + openmc.lib.simulation_init() # Run for 7 batches then write a statepoint. for i in range(7): - openmc.capi.next_batch() - openmc.capi.statepoint_write('restart_test.h5', True) + openmc.lib.next_batch() + openmc.lib.statepoint_write('restart_test.h5', True) # Run 3 more batches and copy the keff. for i in range(3): - openmc.capi.next_batch() - keff0 = openmc.capi.keff() + openmc.lib.next_batch() + keff0 = openmc.lib.keff() # Restart the simulation from the statepoint and the 3 remaining active batches. - openmc.capi.simulation_finalize() - openmc.capi.hard_reset() - openmc.capi.finalize() - openmc.capi.init(args=('-r', 'restart_test.h5')) - openmc.capi.simulation_init() + openmc.lib.simulation_finalize() + openmc.lib.hard_reset() + openmc.lib.finalize() + openmc.lib.init(args=('-r', 'restart_test.h5')) + openmc.lib.simulation_init() for i in range(3): - openmc.capi.next_batch() - keff1 = openmc.capi.keff() - openmc.capi.simulation_finalize() + openmc.lib.next_batch() + keff1 = openmc.lib.keff() + openmc.lib.simulation_finalize() # Compare the keff values. assert keff0 == pytest.approx(keff1) @@ -442,13 +442,13 @@ def test_restart(capi_init, mpi_intracomm): def test_load_nuclide(capi_init): # load multiple nuclides - openmc.capi.load_nuclide('H3') - assert 'H3' in openmc.capi.nuclides - openmc.capi.load_nuclide('Pu239') - assert 'Pu239' in openmc.capi.nuclides + openmc.lib.load_nuclide('H3') + assert 'H3' in openmc.lib.nuclides + openmc.lib.load_nuclide('Pu239') + assert 'Pu239' in openmc.lib.nuclides # load non-existent nuclide with pytest.raises(exc.DataError): - openmc.capi.load_nuclide('Pu3') + openmc.lib.load_nuclide('Pu3') def test_id_map(capi_init): @@ -457,7 +457,7 @@ def test_id_map(capi_init): [(3, 3), (2, 2), (3, 3)]], dtype='int32') # create a plot object - s = openmc.capi.plot._PlotBase() + s = openmc.lib.plot._PlotBase() s.width = 1.26 s.height = 1.26 s.v_res = 3 @@ -466,7 +466,7 @@ def test_id_map(capi_init): s.basis = 'xy' s.level = -1 - ids = openmc.capi.plot.id_map(s) + ids = openmc.lib.plot.id_map(s) assert np.array_equal(expected_ids, ids) def test_property_map(capi_init): @@ -476,7 +476,7 @@ def test_property_map(capi_init): [(293.6, 0.740582), (293.6, 6.55), (293.6, 0.740582)]], dtype='float') # create a plot object - s = openmc.capi.plot._PlotBase() + s = openmc.lib.plot._PlotBase() s.width = 1.26 s.height = 1.26 s.v_res = 3 @@ -485,13 +485,13 @@ def test_property_map(capi_init): s.basis = 'xy' s.level = -1 - properties = openmc.capi.plot.property_map(s) + properties = openmc.lib.plot.property_map(s) assert np.allclose(expected_properties, properties, atol=1e-04) def test_position(capi_init): - pos = openmc.capi.plot._Position(1.0, 2.0, 3.0) + pos = openmc.lib.plot._Position(1.0, 2.0, 3.0) assert tuple(pos) == (1.0, 2.0, 3.0) @@ -506,7 +506,7 @@ def test_global_bounding_box(capi_init): expected_llc = (-0.63, -0.63, -np.inf) expected_urc = (0.63, 0.63, np.inf) - llc, urc = openmc.capi.global_bounding_box() + llc, urc = openmc.lib.global_bounding_box() assert tuple(llc) == expected_llc assert tuple(urc) == expected_urc diff --git a/tests/unit_tests/test_complex_cell_capi.py b/tests/unit_tests/test_complex_cell_capi.py index ee5e699254..39431684d9 100644 --- a/tests/unit_tests/test_complex_cell_capi.py +++ b/tests/unit_tests/test_complex_cell_capi.py @@ -1,5 +1,5 @@ import numpy as np -import openmc.capi +import openmc.lib import pytest @pytest.fixture(autouse=True) @@ -73,12 +73,12 @@ def complex_cell(run_in_tmpdir, mpi_intracomm): model.export_to_xml() - openmc.capi.finalize() - openmc.capi.init(intracomm=mpi_intracomm) + openmc.lib.finalize() + openmc.lib.init(intracomm=mpi_intracomm) yield - openmc.capi.finalize() + openmc.lib.finalize() expected_results = ( (1, (( -4., -4., -np.inf), @@ -93,6 +93,6 @@ expected_results = ( (1, (( -4., -4., -np.inf), ( np.inf, np.inf, np.inf))) ) @pytest.mark.parametrize("cell_id,expected_box", expected_results) def test_cell_box(cell_id, expected_box): - cell_box = openmc.capi.cells[cell_id].bounding_box + cell_box = openmc.lib.cells[cell_id].bounding_box assert tuple(cell_box[0]) == expected_box[0] assert tuple(cell_box[1]) == expected_box[1] diff --git a/tests/unit_tests/test_deplete_fission_yields.py b/tests/unit_tests/test_deplete_fission_yields.py index bedd702a90..854c530f95 100644 --- a/tests/unit_tests/test_deplete_fission_yields.py +++ b/tests/unit_tests/test_deplete_fission_yields.py @@ -8,7 +8,7 @@ import bisect import pytest import numpy as np import openmc -from openmc import capi +from openmc import lib from openmc.deplete.nuclide import Nuclide, FissionYieldDistribution from openmc.deplete.helpers import ( FissionYieldCutoffHelper, ConstantFissionYieldHelper, @@ -18,7 +18,7 @@ from openmc.deplete.helpers import ( @pytest.fixture(scope="module") def materials(tmpdir_factory): """Use C API to construct realistic materials for testing tallies""" - tmpdir = tmpdir_factory.mktemp("capi") + tmpdir = tmpdir_factory.mktemp("lib") orig = tmpdir.chdir() # Create proxy problem to please openmc mfuel = openmc.Material(name="test_fuel") @@ -40,8 +40,8 @@ def materials(tmpdir_factory): settings.export_to_xml() try: - with capi.run_in_memory(): - yield [capi.Material(), capi.Material()] + with lib.run_in_memory(): + yield [lib.Material(), lib.Material()] finally: # Convert to strings as os.remove in py 3.5 doesn't support Paths for file_path in ("settings.xml", "geometry.xml", "materials.xml", @@ -64,7 +64,7 @@ def proxy_tally_data(tally, fill=None): if not hasattr(tfilter, "bins"): continue this_bins = len(tfilter.bins) - if isinstance(tfilter, capi.EnergyFilter): + if isinstance(tfilter, lib.EnergyFilter): this_bins -= 1 n_bins *= max(this_bins, 1) data = np.empty((n_bins, n_nucs * n_scores, 3)) @@ -192,9 +192,9 @@ def test_cutoff_helper(materials, nuclide_bundle, therm_frac): assert fission_tally is not None filters = fission_tally.filters assert len(filters) == 2 - assert isinstance(filters[0], capi.MaterialFilter) + assert isinstance(filters[0], lib.MaterialFilter) assert len(filters[0].bins) == len(materials) - assert isinstance(filters[1], capi.EnergyFilter) + assert isinstance(filters[1], lib.EnergyFilter) # lower, cutoff, and upper energy assert len(filters[1].bins) == 3 @@ -235,9 +235,9 @@ def test_averaged_helper(materials, nuclide_bundle, avg_energy): assert fission_tally is not None fission_filters = fission_tally.filters assert len(fission_filters) == 2 - assert isinstance(fission_filters[0], capi.MaterialFilter) + assert isinstance(fission_filters[0], lib.MaterialFilter) assert len(fission_filters[0].bins) == len(materials) - assert isinstance(fission_filters[1], capi.EnergyFilter) + assert isinstance(fission_filters[1], lib.EnergyFilter) assert len(fission_filters[1].bins) == 2 assert fission_tally.scores == ["fission"] assert fission_tally.nuclides == list(tallied_nucs) @@ -246,9 +246,9 @@ def test_averaged_helper(materials, nuclide_bundle, avg_energy): assert weighted_tally is not None weighted_filters = weighted_tally.filters assert len(weighted_filters) == 2 - assert isinstance(weighted_filters[0], capi.MaterialFilter) + assert isinstance(weighted_filters[0], lib.MaterialFilter) assert len(weighted_filters[0].bins) == len(materials) - assert isinstance(weighted_filters[1], capi.EnergyFunctionFilter) + assert isinstance(weighted_filters[1], lib.EnergyFunctionFilter) assert len(weighted_filters[1].energy) == 2 assert len(weighted_filters[1].y) == 2 assert weighted_tally.scores == ["fission"] diff --git a/tests/unit_tests/test_math.py b/tests/unit_tests/test_math.py index e6b2ddecd6..7c0218fbd7 100644 --- a/tests/unit_tests/test_math.py +++ b/tests/unit_tests/test_math.py @@ -2,7 +2,7 @@ import numpy as np import scipy as sp import openmc -import openmc.capi +import openmc.lib import pytest @@ -15,10 +15,10 @@ def test_t_percentile(): # The reference solutions come from Scipy ref_ts = [[sp.stats.t.ppf(p, df) for p in test_ps] for df in test_dfs] - test_ts = [[openmc.capi.math.t_percentile(p, df) for p in test_ps] + test_ts = [[openmc.lib.math.t_percentile(p, df) for p in test_ps] for df in test_dfs] - # The 5 DoF approximation in openmc.capi.math.t_percentile is off by up to + # The 5 DoF approximation in openmc.lib.math.t_percentile is off by up to # 8e-3 from the scipy solution, so test that one separately with looser # tolerance assert np.allclose(ref_ts[:-1], test_ts[:-1]) @@ -35,7 +35,7 @@ def test_calc_pn(): test_vals = [] for x in test_xs: - test_vals.append(openmc.capi.math.calc_pn(max_order, x).tolist()) + test_vals.append(openmc.lib.math.calc_pn(max_order, x).tolist()) test_vals = np.swapaxes(np.array(test_vals), 0, 1) @@ -55,7 +55,7 @@ def test_evaluate_legendre(): # evaluate legendre incorporates the (2l+1)/2 term on its own test_coeffs = [1. for l in range(max_order + 1)] - test_vals = np.array([openmc.capi.math.evaluate_legendre(test_coeffs, x) + test_vals = np.array([openmc.lib.math.evaluate_legendre(test_coeffs, x) for x in test_xs]) assert np.allclose(ref_vals, test_vals) @@ -98,7 +98,7 @@ def test_calc_rn(): ref_vals.append(ylm) test_vals = [] - test_vals = openmc.capi.math.calc_rn(max_order, test_uvw) + test_vals = openmc.lib.math.calc_rn(max_order, test_uvw) assert np.allclose(ref_vals, test_vals) @@ -133,7 +133,7 @@ def test_calc_zn(): -8.98437500e-02, -1.08693628e-01, 1.78813094e-01, -1.98191857e-01, 1.65964201e-02, 2.77013853e-04]) - test_vals = openmc.capi.math.calc_zn(n, rho, phi) + test_vals = openmc.lib.math.calc_zn(n, rho, phi) assert np.allclose(ref_vals, test_vals) @@ -147,7 +147,7 @@ def test_calc_zn_rad(): 1.00000000e+00, -5.00000000e-01, -1.25000000e-01, 4.37500000e-01, -2.89062500e-01,-8.98437500e-02]) - test_vals = openmc.capi.math.calc_zn_rad(n, rho) + test_vals = openmc.lib.math.calc_zn_rad(n, rho) assert np.allclose(ref_vals, test_vals) @@ -160,7 +160,7 @@ def test_rotate_angle(): # reference: mu of 0 pulls the vector the bottom, so: ref_uvw = np.array([0., 0., -1.]) - test_uvw = openmc.capi.math.rotate_angle(uvw0, mu, phi) + test_uvw = openmc.lib.math.rotate_angle(uvw0, mu, phi) assert np.array_equal(ref_uvw, test_uvw) @@ -168,51 +168,51 @@ def test_rotate_angle(): mu = 1. ref_uvw = np.array([1., 0., 0.]) - test_uvw = openmc.capi.math.rotate_angle(uvw0, mu, phi) + test_uvw = openmc.lib.math.rotate_angle(uvw0, mu, phi) assert np.array_equal(ref_uvw, test_uvw) # Now to test phi is None mu = 0.9 - settings = openmc.capi.settings + settings = openmc.lib.settings settings.seed = 1 # When seed = 1, phi will be sampled as 1.9116495709698769 # The resultant reference is from hand-calculations given the above ref_uvw = [0.9, 0.410813051297112, 0.1457142302040] - test_uvw = openmc.capi.math.rotate_angle(uvw0, mu) + test_uvw = openmc.lib.math.rotate_angle(uvw0, mu) assert np.allclose(ref_uvw, test_uvw) def test_maxwell_spectrum(): - settings = openmc.capi.settings + settings = openmc.lib.settings settings.seed = 1 T = 0.5 ref_val = 0.6129982175261098 - test_val = openmc.capi.math.maxwell_spectrum(T) + test_val = openmc.lib.math.maxwell_spectrum(T) assert ref_val == test_val def test_watt_spectrum(): - settings = openmc.capi.settings + settings = openmc.lib.settings settings.seed = 1 a = 0.5 b = 0.75 ref_val = 0.6247242713640233 - test_val = openmc.capi.math.watt_spectrum(a, b) + test_val = openmc.lib.math.watt_spectrum(a, b) assert ref_val == test_val def test_normal_dist(): - settings = openmc.capi.settings + settings = openmc.lib.settings settings.seed = 1 a = 14.08 b = 0.0 ref_val = 14.08 - test_val = openmc.capi.math.normal_variate(a, b) + test_val = openmc.lib.math.normal_variate(a, b) assert ref_val == pytest.approx(test_val) @@ -220,7 +220,7 @@ def test_normal_dist(): a = 14.08 b = 1.0 ref_val = 16.436645416691427 - test_val = openmc.capi.math.normal_variate(a, b) + test_val = openmc.lib.math.normal_variate(a, b) assert ref_val == pytest.approx(test_val) @@ -234,13 +234,13 @@ def test_broaden_wmp_polynomials(): n = 6 ref_val = [2., 1.41421356, 1.0001, 0.70731891, 0.50030001, 0.353907] - test_val = openmc.capi.math.broaden_wmp_polynomials(test_E, test_dopp, n) + test_val = openmc.lib.math.broaden_wmp_polynomials(test_E, test_dopp, n) assert np.allclose(ref_val, test_val) # now beta < 6 test_dopp = 5. ref_val = [1.99999885, 1.41421356, 1.04, 0.79195959, 0.6224, 0.50346003] - test_val = openmc.capi.math.broaden_wmp_polynomials(test_E, test_dopp, n) + test_val = openmc.lib.math.broaden_wmp_polynomials(test_E, test_dopp, n) assert np.allclose(ref_val, test_val) From 67de50e4fc6e45279aa79a41ef328a7f7fa0e61f Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 5 Sep 2019 07:32:52 -0500 Subject: [PATCH 026/158] Rename two tests --- .../{test_complex_cell_capi.py => test_complex_cell_bb.py} | 0 tests/unit_tests/{test_capi.py => test_lib.py} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename tests/unit_tests/{test_complex_cell_capi.py => test_complex_cell_bb.py} (100%) rename tests/unit_tests/{test_capi.py => test_lib.py} (100%) diff --git a/tests/unit_tests/test_complex_cell_capi.py b/tests/unit_tests/test_complex_cell_bb.py similarity index 100% rename from tests/unit_tests/test_complex_cell_capi.py rename to tests/unit_tests/test_complex_cell_bb.py diff --git a/tests/unit_tests/test_capi.py b/tests/unit_tests/test_lib.py similarity index 100% rename from tests/unit_tests/test_capi.py rename to tests/unit_tests/test_lib.py From 8a580284692a30a13060b5d92b3bde558e5ec4ab Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 5 Sep 2019 09:59:17 -0500 Subject: [PATCH 027/158] Mention openmc.capi -> openmc.lib name change in release notes --- docs/source/releasenotes/0.11.0.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source/releasenotes/0.11.0.rst b/docs/source/releasenotes/0.11.0.rst index bf4719374a..170630ea72 100644 --- a/docs/source/releasenotes/0.11.0.rst +++ b/docs/source/releasenotes/0.11.0.rst @@ -81,6 +81,8 @@ Python API Changes :func:`openmc.model.rectangular_prism`. - The ``get_hexagonal_prism`` function has been renamed :func:`openmc.model.hexagonal_prism`. +- Python bindings to the C/C++ API have been move from ``openmc.capi`` to + :mod:`openmc.lib`. --------- Bug Fixes From dc3ab07cedf867dc2c870d2ddb547221c83d713f Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 13 Sep 2019 10:59:18 -0500 Subject: [PATCH 028/158] Make sure no heating adjustments are made if HEATR is not run --- openmc/data/neutron.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/openmc/data/neutron.py b/openmc/data/neutron.py index c63142d141..022cf8283d 100644 --- a/openmc/data/neutron.py +++ b/openmc/data/neutron.py @@ -813,6 +813,15 @@ class IncidentNeutron(EqualityMixin): for table in lib.tables[1:]: data.add_temperature_from_ace(table) + # Add 0K elastic scattering cross section + if '0K' not in data.energy: + pendf = Evaluation(kwargs['pendf']) + file_obj = StringIO(pendf.section[3, 2]) + get_head_record(file_obj) + params, xs = get_tab1_record(file_obj) + data.energy['0K'] = xs.x + data[2].xs['0K'] = xs + # Add fission energy release data ev = evaluation if evaluation is not None else Evaluation(filename) if (1, 458) in ev.section: @@ -833,8 +842,9 @@ class IncidentNeutron(EqualityMixin): # The best way to handle this is to subtract off the fission # KERMA that NJOY calculates and add back exactly what we want. - heating_local = Reaction(901) - heating_local.redundant = True + # If NJOY is not run with HEATR at all, skip everything below + if not kwargs["heatr"]: + return data # Helper function to get a cross section from an ENDF file on a # given energy grid @@ -844,6 +854,9 @@ class IncidentNeutron(EqualityMixin): _, xs = get_tab1_record(file_obj) return xs(E) + heating_local = Reaction(901) + heating_local.redundant = True + heatr_evals = get_evaluations(kwargs["heatr"]) heatr_local_evals = get_evaluations(kwargs["heatr"] + "_local") for ev, ev_local in zip(heatr_evals, heatr_local_evals): @@ -877,15 +890,6 @@ class IncidentNeutron(EqualityMixin): data.reactions[901] = heating_local - # Add 0K elastic scattering cross section - if '0K' not in data.energy: - pendf = Evaluation(kwargs['pendf']) - file_obj = StringIO(pendf.section[3, 2]) - get_head_record(file_obj) - params, xs = get_tab1_record(file_obj) - data.energy['0K'] = xs.x - data[2].xs['0K'] = xs - return data def _get_redundant_reaction(self, mt, mts): From 723153db5c1f85ee847f31a24330d00d3dceba80 Mon Sep 17 00:00:00 2001 From: Jiankai Yu Date: Sun, 15 Sep 2019 14:09:17 -0400 Subject: [PATCH 029/158] revise codes related to white boundary condition --- examples/python/white/build-hemisphere.py | 73 ------------------ examples/python/white/build-pin.py | 75 ------------------- .../white/{build-plane.py => build-xml.py} | 0 3 files changed, 148 deletions(-) delete mode 100644 examples/python/white/build-hemisphere.py delete mode 100644 examples/python/white/build-pin.py rename examples/python/white/{build-plane.py => build-xml.py} (100%) diff --git a/examples/python/white/build-hemisphere.py b/examples/python/white/build-hemisphere.py deleted file mode 100644 index e62182708d..0000000000 --- a/examples/python/white/build-hemisphere.py +++ /dev/null @@ -1,73 +0,0 @@ -import numpy as np -import openmc - -############################################################################### -# Simulation Input File Parameters -############################################################################### - -# OpenMC simulation parameters -batches = 500 -inactive = 100 -particles = 10000 - - -############################################################################### -# Exporting to OpenMC materials.xml file -############################################################################### - -# Instantiate a Material and register the Nuclide -fuel = openmc.Material(material_id=1, name='fuel') -fuel.set_density('g/cc', 4.5) -fuel.add_nuclide('U235', 1.) - -# Instantiate a Materials collection and export to XML -materials_file = openmc.Materials([fuel]) -materials_file.export_to_xml() - - -############################################################################### -# Exporting to OpenMC geometry.xml file -############################################################################### - -# Instantiate Surfaces -surf1 = openmc.ZPlane(surface_id=1, z0=0.0, name='surf 1') -surf2 = openmc.Sphere(surface_id=2, x0=0.0, y0=0.0, z0=0.0, r=0.39218, name='surf 2') - -surf1.boundary_type = 'vacuum' -surf2.boundary_type = 'white' - -# Instantiate Cell -cell = openmc.Cell(cell_id=1, name='cell 1') - -# Use surface half-spaces to define region -cell.region = -surf1 & -surf2 - -# Register Material with Cell -cell.fill = fuel - -# Instantiate Universes -root = openmc.Universe(universe_id=0, name='root universe') - -# Register Cell with Universe -root.add_cell(cell) - -# Instantiate a Geometry, register the root Universe, and export to XML -geometry = openmc.Geometry(root) -geometry.export_to_xml() - - -############################################################################### -# Exporting to OpenMC settings.xml file -############################################################################### - -# Instantiate a Settings object, set all runtime parameters, and export to XML -settings_file = openmc.Settings() -settings_file.batches = batches -settings_file.inactive = inactive -settings_file.particles = particles - -# Create an initial uniform spatial source distribution over fissionable zones -uniform_dist = openmc.stats.Point(xyz=(0, 0, -0.1)) -settings_file.source = openmc.source.Source(space=uniform_dist) - -settings_file.export_to_xml() diff --git a/examples/python/white/build-pin.py b/examples/python/white/build-pin.py deleted file mode 100644 index bd02f2c67d..0000000000 --- a/examples/python/white/build-pin.py +++ /dev/null @@ -1,75 +0,0 @@ -import numpy as np -import openmc - -############################################################################### -# Simulation Input File Parameters -############################################################################### - -# OpenMC simulation parameters -batches = 500 -inactive = 100 -particles = 10000 - - -############################################################################### -# Exporting to OpenMC materials.xml file -############################################################################### - -# Instantiate a Material and register the Nuclide -fuel = openmc.Material(material_id=1, name='fuel') -fuel.set_density('g/cc', 4.5) -fuel.add_nuclide('U235', 1.) - -# Instantiate a Materials collection and export to XML -materials_file = openmc.Materials([fuel]) -materials_file.export_to_xml() - - -############################################################################### -# Exporting to OpenMC geometry.xml file -############################################################################### - -# Instantiate Surfaces -surf1 = openmc.ZCylinder(surface_id=1, x0=0, y0=0, r=0.39218, name='surf 1') -surf2 = openmc.ZPlane(surface_id=2, z0=-1, name='surf 2') -surf3 = openmc.ZPlane(surface_id=3, z0=+1, name='surf 3') - -surf1.boundary_type = 'white' -surf2.boundary_type = 'vacuum' -surf3.boundary_type = 'vacuum' - -# Instantiate Cell -cell = openmc.Cell(cell_id=1, name='cell 1') - -# Use surface half-spaces to define region -cell.region = -surf1 & +surf2 & -surf3 - -# Register Material with Cell -cell.fill = fuel - -# Instantiate Universes -root = openmc.Universe(universe_id=0, name='root universe') - -# Register Cell with Universe -root.add_cell(cell) - -# Instantiate a Geometry, register the root Universe, and export to XML -geometry = openmc.Geometry(root) -geometry.export_to_xml() - - -############################################################################### -# Exporting to OpenMC settings.xml file -############################################################################### - -# Instantiate a Settings object, set all runtime parameters, and export to XML -settings_file = openmc.Settings() -settings_file.batches = batches -settings_file.inactive = inactive -settings_file.particles = particles - -# Create an initial uniform spatial source distribution over fissionable zones -uniform_dist = openmc.stats.Point(xyz=(0, 0, 0)) -settings_file.source = openmc.source.Source(space=uniform_dist) - -settings_file.export_to_xml() diff --git a/examples/python/white/build-plane.py b/examples/python/white/build-xml.py similarity index 100% rename from examples/python/white/build-plane.py rename to examples/python/white/build-xml.py From c36f19a56fdd126ac75dc44bd4f410718de7c5e1 Mon Sep 17 00:00:00 2001 From: Jiankai Yu Date: Sun, 15 Sep 2019 14:11:59 -0400 Subject: [PATCH 030/158] revise codes related to white boundary condition --- src/particle.cpp | 66 ++++++++---------------------------------------- src/surface.cpp | 47 ++++++---------------------------- 2 files changed, 19 insertions(+), 94 deletions(-) diff --git a/src/particle.cpp b/src/particle.cpp index b6a3f5dee1..6e87691957 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -428,7 +428,8 @@ Particle::cross_surface() } return; - } else if (surf->bc_ == BC_REFLECT && (settings::run_mode != RUN_MODE_PLOTTING)) { + } else if ((surf->bc_ == BC_REFLECT || surf->bc_ == BC_WHITE) + && (settings::run_mode != RUN_MODE_PLOTTING)) { // ======================================================================= // PARTICLE REFLECTS FROM SURFACE @@ -457,10 +458,15 @@ Particle::cross_surface() score_surface_tally(this, model::active_meshsurf_tallies); this->r() = r; } - - // Reflect particle off surface - Direction u = surf->reflect(this->r(), this->u()); - + + Direction u; + if(surf->bc_ == BC_REFLECT) { + // Reflect particle off surface + u = surf->reflect(this->r(), this->u()); + } else if(surf->bc_ == BC_WHITE) { + // Diffuse reflect particle off surface + u = surf->diffuse_reflect(this->r(), this->u()); + } // Make sure new particle direction is normalized this->u() = u / u.norm(); @@ -490,56 +496,6 @@ Particle::cross_surface() } return; - } else if (surf->bc_ == BC_WHITE && settings::run_mode != RUN_MODE_PLOTTING) { - // ======================================================================= - // WHITE BOUNDARY - - // Do not handle white boundary conditions on lower universes - if (n_coord_ != 1) { - this->mark_as_lost("Cannot diffuse reflect particle " + std::to_string(id_) + - " off surface in a lower universe."); - return; - } - - if (!model::active_surface_tallies.empty()) { - score_surface_tally(this, model::active_surface_tallies); - } - - if (!model::active_meshsurf_tallies.empty()) { - Position r {this->r()}; - this->r() -= TINY_BIT * this->u(); - score_surface_tally(this, model::active_meshsurf_tallies); - this->r() = r; - } - - // Diffuse Reflect particle off surface - Direction u = surf->diffuse_reflect(this->r(), this->u()); - - // Make sure new particle direction is normalized - this->u() = u / u.norm(); - - // Reassign particle's cell and surface - coord_[0].cell = cell_last_[n_coord_last_ - 1]; - surface_ = -surface_; - - if (!settings::dagmc) { - n_coord_ = 1; - if (!find_cell(this, true)) { - this->mark_as_lost("Couldn't find particle after diffuse reflecting from surface " - + std::to_string(surf->id_) + "."); - return; - } - } - - // Set previous coordinate going slightly past surface crossing - // r_last_current_ = this->r() + TINY_BIT*this->u(); - - // Diagnostic message - if (settings::verbosity >= 10 || simulation::trace) { - write_message(" Diffuse reflected from surface " + std::to_string(surf->id_)); - } - return; - } else if (surf->bc_ == BC_PERIODIC && settings::run_mode != RUN_MODE_PLOTTING) { // ======================================================================= // PERIODIC BOUNDARY diff --git a/src/surface.cpp b/src/surface.cpp index f759e7752b..2354659520 100644 --- a/src/surface.cpp +++ b/src/surface.cpp @@ -12,6 +12,7 @@ #include "openmc/string_utils.h" #include "openmc/xml_interface.h" #include "openmc/random_lcg.h" +#include "openmc/math_functions.h" namespace openmc { @@ -148,7 +149,7 @@ Surface::Surface(pugi::xml_node surf_node) } else if (surf_bc == "reflective" || surf_bc == "reflect" || surf_bc == "reflecting") { bc_ = BC_REFLECT; - } else if (surf_bc == "white" || surf_bc == "diffuse") { + } else if (surf_bc == "white") { bc_ = BC_WHITE; } else if (surf_bc == "periodic") { bc_ = BC_PERIODIC; @@ -201,51 +202,19 @@ Surface::diffuse_reflect(Position r, Direction u) const // Diffuse reflect direction according to the normal. // cosine distribution - Direction n = normal(r); - n = n/n.norm(); - const double projection = std::max(-1.0, std::min(1.0, n.dot(u))); + Direction n = this->normal(r); + n /= n.norm(); + const double projection = n.dot(u); // sample from inverse function, u=sqrt(rand) since p(u)=2u, so F(u)=u^2 - const double cosine = (projection>=0.0) ? + const double mu = (projection>=0.0) ? -std::sqrt(prn()) : std::sqrt(prn()); - // handle special case for very large cosine - if(std::fabs(cosine)>1.0001){ - std::cout<<"cosine="<<"\n"; - exit(-1); - }else if(std::fabs(cosine)>=1.000){ - return u = cosine * n; - } - // sample azimuthal distribution uniformly - double t1, t2, rr, ss, tt; - for(;;){ - t1 = 2.0 * prn() - 1.0; - t2 = 2.0 * prn() - 1.0; - rr = t1 * t1 + t2 * t2; - if(rr<=1.0) break; - } - rr = std::sqrt((1.0 - cosine * cosine)/rr); - t1 = t1 * rr; - t2 = t2 * rr; - - // determine the direction off surface relative to norm - if (std::fabs(n[2]<=0.9)) { // for the general normal - tt = (std::sqrt(n[0]*n[0]+n[1]*n[1])); - ss = 1.0/tt; - u[0] = n[0] * cosine + (t1*n[0]*n[2]-t2*n[1])*ss; - u[1] = n[1] * cosine + (t1*n[1]*n[2]+t2*n[0])*ss; - u[2] = n[2] * cosine - t1*tt; - } else { // for the normal is almost along with z-axis - tt = (std::sqrt(n[0]*n[0]+n[2]*n[2])); - ss = 1.0/tt; - u[0] = n[0] * cosine + (t1*n[0]*n[1]+t2*n[2])*ss; - u[1] = n[1] * cosine - t1*tt; - u[2] = n[2] * cosine + (t1*n[1]*n[2]-t2*n[0])*ss; - } + u = rotate_angle(n, mu, nullptr); // normalize the direction - return u = u/u.norm(); + return u/u.norm(); } CSGSurface::CSGSurface() : Surface{} {}; From c377231d8d27ac66717a0557ddc86815b636daf5 Mon Sep 17 00:00:00 2001 From: Jiankai Yu Date: Sun, 15 Sep 2019 15:27:45 -0400 Subject: [PATCH 031/158] add regression test for white boundary condition --- tests/regression_tests/white_plane/geometry.xml | 13 +++++++++++++ tests/regression_tests/white_plane/results_true.dat | 2 ++ 2 files changed, 15 insertions(+) create mode 100644 tests/regression_tests/white_plane/geometry.xml create mode 100644 tests/regression_tests/white_plane/results_true.dat diff --git a/tests/regression_tests/white_plane/geometry.xml b/tests/regression_tests/white_plane/geometry.xml new file mode 100644 index 0000000000..b8be4b59dc --- /dev/null +++ b/tests/regression_tests/white_plane/geometry.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/tests/regression_tests/white_plane/results_true.dat b/tests/regression_tests/white_plane/results_true.dat new file mode 100644 index 0000000000..b80361ced1 --- /dev/null +++ b/tests/regression_tests/white_plane/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +2.268730E+00 4.470768E-03 From 37df7e6a3c3bd3f277c099c24715b08f635c7798 Mon Sep 17 00:00:00 2001 From: Jiankai Yu Date: Sun, 15 Sep 2019 15:29:01 -0400 Subject: [PATCH 032/158] add regression test for white boundary condition --- tests/regression_tests/white_plane/__init__.py | 0 tests/regression_tests/white_plane/materials.xml | 9 +++++++++ tests/regression_tests/white_plane/settings.xml | 15 +++++++++++++++ tests/regression_tests/white_plane/test.py | 6 ++++++ 4 files changed, 30 insertions(+) create mode 100644 tests/regression_tests/white_plane/__init__.py create mode 100644 tests/regression_tests/white_plane/materials.xml create mode 100644 tests/regression_tests/white_plane/settings.xml create mode 100644 tests/regression_tests/white_plane/test.py diff --git a/tests/regression_tests/white_plane/__init__.py b/tests/regression_tests/white_plane/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regression_tests/white_plane/materials.xml b/tests/regression_tests/white_plane/materials.xml new file mode 100644 index 0000000000..2472a74717 --- /dev/null +++ b/tests/regression_tests/white_plane/materials.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/tests/regression_tests/white_plane/settings.xml b/tests/regression_tests/white_plane/settings.xml new file mode 100644 index 0000000000..70b4e802f8 --- /dev/null +++ b/tests/regression_tests/white_plane/settings.xml @@ -0,0 +1,15 @@ + + + + eigenvalue + 10 + 5 + 1000 + + + + -4 -4 -4 4 4 4 + + + + diff --git a/tests/regression_tests/white_plane/test.py b/tests/regression_tests/white_plane/test.py new file mode 100644 index 0000000000..223f4e5e6a --- /dev/null +++ b/tests/regression_tests/white_plane/test.py @@ -0,0 +1,6 @@ +from tests.testing_harness import TestHarness + + +def test_white_plane(): + harness = TestHarness('statepoint.10.h5') + harness.main() From 849524d4dc09e22f1ca7dbde8dfae84c9b5c22d7 Mon Sep 17 00:00:00 2001 From: Jiankai Yu Date: Sun, 15 Sep 2019 15:45:58 -0400 Subject: [PATCH 033/158] add draft description to methodology on white boundary condition --- docs/source/methods/geometry.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/source/methods/geometry.rst b/docs/source/methods/geometry.rst index 4e90dd9922..93dec2df47 100644 --- a/docs/source/methods/geometry.rst +++ b/docs/source/methods/geometry.rst @@ -617,6 +617,10 @@ condition has been applied, the particle is killed and any surface current tallies are scored to as needed. If a reflective boundary condition has been applied to the surface, surface current tallies are scored to and then the particle's direction is changed according to the procedure in :ref:`reflection`. +Note that the white boundary condition can be considered as the speciall case of +reflective boundary condition, where the same processing method will be applied to +deal with the surface current tallies scoring, except for determining the +changes of particle's direction according to the procedures in :ref:`white`. Next, we need to determine what cell is beyond the surface in the direction of travel of the particle so that we can evaluate cross sections based on its @@ -892,6 +896,13 @@ Dxy + Eyz + Fxz + Gx + Hy + Jz + K = 0`. Thus, the gradient to the surface is \\ 2Cz + Ey + Fx + J \end{array} \right ). +.. _white: + +------------------------------ +White Boundary Conditions +------------------------------ + + .. _constructive solid geometry: http://en.wikipedia.org/wiki/Constructive_solid_geometry .. _surfaces: http://en.wikipedia.org/wiki/Surface .. _MCNP: http://mcnp.lanl.gov From 46367e8369cfaf69a51dfbea74fb94c0805eadd3 Mon Sep 17 00:00:00 2001 From: Jiankai Yu Date: Sun, 15 Sep 2019 16:57:07 -0400 Subject: [PATCH 034/158] add more details in documentation of geometry --- docs/source/methods/geometry.rst | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/docs/source/methods/geometry.rst b/docs/source/methods/geometry.rst index 93dec2df47..65f5e9fe31 100644 --- a/docs/source/methods/geometry.rst +++ b/docs/source/methods/geometry.rst @@ -901,8 +901,39 @@ Dxy + Eyz + Fxz + Gx + Hy + Jz + K = 0`. Thus, the gradient to the surface is ------------------------------ White Boundary Conditions ------------------------------ +The white boundary condition is usually applied in deterministic code, where the particle +will hit the surface and travel back with isotropic angular distribution. Essentially, the +change of particle's direction will comply to the cosine distribution instead of uniform +distribution. +The probability distribution function (pdf) for the reflected direction can be expressed +as follows, +.. math:: + : label: white-reflection-pdf +where mu is the cosine of the polar angle between reflected direction and the normal to the +surface; and theta is the azimuthal angle. +Thus, the cosine of the polar angle can extracted like this, +.. math:: + : label: white-reflection-cosine + +and the azimuthal angle is uniform in the range of 2*PI, +.. math:: + : label: white-reflection-unifrm + +Then, cosine can be obtained by analytical inversion of cumulative probability distribution (cdf) +like this, +.. math:: + : label: white-reflection-sqrt-prn + +Eventually, the final reflected direction vector can be computed via the rotation of normal to +the surface like this, +.. math:: + : label: white-reflection-rotation + +The white boundary condition can apply to any kind of surface, as long as the normal to the surface +is known as mentioned above in :ref:`reflection`. + .. _constructive solid geometry: http://en.wikipedia.org/wiki/Constructive_solid_geometry .. _surfaces: http://en.wikipedia.org/wiki/Surface .. _MCNP: http://mcnp.lanl.gov From 85f80f6b331109a3ced98a56d42cd4a5e6fa9102 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 16 Sep 2019 10:59:03 -0500 Subject: [PATCH 035/158] Make sure Nuclide::reaction_index_ is big enough for MT=901 --- include/openmc/nuclide.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/openmc/nuclide.h b/include/openmc/nuclide.h index a936ac0102..a1571ea58e 100644 --- a/include/openmc/nuclide.h +++ b/include/openmc/nuclide.h @@ -93,7 +93,7 @@ public: std::vector urr_data_; std::vector> reactions_; //!< Reactions - std::array reaction_index_; //!< Index of each reaction + std::array reaction_index_; //!< Index of each reaction std::vector index_inelastic_scatter_; private: From dd9bff40a1355cd7dce7d9dd17297ea96ac9ef25 Mon Sep 17 00:00:00 2001 From: rockfool Date: Mon, 16 Sep 2019 12:00:47 -0400 Subject: [PATCH 036/158] optimize the switch of white boundary --- src/particle.cpp | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/particle.cpp b/src/particle.cpp index 6e87691957..bf107a741c 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -458,15 +458,11 @@ Particle::cross_surface() score_surface_tally(this, model::active_meshsurf_tallies); this->r() = r; } - - Direction u; - if(surf->bc_ == BC_REFLECT) { - // Reflect particle off surface - u = surf->reflect(this->r(), this->u()); - } else if(surf->bc_ == BC_WHITE) { - // Diffuse reflect particle off surface - u = surf->diffuse_reflect(this->r(), this->u()); - } + + Direction u = (surf->bc_ == BC_REFLECT) ? + surf->reflect(this->r(), this->u()) : + surf->diffuse_reflect(this->r(), this->u()); + // Make sure new particle direction is normalized this->u() = u / u.norm(); From acc157ad30f224af3d65bdfe4ec31c6fb57afab7 Mon Sep 17 00:00:00 2001 From: rockfool Date: Mon, 16 Sep 2019 12:14:05 -0400 Subject: [PATCH 037/158] remove the example file for white boundary --- examples/python/white/build-xml.py | 81 ------------------------------ 1 file changed, 81 deletions(-) delete mode 100644 examples/python/white/build-xml.py diff --git a/examples/python/white/build-xml.py b/examples/python/white/build-xml.py deleted file mode 100644 index 184cbf346b..0000000000 --- a/examples/python/white/build-xml.py +++ /dev/null @@ -1,81 +0,0 @@ -import numpy as np -import openmc - -############################################################################### -# Simulation Input File Parameters -############################################################################### - -# OpenMC simulation parameters -batches = 500 -inactive = 100 -particles = 10000 - - -############################################################################### -# Exporting to OpenMC materials.xml file -############################################################################### - -# Instantiate a Material and register the Nuclide -fuel = openmc.Material(material_id=1, name='fuel') -fuel.set_density('g/cc', 4.5) -fuel.add_nuclide('U235', 1.) - -# Instantiate a Materials collection and export to XML -materials_file = openmc.Materials([fuel]) -materials_file.export_to_xml() - - -############################################################################### -# Exporting to OpenMC geometry.xml file -############################################################################### - -# Instantiate Surfaces -surf1 = openmc.XPlane(surface_id=1, x0=-1, name='surf 1') -surf2 = openmc.XPlane(surface_id=2, x0=+1, name='surf 2') -surf3 = openmc.YPlane(surface_id=3, y0=-1, name='surf 3') -surf4 = openmc.YPlane(surface_id=4, y0=+1, name='surf 4') -surf5 = openmc.ZPlane(surface_id=5, z0=-1, name='surf 5') -surf6 = openmc.ZPlane(surface_id=6, z0=+1, name='surf 6') - -surf1.boundary_type = 'vacuum' -surf2.boundary_type = 'vacuum' -surf3.boundary_type = 'white' -surf4.boundary_type = 'white' -surf5.boundary_type = 'white' -surf6.boundary_type = 'white' - -# Instantiate Cell -cell = openmc.Cell(cell_id=1, name='cell 1') - -# Use surface half-spaces to define region -cell.region = +surf1 & -surf2 & +surf3 & -surf4 & +surf5 & -surf6 - -# Register Material with Cell -cell.fill = fuel - -# Instantiate Universes -root = openmc.Universe(universe_id=0, name='root universe') - -# Register Cell with Universe -root.add_cell(cell) - -# Instantiate a Geometry, register the root Universe, and export to XML -geometry = openmc.Geometry(root) -geometry.export_to_xml() - - -############################################################################### -# Exporting to OpenMC settings.xml file -############################################################################### - -# Instantiate a Settings object, set all runtime parameters, and export to XML -settings_file = openmc.Settings() -settings_file.batches = batches -settings_file.inactive = inactive -settings_file.particles = particles - -# Create an initial uniform spatial source distribution over fissionable zones -uniform_dist = openmc.stats.Point(xyz=(0, 0, 0)) -settings_file.source = openmc.source.Source(space=uniform_dist) - -settings_file.export_to_xml() From 4987f5aea0076c3259f0054304b0b4c5e3a4e6de Mon Sep 17 00:00:00 2001 From: rockfool Date: Mon, 16 Sep 2019 13:36:18 -0400 Subject: [PATCH 038/158] modify geometry.rst --- docs/source/_images/cosine-dist.png | Bin 0 -> 39479 bytes docs/source/methods/geometry.rst | 48 +++++++++++++++++++++------- 2 files changed, 37 insertions(+), 11 deletions(-) create mode 100644 docs/source/_images/cosine-dist.png diff --git a/docs/source/_images/cosine-dist.png b/docs/source/_images/cosine-dist.png new file mode 100644 index 0000000000000000000000000000000000000000..f7c165dcfd6d44ef7d6269e5efb0dee73d4bb3ae GIT binary patch literal 39479 zcmX`T2UJtf6E+-FL_x67yU0(a1w?vB5RevnFH#H;AoLCaEP&EgdX*Y_4ZU|HQbTVM zkP=#`1`^5}|L^yH_ne%Qdvov3?C$K$v(L=U7j2LVB{>s0005v=ee+ru0Js?r0NmiY za~l94xxe(TnfL>Vr>=?upkjmtLp-@<|4Q=}08kxAabZnLJiqJq#>5iiL^wX-S(u(xj-1Xl%bQE|J9Fl& zvumNnMUm%DF({e(e91Rp((0X|E78G2dgCU4SUHf;a^{z_+_HJwbrY_3=LgMrD*<0A zGShRqI4UzBBIHEXf!%CdPW6_nwHrQc-^^;VF9c}QWb647o-GZXciP3&Lq?$w)U=GE`@gFvn>WkK zmF#X@6^;$HncOu?;P-p`rYS5jKz_^8{%b7qIx84=m#*pX%fuSGg1aRvDOiB2@{s36 zjudj={6clE??YSL?u`YnxW0A5GBy2BXEdZwq$z)QW6{tj;v3tGt|GM;q;yxGCloJp za2fhbmQjPE1U<80{M^RPl0#{V|_1iq{>h~SM{H9{^IXYjUYwzuu(mVd#4{?<^QHP=MxV#|a7v>k`n zr1xY=BJ5)W6@GJo>Gk|slfw+Hlcy{go*|JIl>XqkFvhhEuuq?fjm7yi?YecmCE4x} zvu2&$O`Hiy1n9Y=Ls)O(1ei3l_I*=;pyDWhOXlp*Zq1Qk&3%EEHgl7XmzBU4ISU^# zRWdq7HuFENIlF9ScG$%WOXhTo=;{}iZ+*x-uXep$f}i%2Rw2UN_uDT`avFB!6cCS+ z>z|0U#i4OFs{1@G^iawYwId86&iKR9U2ijYNNm^7gVbZk*Rk1(>$}5`W=S+GC_GC+ zHvA(Q9<|a!6+cqKlv!r~n_tuuylZAFn66=D^~(-z?&E%I?)y?|SuLceWL{CwwtZrP zUpc7-`1@0Nn4;k=-oq4UjFG{8N2BI1CcAdoHRJge!PEIuU0=&PONW;AmvF_iqpuV* zE2nOUT2q>^iDdeZyT-sgcM83RrkyeD%{38=#-ioN9}ofv$l0XbM+f|#D{o8D__?wk zFJXQ$R#cyHFiY}3GymYW%AfIMf@Qs&4kctZH{dmD3zW0eMC)vLXZY89_)+OlXO8Hn z5+V6Fb3;y^`6VS`h9l=oa99V=G4y@_{;`omPm*qW0J{3ZWlkx!T)O_~={UJ<0SRZ( zd}&EmMfRH3xa-S4wjkfRM2@VU68M`D+0Ajiqf?iA*<)TGE(KHC-7eJpS@yB;^I9Gs%`P;eIPq072pAfDRTy%N9xY~? z$?0d<%%5N!>z;~?eBO9@q|YKMW0HBbWKhs@;yT}0QlTmuQ(7t5-{cUS_!u`53>f6( z9d-zes1RcUX|`#zoA`|DrqiuBc8`8MiS5Px*coz}go~Wf@E&y}S|oVP{m#)6VF^^a z3Yc4>GE;A%EvUeeRXA}jl=CZ^#k#S6?U49$&z(JJKVPg&M!Mk!#_utEN zb&`52$0Dd`mzi)&@ib+H}FM8cHk`tHc)?*28b_U+&7 zfd&Yihy@C)e)|r7*pyh%=CNDKS6dk7F{lTORU%Vx4|8cIe`z3kl3ybwj?@#muQxov z2icfHSjP26($#7^D+nxG(o_QFY^+_Hu?O1^>^`VnDP*v~cnExfc?F_dOv8NlXQ0Ew z=U=C4g}n8mCR1gGhh#$@NZalIqUdm5Ka6Z69ZYn!(41ppeGfVX8?ZE5J?r<&xvNq@ z89vX+6vsm!G&)dHC)r(>==;V$$>m+PfsY`q*-gb60++gzFfaqe+oLQx-EH@j|5L1E zDOG%ykWbEYxyqow*Ioz5PzYKQ|mE#J6>(~xj`H-lNy&k;q0PP-@J(jsjmiMBKRW=CE z7C$%Ei;o=Z?N8>A%JzTzaKye|4c}0>vM}L=_gf|Za%q6bT~jw`*0|<7?;bO2pl$C9 zNE`js9C>y{p`~|bn?EIB`evT@vevJoIb!)HCNQR-7H$$48E0m3ISem%6gXN`Z2GvN zHRtf_tMu!heBbDmJ3(Q@(9ZI~mfY&uEaQ7dE0tSzp{x&6NPK$lZjr~)A)Z)z?5M4c zPi34))||Pr^yG9f3)oT#-XAcfitq!?^$o7p5gK#2WUe%mqfypvF!|Pt5-*FanB@0s ze%`4fI6E8SNsoU=Gr2|cY4EsfV23(<4z88JQADZeOz}f~(EaR9PkK!;W*btz9yx6n~mI8(-jPqPboSrl%f+es=FUx@vs&1%KJ%zE**+(61Z+j2>^K zac`bD{Ooi4=Vkfuf&0fesWJqsQS^(W0>Rf2OldtgkUVq?w}T zuEGVB+D|{Z{^BuN4X+4Bzez+A$TihJzk$rh!=!T&#;d*9fQ}Hl(*elej$-{o%6XLV z=oL&WYNDe!UFssX=X0=1cAI-*(${O5OG_8iO9utyE5=V^ZQ_@|2?o5vlcPUCenQvJ6VrsLy~Ss&IDje&`uz^?Y~ z-|h4ZM#r_K{-jjKm`@z_R?kg)fSWu6oX5wGF9qo);p1xS{q|4yB?xOtK5E0ItD`>I zR1F2-2<}U39cgAidq>6XaG(N$zOGEUIMtW*eC^DN>gho z?6?{AgVnPnrfF}*9-oA7{^tEnF@$fF!&gD0*kiM$kSj~_Xe4#t0j8Pw9*`*l&4{Zy zD}=cQ#_>mr#Z`Iw%yD==4-NjYd!zQD@3#`xjgSVv*>;?p*ev!Q!t<{LQqrq1G*SF- z_(OJmgVWjWarhw>A%@_S*`=>3B=y8jmDvu}FJTOs?OMm}`i{sJWJMhps00Lk?z{BK z4wYvfcyy6h6X@I`T1Tg;x#ltRaN(V}aZ?G!FZH<>8`7Ed&Z`K|4Hsn)Jdhj7d~>2F z;?kYYfVuOVXta`Msit#=CHC!vwAEwxf&rY(YxkShoQ%my*wOt!5)E*2^<0RhEuTE?vAK8P$#*SXXzfa$0f3v`n(sSYFoD-<`K}F zjrC1d>p05$wa%3Nwae}ricW;fkYV+)_quOA z5v9H_laTw*?+z53f>s69dnGP|=zRZh_&Am=GKqe(e4g#xUx~=9EKb<1^(;Jf^?8l( z*jginPY3qYvtpkSJkovNdCife_YcH=p58b!PkA-6>m&VV*Yrj`ZgiIM}7!!=b}svm?HPAmMkai{OZND!oOw<$5*dV&Um-_hsCLdc7R& zkG8gjko;~BSWVe#IDBm6UQD{)J2#G3R40rlGuJEHi;y*y#WT5WGS6!l$VxrgC|(n3 z=Z+bdJqaA*f01U`)JBUh zGXkCh@)#_Raoq|4XR~S#pT)LudIiUkjeUWxScrL0qP?G=VkgIyf+>Xzxr2%dF=Wlv zz{r>qsU${Ft>PeA@)9#JSaoTMooAMgC^4@-Yc5oar=^oH8|&>fS9-OBT&7O)`QA5c zPyHTRo4M=gBMj(#cBT#qciZ|Be)eJFsl>bXIG9KHcg35Bq>R2?S}nD^c>6ZWA^(o+ zJt){ndT+lboj|!6LXC}s$i@y{XMq$^jS~pop?$$~;a-fsX%OmA1UwV*MF0?ayo!dj zWGuXa3tYOS?e_#4m9&5Cq*rrM(V$NeFo!aHK5nBBcRR(TKni{}qyWHRkx?at`*#Ml|R&3@_@+1A3T0W1V_aU-2R&Wadw zh=<#M7!xf3we9m;vC)|i#A~p8Er8fR>`4{?m817y`p&OG#&e`@{}E9D3Cquh`HS8K=JWbgmwRWIiUWx)t{(fbP5YvK^oactHjg}S#fm|{@7pxt zey{s=*=sp=KT&_7klfQ^PyZbkr)?CMdO*ns+f|wpKW#gcm^gD;#_flz4$z`rUurGm zvb0Sz>;zyzjc2e?uhoOES9HFQuPgcsuxG57Y5U@=X%UUmG(*z-1`6>6XbckNMb_+6 zC1ZDY`#AdRx$-0WM%Nct()8EqaKgwcbMduIET?8voaW~q)g|1j^48Ow>tP{MRk+DT zi~EIl+;6vby*?FzeALtB%Jm=TQ6cKdI*U3e$W+kz(V_bpI<|@WiiqU~SHn3mYH`e^ zuG;hRtGLzfb!(IkuCTPLvrZALn-588ix>P-auYR;034{3sZx#q{c?)Z%E>&eLF zY_1lyyt90A=0S?Y`QlEXW^B{$izGtiYJgeobKV)*w-$eWXtDnt zNIldI?*5Y!J|7=ftyo1}VX$~t4{x@FRzJLLWVz<~28E z4nL~UZ>IEIelqi<;OF8qM-|W!biNOcVUYWPiosJrnk;;Tj5k-AjS;n_0pt zsFAZ_r9=GD8lA&)7M`2<F1&%#;fI##j-1oXj{DVTDKLQ-B#E)psS95+6kmS!OE=6uWI0-x z4b92s@LUBd9gbwrdW*(oK1h7lbY*Y$d!5d6?g0U$Vs2>RYIT)UL&1{m(+{Wbqd>+D zOxUYT+P=h}71vN?xKDn=G%2}9{nP4Eos3!YAOBuM2m7F9cT4qYZ!*;g*JGHE!n@(4 z!v~}Mkw%UYd~*D+(xo)6g6Bw+z^j?=Wz_nW#DU{h=Yd@9v5L0UKfMk<_t${>_VQCQ zCn3t-3k456X&<;M`Jz4`3C-pk0eu$QIQws##lnct4#S$vGopnp1E*w>MJ3*2$wXW3 z)%EE%d^%`E*C#Msx;d_n zuGQMVZI>Af*l5K)fVY%Qz@4h4IzR+LqRv}pop4u!aCa@b5#eJ5toC2Nkf_*hM_Xc)ezR!{F*uC)_zfAHX zp>bvBl1@Tzm*sic9TYD~v+Rq;J)p+-5sIXhS>`Y%dncyldP0J40t7*BJ3}pjiDg)b z!-HVE>y#%RyOh4ZPX^QmWVM*yq_&D#?T}@7az{5+|khWbL&J)R< z=h#4dlW$&ipZsTu!&Ln+G*)5JX^3C+HFJgOkKTpgr0wOJmRm-}4?AISfj>JSIi+h zzGS8t->!DIH~k5gfv4_$%BP)-PRIi9JF}RhC?Dwsl5{2kueQyXRM)OZ{`k5i#fkd+ zD>8jPouaAQ>8vkgazC%NRqWnytB){uDnQ3KxJ`lWvWd2*rtYLtVqf?-I64G5q1DXx zVfwtahJr6iEv;TkD`^`h`hlCO!}EDdmI3izGU#v4ZG)L8#M&Iy?D{*+Xt&wHE$Z@rFEODx#w()Vq&h7N5wM#jxXV;KwOM5|iyyxx;@_uB0<|BRh zh*IaMVeZgB*@QLllVinnwvhCO9DivkZ-zFI}@=-B6;|7s8MQLx7)po zE%S74(EEeUq>4R@G5)9a^Xz62nd^>{K!nS|$Leodu0Lh|)@jJ)Ou}Qu(!pQWqnN+r zRnUS|;{T-Ga15q}jdT+gd%#125Tx|bY5OTJmCdsAK-lJaZ`akN0`s>42j@SB<3~=1 z6-1x+?qqQ>*lG~XP;zUsZdei9c{W76>YTtjuGU_tf@DX=$Aw=^)KL6Fb{1scGq^#u zrf2Bw+7tF(?fcuFpQ=uDR36gbqyoJ>ZHmYO>;$lEMV ziY(ibzf)%Kx=TwQ+6bP;)I%|UkTm7)S;YC&$($Qk=DYk8|LA~W_&1x5&-?MElZL2O znR;Bz`KQ`;soyE5?azo@YNsD!4fD(XT&G~zd|M$PcBI;D^%vO0s@$2PbYYN6hOLQsGN58e1;fxB>DPrRG95`a)AhH>K*swNo`si~ZB|{R_K+)OQ(gx$E zvxZ_so=E&q3qgb}x94{dII!lvjTr*mEuAY~T}yu>xwGBs>4-@JdAa&EZx})v#JoC4 z;$$s_YO7b;6(!#ZiN+a0@KUDMjH`%7+qqaj*j!*F<&WnXlrW<%L!uKCM&E@#M`n4S zblhaPE5+#cZub)r-WL766-BliXr_7}SXs@m?IskvIp)4>>5~lFdDg%eZn~k^IAsGu z)v8(;$}d$Z?ur{FEIl26=|9;n<}||G_|lz)E9pZsy(aTvW{Xzfj7e|c!X>?lEx?406N0R!`=Tr*2m|?2>FzB-&Y;(< zA$&bA*!G{k5o$*t|0ZMejCHtU0w&?Nk?-D%ElRgDS3@e?=~+@i<3?M^kbRT7Ik2X zCo#r7TZC`vS={Gdg{ZITu7|EA~r(`V@|5p*+sNmT%@J-&MW9NA7_`v`-J|lh8b{sq4rg^y>b4(wB zily7xIduIN?1Rs`N?K4&_fY^lGxc9D+rhdJOGNzo+mbN52(BWLe66J@bVNhZz?6qBzlLvaRjBO#K6;!tBX(R0yw92Xx;>b4(q-;8-WQ_ z1Vm_+8Wr3iEJ%Lf>;@p(OK)4Sg>& z4>RWd06vPh%z&4m)lerAz)zg30CR)!uoJ~j4=mb8U$c(4g!%R=bhT*|dVixT# zOWt&{{OoUODL0Yb6c^(B+5B4SaAc+6Ir_ArF?to<8`5)rhC0phf{T^I*S;36mp~31 zzU;WlLNSde3+@saf+i9gb2avapWf;a!6^ovoL}+m@e9wrXE0g$IraOoMm5jc8zIQy zVZp>xt^>A-rNCk0P8}GL*7W;9`W=92{bR)|>o!xkLfy z661$~;JkFEkh7|7*GCb)7_T~u3h$l4^Tsr)vS|=f{{GHwL(b%{@Ycsjoq#k(|M0Jb zbVn!RlG`Xk3`VGP1MRjjZU*R5z0*vuW)CE9a^a}upu}iU;y&^fqyp(Ny=k74#E&&k zOKO=3_5S^%C=cr-U9J2qiw)z+56lcN+#5-bb4>R3SOdXJ_dK9_43avFQft0Idoog(Bj2h6H60b81kZ>BJVhcByHcUR8@i0@-xK zxu~by;+A0n>@XD!J)f`!Q_B7FI3sKz$?vij$xrrpPtWCUipR9tJio&8yHNw!9EH;d zvTU@yJfVVF*qX7L+o2x|M9R1^4F)%*p2jjd{&rYz_~j)CY>*Lap6(gR&(}h z3Z-i-l%EVxBIs!9G7B^9z}1dj1kdkNhk~`c>;v~xyOkk-8_1H`wZ_prxmq_V2=k!* zQ@KOhR{`GNI~=)GMC3C>;5Tn%f~F)bJ%JX-xNw+2XkV3#QP?u?P@L4?^NhZj^#MM@ zvf{}S!(Y}V`b#%De}n4_pXo#*$WXfpbpZ z@FFa&-C?QH;P?H!n-_BRxN~th8Jk5Uav?w&)mH z@|3^W>RdYNsir_^Q|*nT!Wy^MGg76)y`=mP+LWGN%~R~4w$nm_*^2$m4G`)~h{(4x z3_KYf(Tn<^}~kh8y>Coct=$LkxUwG5Z}Pwln+S? zw18CeJ`SnkS~h-W%Xq=9gZu2Tv4xwL{L&ct?o?-UTAQ~nB9Q9hx*mrbsc-W6qN@Vici@WG}8 z^;?f#?S9Re>s7(hbAW?t>M9dk=i<*0fk?5E(2JN{Kl{{AYYfd*lV{5{SWPJFjN$pMJ{aQKb3I6v2uF0F+PjbI)98V2lyC&s zg|*)Ylou(BH^3A4=5e%rKuh_nzs;sqC54R%LU(xXWOramLjxSS@&-z+jVlq_b{FhE z*}qUjbYpm42BnxCBU~Fm+ja1l{muo+SVs+E61{%KHuy5>DBgqivn~CGCZ)?Fy4q^t zcIcPOFMTPgSOZJ>_5v=;w}r!V9Pm(s*{P+qc{>RwhJ9Mnu{SCQ=`F>wjlUEaH_BVI zl-%J;=xz=44q-_#qqbzWBDB>v3utVhw@bLi2^?)^a$h>S0#ICDa{6Wi04Ba>e_rYp zSX5)7n&@I=3f#vsF1hw@vmg2@Vb!!)#K~aqxWn`pBomOr4Sr%)PD?DDL2|K!$38dITL~u$b9%>ryljD6fO|(gUy^rAq!c*)YUm~CH2Kwr&>zf+kNAhrO zrQZD9k1P355rs^a=_q*9g6M=de3XcBX?(hsX3IjaAHaThQXlg#uN zT)a7V+I3J}SOj}wlhe@>l_2o;7 zdRMCgmxeLTRmaOMspf{@ldTWGY>WdIlanxV3$X47BX1$dNxBl11tg%us_QRhSw>cN zF{{k-qX!g&^@DuDJdC6T{5Djh0u zsE32^C)b(&mZ0_9o>#e)k(D;jUdx+1@kfp>k^=ZXu|3L3Umv9j;?KA}ER(Q+`6Al< z1&!&VX4*v0!fu4#c1(zOx_5k)-K(U7XmYN#@ldA zzX{8t4aIQ{<=3A`8N^xZ1r6;}0}%4opE)rAoVP)0>NRfVvx1E`g}V`~Icv?bs#Wzij}(2|c^( zbQEonSTO+cnbMr9U?)z(@!c9$Ytl>3gSdp`rM$~Uq^fTmRMY^RJ)J(1m-CCf66u7X zw$#_AzNZp5qXjo?{>BKFrXk;1hip-tGPk8IZ_q^ zPB4MDZw(KY3U7?9#w&|R7NxCXlnqFi?;C3~+z=nM0#b~tV*}6eH8&1E^wCgSHETol z#m0?PvsIa7hCXyZ0MK<)w2L-y+*1zAB9~K2^(xA7`oNQLANcF!Qi66L01)~Uvzck~ zbL`{}Le|R^wjI&dAmgTOeFi1QI&qCDla-3Qx2W^UTSU$z(+1f}-x!WrdZD~lDGAVvsY1G5hm$VwhjwW{Zr9>Fv+E)6ktV+UWuPa1Hx1)7+Qp;NMYvXRrT_Hth%SgrLLaFN{}_LtE^*R~F7R+FJOvLo#tlGa@q={MK)jlv+t-}!@{9PWu!wn zz@RniEAvogW6t^=!0|hY$#pt)M?fA^ zAvvh7RsTI46Fb9eV2i@2$|+&6hk9GwVv^7j><-qfCD>Pk=N>zP*crwu^Pgs+6|0FL z<6E@h8K$?jN68D#4CE8pefjnT!8zWr)XDUikG_D_l&p8|j)dExW#(P>*_FgyOf@y3 zLM(Kpp6u8A`nAyoqN5I{HE%JsIzN2PUmhD{Ux5YOLI6A75xpj(VQ3o%@QiP7xuzkB zT$VX$M-UJS)_YY{rBIH8==?wW1z^>~W+gc7U0=U8{lZqWM+DqZx+uH^8TSo>S?KsI zf$3Zqa5nOBHCru|KK?i>m&W3T;-)XicfT&~q_iyn`)5g9>bInps|$;PqcjuU4|-5F z)9+)Ge9_*^xWEYKJSVr^%X_oqu|=lJiwxP$Iiuv4my#56j_nIibkklwI{Zbr+oE;u zt{(6Qo)#+$@97(r^q97%EX{MvR^EGqr=(@LlDEBFs#_XQD^v}c3BuI&bDKWP`igPA zB3!Xv0_olnX|k)rxVIQk_L}%=^OvB2%#goK5rs-SAP7EP%gn{xj3)U#9<^t_`f{@7 z*gL192%X&G>YMe9X@p{tYe{)0cwi>=7}|esANH4mJ?_t6rNcwRaa^t!3qB)Qu5e28 z`=7t#UhN(|`Sy^Y3lP$RkdFig{!Z{B`!32{gg1CRsXCBJ0IG&)T^g)RR$`SPrY^F& zP`GMIupaW+(LFwT*)n{(1H-7kc{AIEJjXg?EtOrqqpT;y?Au6(0Ng86ZR%x$dBA=Q zHD+_s#}Fq4BGb(38i2meE^SYz`z-0EzhX8pVC2nXM69k8fTYE7!XfNa!iA(o8;Zx4 zefNb+ZwwCY`881r8!=2niWKdw=Pu7JM42LV71nQ*GQAGOT1=l-h3sC=I&XgdQzhBFOmute`7n03P&|s7~KA3Im7r zdI-VasJ^4@7?NGS&tI)!RAQd*Q*RrkkqaB)0%e2pM-P41xJSHXWwhQj_@bW&UF+r_ z-vG>36$(iL0A=4Ctt1IQK4@F~|I6b4eF>%X&*+=^ANNw&V0xx!4gk#l7s9sx@!!5- zmW#+$F8zG}*;14MrdSjg`_fc(Fv{_!~fYvKO?l#)p{E_!i`PK()t-=(2((#G+Ma0G~I z0bY0}*7uYTQ}YRrrd``MO(~#$K-`Utx%Or&PsfGFhTP4sXKkDatcDDyV3%kx)B%{N_VBdQ@w>DplriB|b3{9eF8yIn-QOLh-3_peVHX6|+ZIF+# zVL4nvI(U}YgN2keP*Yg6!f^^+xZXmyM^>Jgan)`$&-@Qm_n1~s0Y=ZMrFIw!*9ZDi zFota$riA43c)# zhdn@Awoc~lt(TMTlF?D9Oy&^Un3>Jd5KiDd6!aM=IjJ1i(%9;WRP3N`;NB!-5zGiz zhpVS0m0nu}F=A?74Z%&0=fUUYUpB7s|5r)Ru=po*kB53!y%|Pn@>0ejvu8LjZ=nU+L%qe+27tRmqm>wg-2^=e%7 z`VuEZp9^Ml%9Ze5i$xWTLX({ECMDr>HXK`7;II78i5$4j=F7+;#}r|qNO+$O$&+By zF;1DMaKwpNLeda;Su;EROx*VUMgrSFGBdxXdIu+_<`FEmP6y^vo8gIkEOv!BI8HOm zm=+aLyp$1(o}l8sx3h)Ca1-CRXe1K87~E8Ltbpb*cJT9uTi!wHFR<5T7A2C6yx=$J)8HAN494jipR24Q^-drfTaUZpb5`iU#(*WMx2D^ zYzJaI_RSa~v++~X1QIn=&BP0g-)l$&O}IDdm#Zv-rM}oc;>9n@4knX7%;+jAYYpU#vEo42@q+t44AOU-h3sj2& z-aeJcgbSBDntOK$c%!4xr(ouF@^EwO6ia$&o?~-@&l~F$bsV&F#xvkH2mfZXdjt19 z)Os3ml#rR@Z#rsy($Hjkf$vYp!k4_Dg(VyL!j%SG--l=)@f(d5glV{>Af|N_>HMD+ zM}tuOX)4&!LsXdi;I)M(`%r<8xPWg5kcYfF)!{j?ZG{@Y4j zrUXP$>Lu$^tNp6zhE9*AWxpaHe|2M*0($&~nlFCApMv%WK9Gq?U}J>>hyVes9(ayw z>!%ci>6xbQaK>uoe5h9C`!prj>-8gh#>AnGME9S3q%c+nwEn`ZN%Aa zbHId$Em{D#AGUTAJkd`qD$~>GjU~CXqhE!U1+Ot2HyDoAjlCodRhsY?odP6&E$;W) zy`g|w5mCsD%`EXzQx0YTb|e!^nt!l{$V@EJ9eQi(wdelSVlJurQZ5z;1V$Ukv)_d4 zw2arEV2I(_VMA-{4TWr*i6zEX&pHo1zDGVt#Ny!m6y)iGL|}6t4ROCst5f(j@MG(i zYYC7F3Y_WhZw@zB=_o#(*bt2?pE&YI66Ze6!agT@ZDI*tdm`317OV3)`W0LQ?y|D2 z|GiNA7s})Oh&Y+TP649}@nI|R4Nrgl6r^})t}*llh$5i(qD!G6CC~}ggou`)C13x1|ljb zFPiIUuieng)tsyX^ONRIhuQF6_1dT(Q}89zdfl&YG`g&xDf!(}t#SP{YGA8&B2o0~ zbF)9eSUZZBNx>WXkMWpoAj=O2*=!;+Ud2$qnN>_Wzge1S87$p*yO7tPYoWV)c544i3fx3tx1$h?ul$vf#YLxzld z?g8N@?8`9mf2oKuxbV*x%%D$b>d0XZSzI)C2$=TEYfm~9BAOyy4>`j8W|}8mIn(G? z3pw-E2k*+0Q99e~EIrM!;BYb(-``qMUE;D`3P_xyPd0!rg4V`Wd&MAlu^M5~lU9EM zi_#1G7GvvcG2YzVX6=ww>}iTQ?qi>^JFu z;3_$$XE?nGuq8KZ!MgIy`}#HBta@#!Z9-X5bGoIyE4I%SQi`?hIk`XzI;N#0Kh45l zJ*L=`%WfpxvNCCXfY^VB3&Av;q=OOV1x2DodO-|HFAti=g@NUN@!#n@&65C0o#e_Q ztvc3=lQnKgt?!KA(Hk$s(h8fFI89Vggg~OnZ*dm`CltI)J^3Fbdr*Qi>BT-pdIdv|H;r!?gsus_)0^grm(9Ii?Sos;6EiPqQ zH?1g@^6=Dt>4MTO{HVQ!txGxXigW!Ac#)+--3u@Ga#i^b}4DxckOu;>N^xp!v1Kg@q9f>HXKG0;* z2}@~s5&BAnI330*Xkg@9-JGKI^F2;Vmn5YfKACu(`ANhQ%bG7q*&tt9zZuBKBd~uh za&-#)azk?ggqKCyP8y#E>RmqzO&o9gR2z^nf6Wy+Q>@);o82}3yD-(!et_qmM~Bqu zsiJ1j_kV2NOJZ-?xgs|ce-27bi@aX?oZYff3Q1kXuk-+81<`;PZ4GpNZ9Fu{^mB;Y zcd-Fw5nF=Dzb#W6@a0W^;;T0nVY#bmxqgnR(R&j<7axe;%qtHb!PmTAxE^}nUqz?Y zC~Q@Wwhx>9Q#`BW`NUShNy<#V0Yfk`%K(^1)&a~B$@6lwk&&g5tIR`x%25G^uLKiI z&=cHy)uw!C2f=me8^#a8Y_*Ouk~Hbnyg;9@)&H9`OVaeG)Ujt8k#2;&OKTtyJ2RH$ zl9wXcBN_P?7->|Z&4;dQT)!=*NB3FpG8Tn}ZT^#}19jW}wY`;PK!b(~wUq*|+;lFy zQll{#x@ZoxmwRg81~D5Qf%5LwM0WF3~(qJFcqIASiJ1VHAm=pPtfA(*7SzPBKm}Pro~c|G-=l{;5!p&~W)1+|0FHtZ?Y9ko}{D_s3d{o8kfQ^F+1;$Xf21M2XcU%21* z&UBOx`IvuJXb0)(Eb2ZR%_Q&5VV?SfuxMx6_a&N+cICdEz1Q(Q3UH^Q<#o$ZkO`M5 z_wm5A!TVZL@4D4`xNaSPyE~DlOX>c|mvFw^P7wa!6t&u5z?D?}X>)*0*UibqQ0LXG z{>@9{sAhNKpBc!!Be+n2GiJDRp2I>)e&H5b|C;@^rAJGSwD~I@IDLjuyq>z+Cch{I zUkXPRPm_g))k=G#S|3cBjR^n=r{Nzfym&z9OR^R%>VVa&NsPHuR0PfEt0f*yxY~7h z?mhwFW`lIC>6m&l%!fs6eSg0#H8A4mdbA+44R@pR2~Roy z$6?HIrfQI`&IMjZ6e{XYk4+!g6DoO1bzbr7|FHGm(QtKN-1vkbAxJSpL`iuP41^tY|v-fB3 zea=4nBGE&k&G(CZAC2_*gq=~^nGDPowm#nM3;#r9a0Ca-cl=k z$%Kb}I4%EYWE)eqrCq>mCH^HxVafGXlleDoy1nd!U7Qx?X7?E6BZ!^>;+?|l3zMoDR$fC;_W2B zMr(CIcPzW(B;0!kioniZyFKq=mBln{$Gzvz#vm0e3B*6A3Mr&Rauj_27?1!{`dB&T z+QC#7y2BWPin?LLgl+)rZn&L?B-(>GpRNTm=68C)$PH?~8MV?OZ0vz{&IIVA}UsjIq&o3;ud4vWR$NA0g3i4VM6yB z^=LY>)(B;+IGQ}tsI0+L409W@2;|?`>em}N5;g}Gc_d1G*7ow0A<_BCFofJ|%mDuJ zhtgdB@foDxfu5h^=bkrlEm0l8vc~PnnE=tG_ps3nr zuZn^;&GBLAlxV8(RbVNJURG3^ihp?GZ&0{+_eIL4$6zZhQN4vCS2wSxb%Kwd1S@TG z*NVGztO>V$#9VRI4xn8jWPR!*tfP?0ndG#E4T5yy&i(kz%gaRoK2*>ibBo*G$0}T7 z2)MqrX;r7OP;{Ht3W;}5lKgc1v$n?u9F7zW7DDVg$1DQ&?`t%erG@N43`I=rEy{I< zZSe#7d}?6tHGf!{hrs326g`iC+#$G|ufMacB2Z1^l0}wW=tFIxs|X}u$C^Y}iZ`*c zKVSXQr>rs-B2OF7-;!sdejN2z+D zHe$)7#fbfrtCH>b^wG8)AmS*9c6OA=>_@^X;RmUPG>b+M4ay_n;Cn!2S^LhGMrc@b z`P9`1Ox@bm0FaZ!HN+*hvAkjt5H%{Htv`OKLa|%Us={cXMfrX5l_RNE_Y8h>u+X2U z(}U=v-HNnH_8~F(l&VAirz|Pdeo0fwX%?ZU^=Bl$BV8NIwa(*COA-G1EH@qIiDLA| zW*47Wwl%*nWsrZ=#VA{)Pt zRDA6FRh7^ejFwBT)5$%%1C!?+X)TwVi03qZN~|~9r3rJ+k);2y6AM#epGX-G_!zCM zA3OOQ&I@gLZ!v__Wd8M*-G!eP`HFhUQe4RFbp|?PDFpi`rjPp$NDW3?pZ8T+)V1b- zQI1&LpO9nKT?7OQSqiI{t0`jKto=<~PtF*OIr=p4R5&LJIASe!_3lqK|Hz;+tFCpv zdQo>*{?gdrkVY;R1f-glrz3btckJPMrqEyeAL1f4(w=LjSNuz=eRs)x|LByiWz;>`$ z62$W~G5ZFH{e4Zsno9ww{yjmOzley-k?*G0*29%YEc>r-171<#H+?;*4oQI#{yOz9 zHT~SBu-e*B>`P7%%Sn*Nv$p2(`WOgF6$@9m;&}5;l-bxP1s$uFO43v=F2P?jodh(G zt(C(jW5K?4gdC%?@-E!Jt(u@WeR9_EUNiG+Y{lQW!3T{xzid>kS|9hFc`b=qdxn^0 z+E_561Zm9ah~JU$mtVtYW(L_Pi<*A(5VlTG&X};ypRwLa!1Z{BevQx}S?}TIwt1}V zba(WSTHl&VHg?FT5O}}E2Wr!93?$7edvC>w8N{ZRN&4Zb&rMC;(?+?AD@vSGko46o z6_ymUe|D)+hF5HZABR$z9L~GatT?zQd@*K6+?XB{Se^isdE z%r*soGO3zk@!88W8zA%5`6W1-w+pn5OWM~+OcHVorV;4JZV3-uXB8kt@3>QwMp_*7 zH8&Oj=@6UFh5D=RGf&W2cnzpInUPo0BBlVePKU;PGf+WhnN4#wzA7evRDxlB>5Lx- zb(+i~o|xi#@3k}!M4SV%+Sz_?X0>q_dX+C}CSDtVv512EC!xFjoDw9ZCwHIW9NRFt zvM{AOAXBe8_em(nDQaw;zXWgK(XYS6@!Z+wEXoIuq^~A$t~ZYaVSsnJwWJW-Ol13R zh%o7g^gLoQ@G$b>;_^|G2X0hM4_3~N@$Se~G%H&8FfoG~KK49W)5Xvt9~Zb2=Al>6 zNV5^n^Ksn1RmNjMl;6z5@xY6(JYVB9EwI^Q6`Nb933TJ6AhpL_RB?ZOe@cf$0(#39 zc}AUHBBSC$tm0@@w@D`r4sgL8Za6?JK1M3R2E0{r-XzFLRKD!sYOPgHwW{wn*j*sj z^ZNC+BWof5j?cd4KTppF-?KPMJLXbuhL5Oa+c2vh>9ULm+|V-)5DpuU1N&CxJuatO zwzUN){Qvu*@2}x_VUlDhX)U&WP2VkLWsmud= zab0v(LGJi*FXas7N@$m(>_-ae_#dw+Qwci*D-fuhJ+u(EXtB6;N3eY5O3%`u{$o#O zgiXSa7`L%k6sKh5$wcgoMl70({<8%f2tXH?%X4MN)pAUhZ0}~G%e+wS=3WkBA3>8T&zPk_}>OXK= zCUKdJFMV84N0;X`coe_h%~Dz1i`P6G<B+BDSzE}tFa zGXYh}_*d;t6)k`8v^)SEQ??X14HE%erDoTtkMsf$j?kV+g1&}IcVKKYuZ>qNesr3% z%sXzS$^}XqQiQWrQW@5RYy&tF48dYZQY5>MO5p(Hhlu<>RiXNlZ5aE77wxZ5#n(Pf z?Ila#<^Y-Kx!i%IQoG-&mQRv6zbg8~31;zCQjcU@cDOD?^=zmJ`$j(!K<^M4F{8Eq zR){i#B1T@nh}UdM?T|H;kL>7w?|nl?GKaa7zZDT?{Eda7@uxuufVU%yaD0j;VWb~5 zSjCWjYMH0e6%d#`rPv#NyH4-C)NZ&I09Zsr4PJTtZf$tpp?i~}!t>vAUV{%cC}t7PG*n`>4yS=*g&8Dypcj`(i8p^_Y=Ws zu_h8w*=(GCXnR@QHX(;07o7&a$g^$nI(hUdUI^UM0u&?rP!ZmfW+!C>*jeLOX@ndO zZkH=su{nLjG<)SGl@-8+q#LuYNa99`MzStrV^%<+__UveF51?rb}y{q)Jd!B>!Q6- zArH4xs08V~j4}&zK9%)P!MPr>+o`rqlDvVf135-PnNRp&=%t;T@aKHw6AGh5S_akf;8uFa;YtTMj zvmvFhdt!Xd)qM*;rru64;LfTg&y4M}9A$iz5%Ioo=Gsc+j88GFmm~&B4z3R8UF(Tl z1ffN`m?w+7K@1QqjlHOp^0 zmEmj?%Z7GPC+jUER+~7g?s*o#K`V&is18{=@=v8sVi_Dhy1!cGc-EDeZ2GE_2*-Oh z`tCn+)W7O=73;lII72AA`l5+Nxn2y}YlxE!qBt?S)ws-}TcjU`&>qMQMs$J)iBb|Z zJj|9D`SOe-?md2z;Q+_+ZvYorek>Trs+F5PejqO(2VWi@6pFO&viN+O1casuIYv7u zT%V|GltPhyQ1K9S&Y(8>8C*glHgXv=>T3#ZGDtHb2X!0P#5UQw&C$sQjt#D1s{YL?If+M;IWZ6Ug7U9edqTt6b@>BKkuoga+%LK&kaIUZbHSfEBvF zPYeF!;A*_`-HcDz7=VCRMtsqL`w&}|o5Ll+)Z59Mz2vtC8 zy;Gvc%L)!isRWkg4s#-^vU&{LZZs4w%-nE3o?!0FO{Y^GVhE&8L;>vnrweb(Jp_?E zs``T23vmnDds;N}{7?@gRAi^dlJ)V<=)1|oM}Xccv=A3k<_|zck`>}FHd+=sM^%oa zF+hjKSsqxQB?e?0Ow1~Aoj(->_NDCF8GKFX&=TT(gHw6V$bbC*cbP+yJKUmsX8>s4 zdmVx^1+V=}?+U=TnF`knjqm|7AP3Dz-*rf#<8-8WQGcOR;}q3SMGm;Kdi>CVu9?0Z zsT&|Q|5E3px6C(nahEmjfSR3wxi4mW{FiO=@Bi{lkA1mK^dq$dJB(SH{{DS*%`aK> z1D2>mUtn`(kkj;(y#T1}hB!?g4N8AEIaj%8O|oH8rD=MiWq3nI?%|r+Bi%_m3nHp~ zc9kciQv9;en>l^ItXb;@Cs|l=>0BCBftJixm86CJQ~Voebrz|3?0r{-5TG^)-ee?1 zP<)U#5J;U&lO+sk(>5QheBI6Ww+@%fmAc>zWOMxpD{;OR=91^1mV#_QR+&@{lkL_d zFhrc{V4SkH6shoDrJ{fE!|$zd`fsK3TPaqwtPZE8EREUbO;t;*oQ88e5rN8g7Ab$M zjY)0F%8}i#J55+VqmJ-_cp)$$gXL{pvXS1e8dR5>U|li|7&pRvvu>kO>ZX3fp1WLh zS?#vetg&vV`^@%ZP3K$bTlpzyMUTdra+x6~s&=E?gT=|pA;IY}w@t_83>MGoTC4I^ zmp5xYf&GOd7pRW;`G_%2u$|V+hTx72qx6C5A-8Z$Z`M10*%~**PqV#++}zsawKmhH z9_{Xkv(Kj=S*!$;nNB5hlSX*hFJuMMxv^R>iAM+)8bSQnsp_Nvv<>f7vdK-XKm;1& zHFcWcBZW@ABih!XpD4UU9Ik@qY{xjhR2~w94sQt<(TuuC8{x@yvu>uI)_J$BIM4)U zxBI+GV(?-M7f4dor6j(PUHgNsOn$fgxQ|?AXaSPd%}(|R?Io~nyL0wUxbxu8zCa0R z*aXwGFLY&9sEbi8PdaJQK1V6LgRQ!Sy$37ri>%eH92x2;^2h=iagD6P_2{MQP)==W zhsGXH%mWzD)q2|!+4#o?6K}DEtTm)kW@d}DCxj0zUawR)D17l0mnCWX)w%J|+j{<} z{B26KPR<6W*^*4)9<7jE;7{5?Gv2Glj)ZXLOYNb4IWxOt`#qSnMOXE{I+36zBsPeW zdZ(GO3nu+E4Pr?Nbd?&0{Tmy|*J{ZWe}uUDVZX^Dc<1ljztk_Oqz;k_y*%w>j_ z#t3K7WeoG3wcUGO;ob14#Wh*3-{rGo81U;XNAU+)3(gAid&+FGLv~Bv@1r&aKGO>= zw)%)5Kq_GN70*mg88qiR>vJF#B8G9=<<><*UL{uGZRS>v*=b^*6tWjL5P0{K zSjftj>|U-uvV(T%U5W!bMNZS&v!)!jbJ8z?>iFyPzCyO2G^fAD3r5@5KA|ykj3kbA zJkG}F*x7jdE&qAP&O27wl(lT8Bc3p+C%|3WxJh#5QxEHl8x`eq$C67?xPizDd_WU2 ztyCZIRpCRYUA|29MGZM-w*7U!? zc8=^PeSlHr<3^R$ZZUhhKELl~I?*&ivER%zVT&bWmhRq2OTcEv z*<6mXy_B=CwX76>vX*{U|MhyE3Mpz#y>poF-#Av{lGBA~Sf6@K%{{-=8R3nBEajQ= z8tD+nK0>Y5Y2J;Mm428P^M2Y1lgld9a2QSYKdzz7&-$|3Hy9T%@0wyG9E|XDBwF`Lb-0^C9K|}TerZQ*~f8BCb(X4(zq3q5A z=tvYrhPr8YPOBTXooZ;ap=H}7D=X*KmFTEPKGvO-^f|30f?*<%?MoMVvfgY?ndxhdr4tC*6*3AWwp~zxO}ZPJh5Fl zQnt$e-APYEOD}wxy*H5(NV(3s`8U{6>cEf0zduJl_URBNIvX><9&gR|`8kTD{{xoe zupIWKyLHT}>*$6*rOJ+n-5+VK*a_isRy2MOav{k&!>MV0g;+Mjrb_48(CL^-SXp?0 zSsTd<%;mm-UCM)tX>s%#`fqokbv*KNabEeSmGGvxMu%Osqk8*=)L^*`2=r*)p8*6) z$fTZ(>AML66=x_Ty)Por{=i-C;IY2{%LM+v9-F1Tvi+MFNLP(_S5}E%(2+d7$mw_`kQT z!RifiJ;-k02p{e7;o7+VYcmAej@|!zY6E`HwdlXMT;h5<{MY7(>-pbUx^+vogXid( zuise=^XYD74k&`-;eJ6+lg?}o+_v;zTLiABJoMk5@@2r&d^D~jXguc166h(#_;1@* zDbN-O(E0MY`?e=Rpv5RGFON?UG zP1TuD3V6i|Db{IT8au?mh{NTHjJ*dI()_|FVUDTU1DI$?;NXpTGTSlfJ=!InzI?L9 z0~c5ZuZ*s+a1jd)zi^M{bY%qrazL;qw3?<|%bwGdMvg(g61vFO#pavkp6+kcT|0Ic z#Qto@SNsA#FzK2FS;Rnk;vdV)!g)|10&y!>bBOS3!hvd z-N>`NkSdtRX@3CRUKrf1e@i^VCTuvdFZLd|%_syRU!}n`^e(&(R#|_i7(eR0Xg zvT1Sy^y3>VxXqP)%=nlq@&UOdw3up5Js(@Q?NwEgm9bC9#>eqd3?$)KZIVmuKH0gJ4ro97Vt%qA5k-!#_* zuJS{A$VEfOs-7a^(6$=#@HR8yef4{RgD!$XWLx_}DRQ!b@)@JzBkoFn$p*Mk;+0F4 zld83JU}2%vj(sZG$(a5!v2I!WZlSO#dn@tz!{CUR1RH@Hs9cxJa22BASC`EAH^ zAtttwzDkiCLzq}3BAcHrKOX|FtaXCg36G2vQ%7XCj8)Xy=?+(>>QmTJdr=E|YK){* z4!?~>tbW~qqjTmwPlf*3R5oL52=6Z6`k4Lt>i)Z&efC^tNpXgOG&mT(@iSY@C!O>%fdhLOV%a3I+ zAwPlQ za}N2lD|qoaO1|(BY>ij9v2k_OjK%~tG(Ran;x3aTbfjsQP?P{Md$s;`GCwAkJY&GV zcf-L>MCmfhysUuB2Ht&dZD$bDSh(y22Khb=CZh3H0HZ1RT@~VqPmq2Mt_tbak*>Ds zUx*mI1Ju*}N|E>*Q#UjPQ_(hmP0!&(&>dgYCT`xtgZ4pf9e$&XWRdeGXZej=*pKn) z$W{f3?)E0liu0$sOTkm+?N)gxjxpSttNeyVUL; zR(13%9!_oT`B56yHM`-It# zJ{>K^0vT%}S@SuFDIhy3782_`)s`j)2B@Ecpn0 zpyhf;P6*dG{qD`6jk23I8;4p2>^1R(F(=a~AST7S5*AjsYVtko+yf?(=Sz{A5q5%3 z;DGF|Pd}M;YcRv5gWP#dRZ0Dcys+RGR@vGUp=BGp-+3+RR}Q_uu%f5S>#@}V<2rRc zCX;jJu+z#I!swq)bzQAv2P6P^|HyH*O}%U!$~S<6ix@AR6QsZV#Q(>iY<+TIKhp$d zxW_(rI4cwmoTP~+afY$cDZs;?Wz9_#1pZ=H2}X9gQ^X=ReK}Evq?%d1wwFiQc6^1p zvdT17VkrkzTKBq_IK;Y4{^e22KHe0gF<&@sY;=8?4=iw2Pe`>&;lp39=|lS8qhUmFn!vw`BvhN+KlC;eOifs`V8t@8&d z@Wql^*F7~G|Gd^uq*rqi(1DAjY-vAwQwur=aACEqN*J?Fg^~*GKW7-Q)7tpb6BqWG zc7@3@i#n@zOd)mbvW;xp3A+_gzw4=7Rl4<=<*SYRod`hMzgLSuhjptgRpz9}8Y&I1 z8pX~GtS2d9_!pAalUp3qRxt<~OxmPHJo#|mBe{9&cphE)RRb?!bZb=v1~9sMje>25 zx%|y$%tEGJw2mh&-=Df{FB3h7`%pIR-s}GxLQeyS2XhLY){!OJ?@{;HkJ08HvkRlN zB-+UrR~NB(YtQWs)BkYHN*{Ba(1}>2){yY<5Kqv zx?!knbgg5(9hGcKN5+ZFSQa^&f;zAx4}$~>CRz5QHY&lfUelsZf_&dKiB?LBNMJ<% z**jkaAJ4314|tkCJmsx8=k$?|Xj}0Ju09{SNH_n}v5{`A8H(jVg&=oM`1`#WB&WIO zPW;Go7XEfK^vYvv`;rFi#jT1?b!g^53s&=I%MR?7o z20w~dixnB;PiD-Sv{R*`IC#Togp)t))ukkL{1H?mV8>UjTcU|?za_dTq?(MgD1Hgq zr4qgf?|v?4p+z1~c_F0w)PCQEJ|a8A{NdWszDw9x=LT9A9FIa`gg@sL+pJ! z)4W59Hm;6h!;h$D%@=El;+%<*%V7w(oH6Dzosu0|QCW6CiA6_F zvJMxO*KUlPxgEPy2x5a!+(0;He`;N171O{86w%SqUI1$xt+;SDNpF3R&{G!%yx*fO zoa?0r-6W<|Zrw$m>Fgq(4G818{VG^NvTvM5q*9pGzTH9JvO>qu;^_;dTHS zlG~-^`+A!3XAOuQAEToa9Q+O@01uz!{CLxsf9GAMZXS-@P{F^m2(P2-8^T-V;Y3%1 zEb<>DmU4?nIah4`C{gj@P4=kcnbugQe_g>MoSV4x?-MP?d@i~*Mfrd3rNE|*U5Y>A zj!4J#bMX`Ts%_~xWdQndE@ilU1@P4Hugly`f^%9c{(TCBe$ru5AppLQGHc4phQF73 z^do8VDA_K^pZ?7uDW8p;!f9i<@~rmu*9~avA#< zk;p3ZH^9@Rb?R$*DZ-S|b{m^$btJp_iJ3>{?ia2TM-oiaOg4_ad*F2S4&3 zmTMuQZ;-jmT|W?~i%N5APjyDR{h^hesIMzW6`>Uc4(MFt5~Lcd-HBnHj=|jIHUcLv zv*RjH62%GhtW^fLnY}Ht+(M%ZsF0j}4W(JcHSr=#&@kyo^H2ucT$}RkjeTcaRG_DU zT+ZeA#(gnmPauYv^yE9fjR#|BqE|9|@5!q;!f7#sj7Q-ao+*%EFckEFM+;&PCQ{Qf zw99V?lFc8#G091>1d@+7e?;!LGk;XU8}zSyfbkyw{G;nx zz`j2unWsVWHb5t?L@+?kO<{#1M1G=b%IJTeLlniBdsEJ&w_AgO0^8;`-0v4>`vp_B zy|Ho+CoojOsKb+w0qO4v^v_iCbMSz#-mYmr-B~a8kFS+ z_>jiw^UUi3W}e-e|7cFR65YX|)<&I|`_qARtjouw(&xvTDSP{}RzhB&fEK53NvwN38 z1lg@DJ{EBh0z0)0`O#^8;*|%iBHqry18vl4``Lp7WJpP_62Qt!&3bWbP0wkY4Z5E% zytx#+1Bu)C+XWBna9T!aud8_eBf6LpUF@js#IK)1Z)s;?trj8`<5wtg`eBG0K}v5l z*B0SslLqxSha%7Vd5@>Lp7hN9W$Ncs4YO0c)ZMQY{r8g1Xx%RLLkUY_%V{ zA^wzIKI;CEB_h+Np{4zTX=9ar{hj-RN0;9@SlQ;GAr@9NODxvVdF~Jamd(=YPB9{P zWo09UBdUqN8#>myQf=nE{yOS6WPUS?@vHlyhkGWDWMleE8-BNrEiB=nF7zi<56M`G zT>pcyVF_IGTpVq5NVt+y9xpypa|u4ns}E9|a2(#^E2t&Tl}_Uk3Ok~JPSbxtA3WiV z*VP7eSYEq^VBq~!&UjJG5)sUE`r;9w3BKRd%+B71L=vmVB^pI@aWOYkg%ZX7P8uiI zGQa>O`HUrGMzlD*mStG?ZR1ohTQ>}qjlLL1nXLS7*wL5e{GD9YU%Xwvzkw6GXQk2; zo5ZQDr@C4%O#Wa-3d&uw*h?|W4nyS}%jmRx|D7@vr;et#6RG1Y>gW!#<`H|jYf%zZ zoYx|F^cHVu{O+0axhZZ?_tdkPXLMb?;yi<)pCHJ)WZv^lVS9;J-VfbdXPKxwuwpcQ z!uh4u<$gL`hI`Bq!8e448K1_(P^HPgeQYTr%C@bJ_^yXkro^LK**B$YYF5Ya9ci!n zB1{Y{Bymy_BO4K)u?P*}QFpP9hBfh1F<+m4?a*PTQ3FYmT)O8W5Yz z@6;^=re|2Dbd+fTf3czVyqo#6J8$>;pK_$xDrx*8l~e)CD$jxY?X#2VmKO-38>w+A zZSS%7sMiW+Vkl50pe$g|X8tvcaN=}FM>dueR+Qg~LZSEATHHIJh(wtpjBh?=DkCsEao7pzA#9mrJKnUzy~H0 z;7~N$AzSO{N*7*hR!89T`F2dW5`1HD;8uR1Wy67r!Q<@#65C>eU9PQcWr0C1Lt3&s z-<1_aReT>mo?F!az8ZPbKfy;787aDD+k(6KxSua(M6z6pD%N*~8_U5VMN{;%nJv*& zEZ=6io7g%%uLYz?LSDcRJ<10#WZO=Tfi zm-Q_gP6IPXF~ce=O$AXwS@Mk?qxm%EFM=BtNlljbVyCkpz$?n`Z#@}mKv=a0Dz9U0 zAnjY4{iIt=!7l1YDn>hz!Pqe?DzeIwn<{tvhl1#IU)0RLQD`txJ1#x-z8-*t=f-E9 z@~NYW2bbTC;jI1juLdt|7imL?;SdocOrkVcMlW3D&!BTJ_i3XG4R?DT%r;&}YOZ@J z3@2Vc@Zq2aj+|h946%NuUAeWAv4|%?Z2sNduiY;gYy9X#LF_L6a_3&C*7#;!T^Iiw zol;42*j)GOAdZ}u+lGw0&cn|#9d+84?i%hS4-HYZn4a!5^-+C*fQI}anYO8_$c{vw zo9U{|q)Dglk|(KQX#)@F4AT~}T^n(j9sbT__o5lq?e$s8=z+(GX5^Uhl>QD-9NYcu z)e+~M+WF0pPaDPY&;jII0Zs5&dRMg8gw&*7Z$MU+cxN2VO z{erwV4V4KLqf-iXz-Z5W^}J2-0<66Ibk|5*poXb8*vH&Xgan|Kfg#j~BNdt|8I!Gi zW>#+qvO|2#&8FckFm0jwX|cV$V?x}@$S~2lu0RQ;D__&?mZrO|rcktBm0l`Gx8q8A zTi%6+P{qf`TIZc)E(yuX){c1}Y@3txreNi8w0a%uxf{EwHg5jCl&ybxqE&l0Qk48@?@|jFy7iOmG7nI_Ma{+IZzU8y??6c zmSumx2BW;HpbpOR8}0@2OqHB{RzGe}cbx*RR2(&==MIpZ?tD%9Oxy$=U_o^oNQy>{9bX4MGDX;U#!&g6aVEE!^>Z!uc4 z1WR3+E7lJ&2hpiOctjcet;@z&@pCx0keNBArWC$t?$7`qL_knE`hI; zq3;9sLRX!lzmj$G(9Z0n|3o|YlAT%tVV z_04n+hdeMHqdB7X;72fXulI_ALWL}i6^xr+z_k#A3oTlyOyZ!pg9AmxqVE6p*BnC zfWSJ>x|AH&hoXJ0pc_A~BP+jh#$2Bj{AM96xQ0u_8|fbtiA}kVUlhzYGyO=C3Ul~q z7Z-OR-k^fxcOiM&2N$X@Wtc;7Uu#p3?ZR#^s)-7I?(bHJe&3Lza71CA7)+!A{5FkD zZ>FPdi3l@~>JINMITM1H2SX>A>QFX77?0S5LQ+b2!+QwMXfEm}|PY|GZ!zIz^YFzvlPipz*HC6oP^W zn6p9IKHX0y<}W1%6Vchq7@^E}d50un;ydlv*7GMH39Z^T#n6VL|F$)WNOceVlC@&H z36uBv_VEZsJ4|}~YkuQTDD{d3?hZ|rl6nD{`OAcNHu5o&FW0+FzD&VJ2`N~e&&;x~ z-OhZi(fd1$BqGJ03I5O^ICkU>?bq-XFyvD6O7b>ipAc}lDlv~`wc=sD#XcweR!+US z>#Zj9+-3e=5u-VbR{;(=eNm{AZNIK;>9l3(G;$H+_U$alrGU8L55r!7w=R_J%!S>N z3bEo~4=#ocrIxGLBpc7@=*nDYd#i~jAlr2Eeu96cTWZe( z7Ef0%mpi)Fb4@!-5Um>z1Cm|cbmYZkbrbA>?|~Xm!{89O`ku$#PUPd$Hd8XURGCLj z#%UPEb8(zS++R4VTm}U&2TtCjR~uv)zUg$wreqs)tn^%u6QMq(sI0DaCIE@XtOS== zaxM6Xi}Z9B$vImQ7p@GI+H^&Tw2RFn>aQ(X;IDUI{)q0hn0VTPH{i+$cpsm1GfU!GLHcpqBg=;I;NNh_Z-Lc3dtOQZQy-0S zET+8^w=a{*mJ~NCDlwKVr`A|ml=POK_tj|R+oH;CjBE6Lc1`5psSj7$<)Ma!(d|t1 zO6bldP192fHYJ=t{$&5D2dx?>mwM~zl=&P{3_QAF|02Lpn<*-#Y#W1p^0vFoqdM%u zCA-uVa3e+=S_}#n;|+t49?PkV4wou5YK`1H2!wHJ2Y2EWKb4L4?{X@(b@M@`R#MI$FAmdo&f0c|1&^vJ^uPV8+k8ZCksLNzXBis2(4NpF}sB}XC z=`V}W%qEkxCMG&0ZabyXu{e!6K$|Yu_h4{DOj{@vEXwr?zEHekbR_QPLvm%+-l?IN z6ot`JFna0LnGT0+6>m=HCd_RuTcSNN31!)c0ea&!$M$P~9#qhxTd|F=-L6~O?7R*Q zImEQru~_1NiEig=G#pIW=1GcDc2FYC$qyYo(qPoyxw_u=Qu900JpPEnX26QI&3jXs z%_-sPi6;V(GvUmRgdpx-DHPv|jXI<3%h9$}fTxnnkHt;$-FgHUvHdLPdPfhUK{x9H zC)HHokb{4Q4OJm7Xycdr;^y@$dAi!?R3u)i^%A%cyunc+%`fC-g8WL_UH-3%lt27* zE4T4yY?SZ@^p0#30>`->{QQmLYr3$*3{Ak!1hbd8pg(*scPH;8&okOznAsZ#e|i_S zf|($-S_ZY_8%6Xt@wHdFCj7gt1WH5qR3sGbGP?{O_;5+cxu?uEl@6G+SK(&J;+G;s z_cXvV`DRbpCUfYdK<~SkQQAg3(vzs6u5>sBn~a)zLG7pIh0k(?i>9aG)5{`FQ#FDk zN}!&@4dhKE(-4)cG?%pyyFM$q4QPs7{aAFXNg0nFZ{cHI*Ek#!*M-%Fc*|oH>IuF) zjXgRQHwHrErsC}#&s0DSn%!3lipexO`27m;$!S%Vd3^a!*h5@r&x|0=6moRd8C}>f zTWV7mk`GHh<$7Y61L1C0ZLssY|3#EfkUH^qdRi#ek6Vhc&tSfW<3dv%21ox4%NXr^ zbzyM_Jb2NZt~8#9VUGUF2pXR$F#rs{09Ie^O9HI^ZiHQ!e_apO3Q{xIQS0+l7o~Mj zj6XY`Y%q8Bk?!}ED5RMt7h73Zn$wkja7SIeAf5Tk>2$1`X^Mhp67Z5AHAHGt);qb= zIYRK=`qyJgXVTs{^U7!q53X_Q=dHUyfl$7-S(Q>fB@LoqnOZT5PM#U zWLgPfDHmb9OvZ6x_ovhjM#I^uf<2myC!mgn(>vKdxaN$G1Vc}eVsoQs`gfaEK|obL zi?eH|f#=&Wv1qv?B1rP39sq2<3qidSPVnMj1U1-@>-lujpUPZ*X40M@ROqpATCRJh z2)hXbpmG}X5|dx#;S}I^MlV(Grs&^G89&njf|%mh=1Qvswa_ncM}on?I_2BXRy1A? z``z77rEcIC$KZ$0@vvBzxL!>&=$!y7Vp!4fs;KukB;B;8@@b~eOcv~g+e!T9+70b0pDCzN*zjP7l|(2?Qs3uo z5wT9a?w^r13xnGB1HjYK6IICz@vqU5QhcuSGBnMDbggkrDk10lD4gO3=pxs@qXGzl z*eZMWhlTu!^VN0AZ?Jmj`IA;z$enH-7I98^vCfxVJ4|Tw=Kl9H)}s6`U3tOKC)XiDk*}H<^P;Mun6`hez4~_vc8hsUxj^!d<=42mQ;1BqC2zd(h z!_FN05=^6vEtjL(!#C7vtEBq(>BUTQ<@uZrzkV#B=K?I8q1Yl#90|x>DXW)Rt@cw+ zU|YGhs*P^#Ppk$UuW&88W%_|+x9A(NkT!5#ke(&O>^Gb-Iy*nqrfk4AIHCj~rMh|a z5KoP{NXG3|pI`1!4)ezn4%2C^$bn;x6y%ExuM3oagvYYWBv2cdR5?q5hzc$n3(~u- za(ftFwO)q@b1%8K4)q&7I3sY{(i;gXf64rLlM8bB$mcySa1kG9i6Ubk_Ph;>kuI|1 zJwP=#vWVADL49+Q|1qD=&JREJm=-remRp%4J?OwYd6_Ae0Q=dgMB3yWlt2!dU6D;M z45D%vH3@$RzuT{OB1h^~&dNWwGA~K1S8vPPJ;QW)x96w>zjUXATuzd27SET=aiI`h zhj&(bPuB?CTpQS_G6?@Y)2sY`CRCSUG2Zn792Sd#o`K16`+8J&df(juRR1p7%%>KP znpflziblof4v%z1m1*qB)1XbsOO<(FR;AMd#*Vx*))9hJfbQxk%p{g?T(E)${GqRF zS1IYqGKl+g*QJKKQsYBb&9y1T;kTZJmC1Uq^}fC6!iK;+H7-#&A12^0k+5JAMYDp+ z!~GX&tEY8%lUmQ$dc9eWwtqAt9&;5UqrwiN249#9BNvurFLX`Q`h%NtaF+ea%B&R8 zW387r2mE)*;PJIO{*R(Yz7GX<|yDa@OQ>Ijf&j> zjuZ5%M%$m9>VyTegBL~jI0)g%6$SfUL6!B-1LY=aCSg(8$9Vq{<~{tY{n0CT$i%J? z`f-#N$-$^|LpgNt!|>Jv%d_IRw+P~S7w^H=oCC${h~F>Xk9No< zS&{Ne{(i-#!U=jDET)*%m*7XXlQ}|PwrzHKy}J`CO7xQdwc!38L0`8$K(jc8MUH_{ z76&OBDX{&Z$IiWUawKqEkJOu4ch2=-Z<-XFR~CGSlP1tWEL20P=q5k>yo-1?o{kP3 ztNXiWI^wtja+-k6`QKUEH`M;p44AhY0^+t?iG1S}Z5wI^mw4a(!OXr}O#bPJJG}li z?q!Bbdr&Ue+U`vuVDMcjP~r)b3+<0(A0F^^v>76Qdfd7Y@>L$#rC&2&0{jEPhW~A= z?D_Q;axdX6ih&uXGqQvG($xs<7w29g@5K1fb(cUtr=Ke|U{^@hU?Qa<|~zj1{aEvVsczvWx()TUNv+Xzn2GUxkWDYNl1%auY;ZJbo`gJpQkF% zZl;pLe?X=ahn(G~*<9tKU$2Q68!3#cKA~91(_UJ-4LJ?R>f-X~^rUP16^NdC#Y^9? zNF)A6hMjNIS5a5*x$u>>3>abvS|y&NiDy|2o=Aj#)>zU}+$g&|OSV0eCh?Nrd4=O6 zh@DjAWqPo06SJ*Vv}l8#cQ<*&3UWjFjf2YX5tp+>mFiY847^_LT2unk{2a7Nb>BZ| zmqZ?`6Kww=kI2KyN>0u=xZLrI_m!L&RVi6lKDlHkxv0cyU&oX&HC!urwF=J_CbtbR z;PKer52xEy+Rqca&si7fS{r3w2k!He$ogT^u6q-5B`Kl#M6ZE+eS zbO=ZfOO_ZfgNQ}iLe??#TCrT|wQq-R5o>w1vjK-%@Jno)p>uT0Zofn3V!lIiW18z8 z`bqpR;XxoDk>@(+aGXP1e>Y#2_<-3;8Bu1{;es<77oTcfzQ(4IOEFLdxIW!em|UP# z13u^WKY8xV_Oqo;%r#?Tk3ll~NMk-k8c(k5Z~vGqsEc93@Wr>4qq}_vf5+Bv zq09Sgi6qkD6I_fuEw)6yHhrY@ycGkkcB_01EMecq+)H?j&JzkQ`jP#$egA4X8*1Sox4Ji zQ_^;k>vNFAV|WTlo%}eI_N~n>fKB6{-ZA?L3L3|iYs)){U@0}1T_~C|aUx%%=-*7P zw$JEp8A1P8`F4SxKp@obJunrpHh6!rNi(~?FQHFPG4E;(atdotxSop?^3Jg+2|{@E zc_XvJT5Olk`^sVfR{^s-ys!iov%)vzj!TT^!QZfNmv>8S%vgA7v~U^0vR@b6bggDJ z*c(r~DVagu+xdRRqgEu9%>Xsa+`DwAQH>`QK6qxaXPFlQ=G;OD?iUg`S$lsCQ#8oU z)EH?!BIhV?oYZLVwi&J#lz@G857z8`$}!4b1;o29YRgC9>qGZuCCf`~$5(06!6?T} zK#bay|6wp8+3JaWQ`lXqMYcgcaS48JK5?cFX}r4R!ATs;N0CHPlRtJe$b#j^N^7Fz zB!kh-T-v|mLt#I)jRtDHn^HGvwa`K2HXIX6wBA4ZCSm3B%)-(*-SLIG@uwoPB8V}~ z>%Y$I^Fb)wbn3-&gF7ZRp9n_r&?ccrX0J@s%#;K*a0KHJ__-@$_Lt?yZ29}zr+@-j zmWbuLZMsLhSR?$K{)nx~>=@#dMva2IgEsH(g%;>0xrEgM<+2Nu38OI0uy<}^Kz|o;=aUqRAD%5_Fr@TYE-J% zCt;T+Yb`0@y(#oO5E4&MumvUn$`54wu@~L6+VRywuz6ly_(r=Bg#UurYdlnO7=&rP z+*_7#k?A-MB;M|5S>`=EI9b>gdwXJw>oPgHMco#Cdo zL)Qw`SgJWd33hJ%(H`PB1wf$;tA0H{zX~m(Vb8xa;I|LjZ~qaKr>D#4nvH?Zsoa`W zpMbZ%o%}zYU3pwm+qcK&t0vtNOH;J2Wm0LGIYnv?*EGifEgZ|J(9kpooG>+{tC@An zp`6kb6$Q-+#ll%`DyiUfF|#zuAw?Wg9N)p;d!P5$`{$j%&gY!H_t|@|VXw3HckNY9 zb`@G|6@7tUyl=VNi?VMeV6}GJ*(1)&`B13m(BV^k_?-+Y)iuHKOLtI&_rJ@grO7US z1S*k#Nk|v7Y^e^#g+1{_Q|bOB>8YIQ-9vF_nSj1&D^55Ydkv z>_S8&(Y~cHscQwqPJySm`omBL>`ngOs_YnZV3{{=KT*LKjIYVE87+PH~H||l5Yq59Qag%r^78bEVr$KD2>m57B93`x? zRILuU!5#$$a4g!79Yi~NU;GGc=^AFa7mID-pWux(Xbxkbv35$X#nEa-wR^sAEN=Sx zsTXk6>q=i1KC^{4@x>%-Sb0lP$Q4><8=09nZH`>J;W|!{dfMT*1Dx1kUomud_zQ1) zj$xn4JQZIqRz+2pcgJizOAWpb|I{cjM;82BFmv+i_{5`kA=p7uw}(nqS0^x+VhxSY z8n}gAFOU=H4ozATQ=IY4U%^{cz);3T4Gx2(4v$+^T3eU;;q4%BqFR2g-M5h2f<&*GtG4Q(~#oFF`r<_P6%$C^0kCuQrn$3e?sAyd|X{ zftR-1-(nAdm7I4JY-2-yloL8l+AW0kVzK_Jjj|1h%$7-AyLA50!_)StU7FWYNGuO& zUh-PYB9Bxv=`u|^Rgo@hXD|LT#R04T5EdZ^x2&kcgO7lR3guD`s6F6?2qiu?YxZEP9Sj;(xph;nDmvU0g<<+tU*^s1hMV-LPwJ)QvTHDlcrp7 zQg(C%L#tpcCnA;vvH9U8bvn2uQtZkDwZ65=sk`r-%_ovmM}oJKWp%#x*#xL#09^2HHc+?)uBq2X>d$U}_ZEJ+iEs^^Si@$(EdvakY5Tk` zES(&us7{&v=4Acj+bcP=CU8_T@S%s0^0&+dz&<=Qr|R&;(kT=5%^veCc<(3f=8f#Y zTzr(68SjTP%0_l`cDpx}|6ZttZ(sn6TR7VN-NzH9vO>%f7)a*<0?7d>1mX`Pf2+_) zYQW|~8mXtf*>!wZ8}olZB~Qr9p-FM9EyH0xFUH3Rj~MSgY>D6M+0N3p^edkrEmx*b41xI?1P_O`?7)!99kFjDzwVU_ji&i}60%hK zCL+g>v#H+{Qn7z~XO6oGio>+AZcfsY@ z=h!p=WfCbyL4eL22zmrm|AR24vz5d1 z@P%6wVitr95-*Dxz|R2X%%rhBdl$V;IWy<6%`vXzx4&UFuN{@foxWMA_;38vxI|18 zi$Cc2O$(2SCeNOXEAw`BylQ@t<;KY3p)1sV1|N*woOVvZNXC;`IVL!K3y^i@=!Hoh zY8Q2CN$S(0We!EQ@anje9M_&f)6uBhmV@nNr&zp?#!C~%?6}$d%y**v=;MutICs#? zRf}F?QS|T#$_=bRfqct-!tvHaA8-%yt>S6-CMoFfP#}MnfE90ZIqhb#WDXRX>*sXZ zl*j=>H>QsHYF>+V0vcJmza`JcLAR0&Ei0y)Jp2ABF?4i2(u;xepJQ!-$IJr&KSp1(?T zNIEv8md}3?2&r~KqBv`D=^Hd*k&z>4GwfC0d2JaR&tAc{EB=K<-$CCk(MFfP%?Fm# zY^^;W4ZyBUQj_Ku*J)`bHPjfwcYzoye@c}c+PEO@^c$q(uKtDDL!f^|_r+2j>9e)F z|8TnOZH}1NrJpXIkp-p3XVL9j?1ftqL*vabJ0m`%@K7>g^7-a3QG*?qA#$*shA&aJ!ckoOE%j?wz&cN|SbR zOE2bc4o#*hGuN(xFpplffkL-~1oJ*p?UKR5=VF~Y-U7S!znG~o^HKgciFm$;^pIiO zna%i)qmbt+aq8p^O^p}jKLuWn2KYRCh|%FYTOzj|G!3th&i@;5nrsyBmlJ5dXIdMY zLoP|7_U!gH^s38m^8l>tkSYH<6`-de0C;p8GxoD%otRt|cC;|i?jki{kIom;HG6rn zihJleXO*2}&HD|~=-jRppna2+HX0m9gGwIi8Pf8@*CV zU3vFI8+i+e&+DS=_Zfk{n!#1jjSWoLal-A$wrx0UJ62lg5gm7=uQAQ7wQIvLX}K1m z?+Da*?6l)!y97ur8Tri;{I%eat?BOP!ng^!6p6kG1u0zTWa|NC$uV8T<2rq!C;x5g zZrgxu#uHbiAWyuU1@fLVKTBm~Z}JOgw;_j8p>HQLWiX8^xD0A<#POA4LGy5%(zA+; znajh%?V)>%hPwq9|1}vE&I)@W`&ORDc55$dv$jhosAUB2JxFp`*7hD)9z46?{G4w( zdbL?H&Mw|!I4nlS)fU&E&{`Cr{AV^!-AM>!LEzp|GE>L`m(V)%$qo6gL(BCNi{6Kb zn@=0*uHgKQg^icw?RM+2?jO3~D|!(G2x^{;5lnp~p&qxUInyNbJO?Whu+EPM^POZb zF4XMBk^GG2o&}^!;>tD1@l}HXa42du>KAUEDX@S#q=YX#6Lk--hxvWRxR+~~z2e&sIcvdc~8O9bA$QU9(` zYMKnF0GSeHahB`QnrD5QjYq01pNVtf%bmN2xvP)OJCZ*guv$GCYbmw-IUqzLZgD#=73Z(v^qT$5IB|%=(SnjG{lFllSC?rcSW5uT}rPksp5O2`^K9o>U zSD2GCkvMj*;}1%*P3{eU(XGR{IZqpRl-pq-l%T3t+a+u4JI^-eN2jDa%b_EHrZ!mapDzQmc=qd2njs{$}r z^^-f=7=L>Z*+t$x2Gje4UA{^6xlUKV#iJ$a#G-9|#&K4$*LM327LShFOQc^&o%K6G zNsfx2g6k&o`qtl=4jmWP+hs?<;@aJSB-l~veQf#%M%*Rz?^oz_7>?dlFg^QvM(-X< z{&!L#M+ZDh) zUn&1-^NTicf*5udL%}4!6|CU>=P#K3JIXRsAlshm9DJz`x^IPu<=@CszAe}tnh$#0bZmhuSNqd=SUWl-e3Ry!YVizz^f{_MmZwbXKOyN9;;%1v_S? z!!CEtVwI7cm3*h;pe#ysIqio3$npbG+~&pLk)qS6mG@=)P3QZOAisgA{SvHoa?xWrs}V1YR@#6*16G#S=J+<%=~#zN^sDgJA?%#)Q+N7 zC07282Rv!SvCPK_QytFk6@~9B^ZZ&4>^VyzsA;S+LcGtVZ+^hK6jpIn2wEmU8jhSo zfGn~OL&Lnd(Sku+iLV}KoBkqNz;3?@lt;iHUna#zA}X-Sxi>h+UvadDCgk6f`C5TH z%}^4~7NeK*FMNs|!t;t>=zR#Bdi(KN`^U;xgb6LMY4P?==FtxvNAq?y2n$gu?c(yI zCd9fVyh1-lImJ0;`%5H18x3ZsF0R&r;d*}B$hFF}AG1texBAb3Foc(+P+}>C1M>?< zP35r91m0v866!*GbvCC==d*M!eLCOpq@C&Wg(v+Z(pSNW!mdu5X+oZ+>peLN+3XAY zmsO_b7Kp6KU{S~=Aba)OciU8pDoz`xN&Z|YzBzRL3g!)Jr#Cl%)p$Ky*n|&U+(*Nb5oYX4c)6gwMa^Sq{LsxEm^d z`beBW1FG`9_27Rq9h3>~8LLHc*7JR`rpuHVRZXn-Z)Q|#*~>isC0D-M%zPjfG_p*Y zd#=-@{nvcK-9fGFzxd@oul_1g3q<%aC{-3z@3FrOU3vwgVTdabf*DH@wA}DllgtC4 z*-IIQ7`x9jS;_(`p(({VYhAIvwdJH-O~;`6AM@WPC1dd9Qc9O21B^HrNF-!UIF6S) zop>1n8|_(w_=Ue_Ei7^UQ0Bb>tfB|7dz9voBFc$o6BBNcs4E<+u&QV*U6anErCs~l zN1Yn)3j9j+nYnOQ+q$>6R&R)zUJ+hF<3t`5{Pmzp%Ch1lDl6W-LnsF_wt_Wz|2|T`?JUwhr|= z8TGo3B@DQ3tB@60GF|mEglU)0;Vc#c=~hPc&T9&nD2+cZsIiBOa(om)&yw1$Z=T@V zoojZvo2t_^HXa6(hL9c~ynEOkMbg4)b}i^)mKA7)U#;_TW=Kv8Qd$@Ri|8d)#>F#Td%AU=ulgt`(6FmlwH=sb zxZ2EiUVp%>Yvpit)#@c=N+MLts4X6zI2)noc;dWfW+e6td>C8)J}ueQDul$;y@k2N zEnLjS+NHcGDbZ2ep`BTsK2o+X!8y{dJ)POEuIr+~5lk+HM_@y5)|?=iAe-(Ez-r)b z>sIljMK2Cp_`DVyh#nS6=TM51wxJuVNF2wasUr;p;!?>t*6LEX{4rXeJ)f{{;^B*jE4m literal 0 HcmV?d00001 diff --git a/docs/source/methods/geometry.rst b/docs/source/methods/geometry.rst index 65f5e9fe31..d0d556f17c 100644 --- a/docs/source/methods/geometry.rst +++ b/docs/source/methods/geometry.rst @@ -617,7 +617,7 @@ condition has been applied, the particle is killed and any surface current tallies are scored to as needed. If a reflective boundary condition has been applied to the surface, surface current tallies are scored to and then the particle's direction is changed according to the procedure in :ref:`reflection`. -Note that the white boundary condition can be considered as the speciall case of +Note that the white boundary condition can be considered as the special case of reflective boundary condition, where the same processing method will be applied to deal with the surface current tallies scoring, except for determining the changes of particle's direction according to the procedures in :ref:`white`. @@ -901,37 +901,63 @@ Dxy + Eyz + Fxz + Gx + Hy + Jz + K = 0`. Thus, the gradient to the surface is ------------------------------ White Boundary Conditions ------------------------------ -The white boundary condition is usually applied in deterministic code, where the particle -will hit the surface and travel back with isotropic angular distribution. Essentially, the -change of particle's direction will comply to the cosine distribution instead of uniform -distribution. -The probability distribution function (pdf) for the reflected direction can be expressed +The white boundary condition is usually applied in deterministic codes, where the particle +will hit the surface and travel back with isotropic angular distribution. The change in +particle's direction is sampled from a cosine distribution instead of uniform. +Figure :num:`fig-cosine-dist` shows an example of cosine-distribution reflection on the +arbitrary surface relative to the surface normal. + +.. _fig-cosine-dist: + +.. figure:: ../_images/cosine-dist.png + :align: center + :figclass: align-center + + Cosine-distribution reflection on the arbitrary surface. + +The probability density function (pdf) for the reflected direction can be expressed as follows, .. math:: : label: white-reflection-pdf + + f(\mu, \phi) = frac{\miu}{\pi} d\miu d\phi = 2\mu d\mu frac{d\phi}{2\pi} + \mu \in [0, 1] + \phi \in [0, 2\pi] - -where mu is the cosine of the polar angle between reflected direction and the normal to the -surface; and theta is the azimuthal angle. +where :math:`{mu = cos(\theta)}` is the cosine of the polar angle between reflected direction +and the normal to the surface; and :math: `{\theta}` is the azimuthal angle. Thus, the cosine of the polar angle can extracted like this, .. math:: : label: white-reflection-cosine + + f(\mu)d\mu = 2\mu d\mu + and the azimuthal angle is uniform in the range of 2*PI, .. math:: : label: white-reflection-unifrm + + f(\phi) = frac{d\phi}{2\pi} -Then, cosine can be obtained by analytical inversion of cumulative probability distribution (cdf) +Then, the cosine can be obtained by analytical inversion of cumulative density distribution (cdf) like this, .. math:: : label: white-reflection-sqrt-prn + + \mu = \sqrt{\eta_(1)} + \phi = 2\pi \eta_(2) +where :math: `{\eta}` is the uniform random number, simply computed from random number generator. Eventually, the final reflected direction vector can be computed via the rotation of normal to the surface like this, .. math:: : label: white-reflection-rotation -The white boundary condition can apply to any kind of surface, as long as the normal to the surface + u^' = u \mu + frac{uw \sqrt{1-\mu^2} cos(\phi) - v \sqrt{1-\mu^2}sin(\phi)}{\sqrt{1-w^2}} + v^' = v \mu + frac{vw \sqrt{1-\mu^2} cos(phi) + u \sqrt{1-\mu^2} sin(phi)}{\sqrt{1-w^2}} + w^' = w \mu - \sqrt{1-w^2} \sqrt{1-\mu^2} cos(\phi) + +The white reflection boundary can apply to any kind of surface, as long as the normal to the surface is known as mentioned above in :ref:`reflection`. .. _constructive solid geometry: http://en.wikipedia.org/wiki/Constructive_solid_geometry From 781cb0eef7c65a2a45dfd3b8e5eed7c932cd9751 Mon Sep 17 00:00:00 2001 From: rockfool Date: Mon, 16 Sep 2019 13:46:51 -0400 Subject: [PATCH 039/158] documentation issues fixed --- docs/source/methods/geometry.rst | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/source/methods/geometry.rst b/docs/source/methods/geometry.rst index d0d556f17c..ca45e035d3 100644 --- a/docs/source/methods/geometry.rst +++ b/docs/source/methods/geometry.rst @@ -920,11 +920,11 @@ as follows, .. math:: : label: white-reflection-pdf - f(\mu, \phi) = frac{\miu}{\pi} d\miu d\phi = 2\mu d\mu frac{d\phi}{2\pi} + f(\mu, \phi) = frac{\mu}{\pi} d\mu d\phi = 2\mu d\mu frac{d\phi}{2\pi} \mu \in [0, 1] \phi \in [0, 2\pi] -where :math:`{mu = cos(\theta)}` is the cosine of the polar angle between reflected direction +where :math:`{\mu = cos(\theta)}` is the cosine of the polar angle between reflected direction and the normal to the surface; and :math: `{\theta}` is the azimuthal angle. Thus, the cosine of the polar angle can extracted like this, .. math:: @@ -933,29 +933,29 @@ Thus, the cosine of the polar angle can extracted like this, f(\mu)d\mu = 2\mu d\mu -and the azimuthal angle is uniform in the range of 2*PI, +and the azimuthal angle is uniform, .. math:: : label: white-reflection-unifrm f(\phi) = frac{d\phi}{2\pi} -Then, the cosine can be obtained by analytical inversion of cumulative density distribution (cdf) +Then, the cosine can be sampled by analytical inversion of cumulative density distribution (cdf) like this, .. math:: : label: white-reflection-sqrt-prn - \mu = \sqrt{\eta_(1)} + \mu = sqrt{\eta_(1)} \phi = 2\pi \eta_(2) -where :math: `{\eta}` is the uniform random number, simply computed from random number generator. +where :math: `{\eta}` is the uniform random number, simply computed by random number generator. Eventually, the final reflected direction vector can be computed via the rotation of normal to the surface like this, .. math:: : label: white-reflection-rotation - u^' = u \mu + frac{uw \sqrt{1-\mu^2} cos(\phi) - v \sqrt{1-\mu^2}sin(\phi)}{\sqrt{1-w^2}} - v^' = v \mu + frac{vw \sqrt{1-\mu^2} cos(phi) + u \sqrt{1-\mu^2} sin(phi)}{\sqrt{1-w^2}} - w^' = w \mu - \sqrt{1-w^2} \sqrt{1-\mu^2} cos(\phi) + u^' = u \mu + frac{uw sqrt{1-\mu^2} cos(\phi) - v sqrt{1-\mu^2}sin(\phi)}{sqrt{1-w^2}} + v^' = v \mu + frac{vw sqrt{1-\mu^2} cos(phi) + u sqrt{1-\mu^2} sin(phi)}{sqrt{1-w^2}} + w^' = w \mu - sqrt{1-w^2} sqrt{1-\mu^2} cos(\phi) The white reflection boundary can apply to any kind of surface, as long as the normal to the surface is known as mentioned above in :ref:`reflection`. From d0eb84dd4e5c8132cbe80a0fa683034846a29468 Mon Sep 17 00:00:00 2001 From: rockfool Date: Mon, 16 Sep 2019 14:12:54 -0400 Subject: [PATCH 040/158] fix the conflict in surface.py --- openmc/surface.py | 2265 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2265 insertions(+) create mode 100644 openmc/surface.py diff --git a/openmc/surface.py b/openmc/surface.py new file mode 100644 index 0000000000..dc251485d2 --- /dev/null +++ b/openmc/surface.py @@ -0,0 +1,2265 @@ +from abc import ABCMeta, abstractmethod +from collections import OrderedDict +from copy import deepcopy +from numbers import Real, Integral +from xml.etree import ElementTree as ET +from warnings import warn + +import numpy as np + +from openmc.checkvalue import check_type, check_value +from openmc.region import Region, Intersection, Union +from openmc.mixin import IDManagerMixin + + +_BOUNDARY_TYPES = ['transmission', 'vacuum', 'reflective', 'periodic'] + +_WARNING_UPPER = """\ +"{}(...) accepts an argument named '{}', not '{}'. Future versions of OpenMC \ +will not accept the capitalized version.\ +""" + + +class Surface(IDManagerMixin, metaclass=ABCMeta): + """An implicit surface with an associated boundary condition. + + An implicit surface is defined as the set of zeros of a function of the + three Cartesian coordinates. Surfaces in OpenMC are limited to a set of + algebraic surfaces, i.e., surfaces that are polynomial in x, y, and z. + + Parameters + ---------- + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. + boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'}, optional + Boundary condition that defines the behavior for particles hitting the + surface. Defaults to transmissive boundary condition where particles + freely pass through the surface. Note that periodic boundary conditions + can only be applied to x-, y-, and z-planes, and only axis-aligned + periodicity is supported. + name : str, optional + Name of the surface. If not specified, the name will be the empty + string. + + Attributes + ---------- + boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'} + Boundary condition that defines the behavior for particles hitting the + surface. + coefficients : dict + Dictionary of surface coefficients + id : int + Unique identifier for the surface + name : str + Name of the surface + type : str + Type of the surface + + """ + + next_id = 1 + used_ids = set() + + def __init__(self, surface_id=None, boundary_type='transmission', name=''): + self.id = surface_id + self.name = name + self.boundary_type = boundary_type + + # A dictionary of the quadratic surface coefficients + # Key - coefficient name + # Value - coefficient value + self._coefficients = {} + + def __neg__(self): + return Halfspace(self, '-') + + def __pos__(self): + return Halfspace(self, '+') + + def __repr__(self): + string = 'Surface\n' + string += '{0: <16}{1}{2}\n'.format('\tID', '=\t', self._id) + string += '{0: <16}{1}{2}\n'.format('\tName', '=\t', self._name) + string += '{0: <16}{1}{2}\n'.format('\tType', '=\t', self._type) + string += '{0: <16}{1}{2}\n'.format('\tBoundary', '=\t', self._boundary_type) + + coefficients = '{0: <16}'.format('\tCoefficients') + '\n' + + for coeff in self._coefficients: + coefficients += '{0: <16}{1}{2}\n'.format( + coeff, '=\t', self._coefficients[coeff]) + + string += coefficients + + return string + + @property + def name(self): + return self._name + + @property + def type(self): + return self._type + + @property + def boundary_type(self): + return self._boundary_type + + @property + def coefficients(self): + return self._coefficients + + @name.setter + def name(self, name): + if name is not None: + check_type('surface name', name, str) + self._name = name + else: + self._name = '' + + @boundary_type.setter + def boundary_type(self, boundary_type): + check_type('boundary type', boundary_type, str) + check_value('boundary type', boundary_type, _BOUNDARY_TYPES) + self._boundary_type = boundary_type + + def bounding_box(self, side): + """Determine an axis-aligned bounding box. + + An axis-aligned bounding box for surface half-spaces is represented by + its lower-left and upper-right coordinates. If the half-space is + unbounded in a particular direction, numpy.inf is used to represent + infinity. + + Parameters + ---------- + side : {'+', '-'} + Indicates the negative or positive half-space + + Returns + ------- + numpy.ndarray + Lower-left coordinates of the axis-aligned bounding box for the + desired half-space + numpy.ndarray + Upper-right coordinates of the axis-aligned bounding box for the + desired half-space + + """ + return (np.array([-np.inf, -np.inf, -np.inf]), + np.array([np.inf, np.inf, np.inf])) + + def clone(self, memo=None): + """Create a copy of this surface with a new unique ID. + + Parameters + ---------- + memo : dict or None + A nested dictionary of previously cloned objects. This parameter + is used internally and should not be specified by the user. + + Returns + ------- + clone : openmc.Surface + The clone of this surface + + """ + + if memo is None: + memo = {} + + # If no nemoize'd clone exists, instantiate one + if self not in memo: + clone = deepcopy(self) + clone.id = None + + # Memoize the clone + memo[self] = clone + + return memo[self] + + @abstractmethod + def evaluate(self, point): + pass + + @abstractmethod + def translate(self, vector): + pass + + def to_xml_element(self): + """Return XML representation of the surface + + Returns + ------- + element : xml.etree.ElementTree.Element + XML element containing source data + + """ + element = ET.Element("surface") + element.set("id", str(self._id)) + + if len(self._name) > 0: + element.set("name", str(self._name)) + + element.set("type", self._type) + if self.boundary_type != 'transmission': + element.set("boundary", self.boundary_type) + element.set("coeffs", ' '.join([str(self._coefficients.setdefault(key, 0.0)) + for key in self._coeff_keys])) + + return element + + @staticmethod + def from_xml_element(elem): + """Generate surface from an XML element + + Parameters + ---------- + elem : xml.etree.ElementTree.Element + XML element + + Returns + ------- + openmc.Surface + Instance of a surface subclass + + """ + + # Determine appropriate class + surf_type = elem.get('type') + surface_classes = { + 'plane': Plane, + 'x-plane': XPlane, + 'y-plane': YPlane, + 'z-plane': ZPlane, + 'x-cylinder': XCylinder, + 'y-cylinder': YCylinder, + 'z-cylinder': ZCylinder, + 'sphere': Sphere, + 'x-cone': XCone, + 'y-cone': YCone, + 'z-cone': ZCone, + 'quadric': Quadric, + } + cls = surface_classes[surf_type] + + # Determine ID, boundary type, coefficients + kwargs = {} + kwargs['surface_id'] = int(elem.get('id')) + kwargs['boundary_type'] = elem.get('boundary', 'transmission') + coeffs = [float(x) for x in elem.get('coeffs').split()] + kwargs.update(dict(zip(cls._coeff_keys, coeffs))) + + return cls(**kwargs) + + @staticmethod + def from_hdf5(group): + """Create surface from HDF5 group + + Parameters + ---------- + group : h5py.Group + Group in HDF5 file + + Returns + ------- + openmc.Surface + Instance of surface subclass + + """ + surface_id = int(group.name.split('/')[-1].lstrip('surface ')) + name = group['name'][()].decode() if 'name' in group else '' + surf_type = group['type'][()].decode() + bc = group['boundary_type'][()].decode() + coeffs = group['coefficients'][...] + + # Create the Surface based on its type + if surf_type == 'x-plane': + x0 = coeffs[0] + surface = XPlane(surface_id, bc, x0, name) + + elif surf_type == 'y-plane': + y0 = coeffs[0] + surface = YPlane(surface_id, bc, y0, name) + + elif surf_type == 'z-plane': + z0 = coeffs[0] + surface = ZPlane(surface_id, bc, z0, name) + + elif surf_type == 'plane': + A, B, C, D = coeffs + surface = Plane(surface_id, bc, A, B, C, D, name) + + elif surf_type == 'x-cylinder': + y0, z0, r = coeffs + surface = XCylinder(surface_id, bc, y0, z0, r, name) + + elif surf_type == 'y-cylinder': + x0, z0, r = coeffs + surface = YCylinder(surface_id, bc, x0, z0, r, name) + + elif surf_type == 'z-cylinder': + x0, y0, r = coeffs + surface = ZCylinder(surface_id, bc, x0, y0, r, name) + + elif surf_type == 'sphere': + x0, y0, z0, r = coeffs + surface = Sphere(surface_id, bc, x0, y0, z0, r, name) + + elif surf_type in ['x-cone', 'y-cone', 'z-cone']: + x0, y0, z0, r2 = coeffs + if surf_type == 'x-cone': + surface = XCone(surface_id, bc, x0, y0, z0, r2, name) + elif surf_type == 'y-cone': + surface = YCone(surface_id, bc, x0, y0, z0, r2, name) + elif surf_type == 'z-cone': + surface = ZCone(surface_id, bc, x0, y0, z0, r2, name) + + elif surf_type == 'quadric': + a, b, c, d, e, f, g, h, j, k = coeffs + surface = Quadric(surface_id, bc, a, b, c, d, e, f, g, + h, j, k, name) + + return surface + + +class Plane(Surface): + """An arbitrary plane of the form :math:`Ax + By + Cz = D`. + + Parameters + ---------- + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. + boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + Boundary condition that defines the behavior for particles hitting the + surface. Defaults to transmissive boundary condition where particles + freely pass through the surface. + a : float, optional + The 'A' parameter for the plane. Defaults to 1. + b : float, optional + The 'B' parameter for the plane. Defaults to 0. + c : float, optional + The 'C' parameter for the plane. Defaults to 0. + d : float, optional + The 'D' parameter for the plane. Defaults to 0. + name : str, optional + Name of the plane. If not specified, the name will be the empty string. + + Attributes + ---------- + a : float + The 'A' parameter for the plane + b : float + The 'B' parameter for the plane + c : float + The 'C' parameter for the plane + d : float + The 'D' parameter for the plane + boundary_type : {'transmission, 'vacuum', 'reflective'} + Boundary condition that defines the behavior for particles hitting the + surface. + periodic_surface : openmc.Surface + If a periodic boundary condition is used, the surface with which this + one is periodic with + coefficients : dict + Dictionary of surface coefficients + id : int + Unique identifier for the surface + name : str + Name of the surface + type : str + Type of the surface + + """ + + _type = 'plane' + _coeff_keys = ('a', 'b', 'c', 'd') + + def __init__(self, surface_id=None, boundary_type='transmission', + a=1., b=0., c=0., d=0., name='', **kwargs): + super().__init__(surface_id, boundary_type, name=name) + self._periodic_surface = None + self.a = a + self.b = b + self.c = c + self.d = d + for k, v in kwargs.items(): + if k in 'ABCD': + warn(_WARNING_UPPER.format(type(self).__name__, k.lower(), k), + FutureWarning) + setattr(self, k.lower(), v) + + @property + def a(self): + return self.coefficients['a'] + + @property + def b(self): + return self.coefficients['b'] + + @property + def c(self): + return self.coefficients['c'] + + @property + def d(self): + return self.coefficients['d'] + + @property + def periodic_surface(self): + return self._periodic_surface + + @a.setter + def a(self, a): + check_type('A coefficient', a, Real) + self._coefficients['a'] = a + + @b.setter + def b(self, b): + check_type('B coefficient', b, Real) + self._coefficients['b'] = b + + @c.setter + def c(self, c): + check_type('C coefficient', c, Real) + self._coefficients['c'] = c + + @d.setter + def d(self, d): + check_type('D coefficient', d, Real) + self._coefficients['d'] = d + + @periodic_surface.setter + def periodic_surface(self, periodic_surface): + check_type('periodic surface', periodic_surface, Plane) + self._periodic_surface = periodic_surface + periodic_surface._periodic_surface = self + + def evaluate(self, point): + """Evaluate the surface equation at a given point. + + Parameters + ---------- + point : 3-tuple of float + The Cartesian coordinates, :math:`(x',y',z')`, at which the surface + equation should be evaluated. + + Returns + ------- + float + :math:`Ax' + By' + Cz' - D` + + """ + + x, y, z = point + return self.a*x + self.b*y + self.c*z - self.d + + def translate(self, vector): + """Translate surface in given direction + + Parameters + ---------- + vector : iterable of float + Direction in which surface should be translated + + Returns + ------- + openmc.Plane + Translated surface + + """ + vx, vy, vz = vector + d = self.d + self.a*vx + self.b*vy + self.c*vz + if d == self.d: + return self + else: + return type(self)(a=self.a, b=self.b, c=self.c, d=d) + + def to_xml_element(self): + """Return XML representation of the surface + + Returns + ------- + element : xml.etree.ElementTree.Element + XML element containing source data + + """ + element = super().to_xml_element() + + # Add periodic surface pair information + if self.boundary_type == 'periodic': + if self.periodic_surface is not None: + element.set("periodic_surface_id", str(self.periodic_surface.id)) + return element + + @classmethod + def from_points(cls, p1, p2, p3, **kwargs): + """Return a plane given three points that pass through it. + + Parameters + ---------- + p1, p2, p3 : 3-tuples + Points that pass through the plane + kwargs : dict + Keyword arguments passed to the :class:`Plane` constructor + + Returns + ------- + Plane + Plane that passes through the three points + + """ + # Convert to numpy arrays + p1 = np.asarray(p1) + p2 = np.asarray(p2) + p3 = np.asarray(p3) + + # Find normal vector to plane by taking cross product of two vectors + # connecting p1->p2 and p1->p3 + n = np.cross(p2 - p1, p3 - p1) + + # The equation of the plane will by n·( - p1) = 0. Determine + # coefficients a, b, c, and d based on that + a, b, c = n + d = np.dot(n, p1) + return cls(a=a, b=b, c=c, d=d, **kwargs) + + +class XPlane(Plane): + """A plane perpendicular to the x axis of the form :math:`x - x_0 = 0` + + Parameters + ---------- + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. + boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'}, optional + Boundary condition that defines the behavior for particles hitting the + surface. Defaults to transmissive boundary condition where particles + freely pass through the surface. Only axis-aligned periodicity is + supported, i.e., x-planes can only be paired with x-planes. + x0 : float, optional + Location of the plane. Defaults to 0. + name : str, optional + Name of the plane. If not specified, the name will be the empty string. + + Attributes + ---------- + x0 : float + Location of the plane + boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'} + Boundary condition that defines the behavior for particles hitting the + surface. + periodic_surface : openmc.Surface + If a periodic boundary condition is used, the surface with which this + one is periodic with + coefficients : dict + Dictionary of surface coefficients + id : int + Unique identifier for the surface + name : str + Name of the surface + type : str + Type of the surface + + """ + + _type = 'x-plane' + _coeff_keys = ('x0',) + + def __init__(self, surface_id=None, boundary_type='transmission', + x0=0., name=''): + super().__init__(surface_id, boundary_type, name=name) + self.x0 = x0 + + @property + def x0(self): + return self.coefficients['x0'] + + @x0.setter + def x0(self, x0): + check_type('x0 coefficient', x0, Real) + self._coefficients['x0'] = x0 + + def bounding_box(self, side): + """Determine an axis-aligned bounding box. + + An axis-aligned bounding box for surface half-spaces is represented by + its lower-left and upper-right coordinates. For the x-plane surface, the + half-spaces are unbounded in their y- and z- directions. To represent + infinity, numpy.inf is used. + + Parameters + ---------- + side : {'+', '-'} + Indicates the negative or positive half-space + + Returns + ------- + numpy.ndarray + Lower-left coordinates of the axis-aligned bounding box for the + desired half-space + numpy.ndarray + Upper-right coordinates of the axis-aligned bounding box for the + desired half-space + + """ + + if side == '-': + return (np.array([-np.inf, -np.inf, -np.inf]), + np.array([self.x0, np.inf, np.inf])) + elif side == '+': + return (np.array([self.x0, -np.inf, -np.inf]), + np.array([np.inf, np.inf, np.inf])) + + def evaluate(self, point): + """Evaluate the surface equation at a given point. + + Parameters + ---------- + point : 3-tuple of float + The Cartesian coordinates, :math:`(x',y',z')`, at which the surface + equation should be evaluated. + + Returns + ------- + float + :math:`x' - x_0` + + """ + return point[0] - self.x0 + + def translate(self, vector): + """Translate surface in given direction + + Parameters + ---------- + vector : iterable of float + Direction in which surface should be translated + + Returns + ------- + openmc.XPlane + Translated surface + + """ + vx = vector[0] + if vx == 0: + return self + else: + return type(self)(x0=self.x0 + vx) + + +class YPlane(Plane): + """A plane perpendicular to the y axis of the form :math:`y - y_0 = 0` + + Parameters + ---------- + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. + boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'}, optional + Boundary condition that defines the behavior for particles hitting the + surface. Defaults to transmissive boundary condition where particles + freely pass through the surface. Only axis-aligned periodicity is + supported, i.e., x-planes can only be paired with x-planes. + y0 : float, optional + Location of the plane + name : str, optional + Name of the plane. If not specified, the name will be the empty string. + + Attributes + ---------- + y0 : float + Location of the plane + boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'} + Boundary condition that defines the behavior for particles hitting the + surface. + periodic_surface : openmc.Surface + If a periodic boundary condition is used, the surface with which this + one is periodic with + coefficients : dict + Dictionary of surface coefficients + id : int + Unique identifier for the surface + name : str + Name of the surface + type : str + Type of the surface + + """ + + _type = 'y-plane' + _coeff_keys = ('y0',) + + def __init__(self, surface_id=None, boundary_type='transmission', + y0=0., name=''): + # Initialize YPlane class attributes + super().__init__(surface_id, boundary_type, name=name) + self.y0 = y0 + + @property + def y0(self): + return self.coefficients['y0'] + + @y0.setter + def y0(self, y0): + check_type('y0 coefficient', y0, Real) + self._coefficients['y0'] = y0 + + def bounding_box(self, side): + """Determine an axis-aligned bounding box. + + An axis-aligned bounding box for surface half-spaces is represented by + its lower-left and upper-right coordinates. For the y-plane surface, the + half-spaces are unbounded in their x- and z- directions. To represent + infinity, numpy.inf is used. + + Parameters + ---------- + side : {'+', '-'} + Indicates the negative or positive half-space + + Returns + ------- + numpy.ndarray + Lower-left coordinates of the axis-aligned bounding box for the + desired half-space + numpy.ndarray + Upper-right coordinates of the axis-aligned bounding box for the + desired half-space + + """ + + if side == '-': + return (np.array([-np.inf, -np.inf, -np.inf]), + np.array([np.inf, self.y0, np.inf])) + elif side == '+': + return (np.array([-np.inf, self.y0, -np.inf]), + np.array([np.inf, np.inf, np.inf])) + + def evaluate(self, point): + """Evaluate the surface equation at a given point. + + Parameters + ---------- + point : 3-tuple of float + The Cartesian coordinates, :math:`(x',y',z')`, at which the surface + equation should be evaluated. + + Returns + ------- + float + :math:`y' - y_0` + + """ + return point[1] - self.y0 + + def translate(self, vector): + """Translate surface in given direction + + Parameters + ---------- + vector : iterable of float + Direction in which surface should be translated + + Returns + ------- + openmc.YPlane + Translated surface + + """ + vy = vector[1] + if vy == 0.0: + return self + else: + return type(self)(y0=self.y0 + vy) + + +class ZPlane(Plane): + """A plane perpendicular to the z axis of the form :math:`z - z_0 = 0` + + Parameters + ---------- + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. + boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'}, optional + Boundary condition that defines the behavior for particles hitting the + surface. Defaults to transmissive boundary condition where particles + freely pass through the surface. Only axis-aligned periodicity is + supported, i.e., x-planes can only be paired with x-planes. + z0 : float, optional + Location of the plane. Defaults to 0. + name : str, optional + Name of the plane. If not specified, the name will be the empty string. + + Attributes + ---------- + z0 : float + Location of the plane + boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'} + Boundary condition that defines the behavior for particles hitting the + surface. + periodic_surface : openmc.Surface + If a periodic boundary condition is used, the surface with which this + one is periodic with + coefficients : dict + Dictionary of surface coefficients + id : int + Unique identifier for the surface + name : str + Name of the surface + type : str + Type of the surface + + """ + + _type = 'z-plane' + _coeff_keys = ('z0',) + + def __init__(self, surface_id=None, boundary_type='transmission', + z0=0., name=''): + # Initialize ZPlane class attributes + super().__init__(surface_id, boundary_type, name=name) + self.z0 = z0 + + @property + def z0(self): + return self.coefficients['z0'] + + @z0.setter + def z0(self, z0): + check_type('z0 coefficient', z0, Real) + self._coefficients['z0'] = z0 + + def bounding_box(self, side): + """Determine an axis-aligned bounding box. + + An axis-aligned bounding box for surface half-spaces is represented by + its lower-left and upper-right coordinates. For the z-plane surface, the + half-spaces are unbounded in their x- and y- directions. To represent + infinity, numpy.inf is used. + + Parameters + ---------- + side : {'+', '-'} + Indicates the negative or positive half-space + + Returns + ------- + numpy.ndarray + Lower-left coordinates of the axis-aligned bounding box for the + desired half-space + numpy.ndarray + Upper-right coordinates of the axis-aligned bounding box for the + desired half-space + + """ + + if side == '-': + return (np.array([-np.inf, -np.inf, -np.inf]), + np.array([np.inf, np.inf, self.z0])) + elif side == '+': + return (np.array([-np.inf, -np.inf, self.z0]), + np.array([np.inf, np.inf, np.inf])) + + def evaluate(self, point): + """Evaluate the surface equation at a given point. + + Parameters + ---------- + point : 3-tuple of float + The Cartesian coordinates, :math:`(x',y',z')`, at which the surface + equation should be evaluated. + + Returns + ------- + float + :math:`z' - z_0` + + """ + return point[2] - self.z0 + + def translate(self, vector): + """Translate surface in given direction + + Parameters + ---------- + vector : iterable of float + Direction in which surface should be translated + + Returns + ------- + openmc.ZPlane + Translated surface + + """ + vz = vector[2] + if vz == 0.0: + return self + else: + return type(self)(z0=self.z0 + vz) + + +class Cylinder(Surface): + """A cylinder whose length is parallel to the x-, y-, or z-axis. + + Parameters + ---------- + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. + boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + Boundary condition that defines the behavior for particles hitting the + surface. Defaults to transmissive boundary condition where particles + freely pass through the surface. + r : float, optional + Radius of the cylinder. Defaults to 1. + name : str, optional + Name of the cylinder. If not specified, the name will be the empty + string. + + Attributes + ---------- + r : float + Radius of the cylinder + boundary_type : {'transmission, 'vacuum', 'reflective'} + Boundary condition that defines the behavior for particles hitting the + surface. + coefficients : dict + Dictionary of surface coefficients + id : int + Unique identifier for the surface + name : str + Name of the surface + type : str + Type of the surface + + """ + def __init__(self, surface_id=None, boundary_type='transmission', + r=1., name=''): + super().__init__(surface_id, boundary_type, name=name) + self.r = r + + @property + def r(self): + return self.coefficients['r'] + + @r.setter + def r(self, r): + check_type('r coefficient', r, Real) + self._coefficients['r'] = r + + +class XCylinder(Cylinder): + """An infinite cylinder whose length is parallel to the x-axis of the form + :math:`(y - y_0)^2 + (z - z_0)^2 = r^2`. + + Parameters + ---------- + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. + boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + Boundary condition that defines the behavior for particles hitting the + surface. Defaults to transmissive boundary condition where particles + freely pass through the surface. + y0 : float, optional + y-coordinate of the center of the cylinder. Defaults to 0. + z0 : float, optional + z-coordinate of the center of the cylinder. Defaults to 0. + r : float, optional + Radius of the cylinder. Defaults to 0. + name : str, optional + Name of the cylinder. If not specified, the name will be the empty + string. + + Attributes + ---------- + y0 : float + y-coordinate of the center of the cylinder + z0 : float + z-coordinate of the center of the cylinder + boundary_type : {'transmission, 'vacuum', 'reflective'} + Boundary condition that defines the behavior for particles hitting the + surface. + coefficients : dict + Dictionary of surface coefficients + id : int + Unique identifier for the surface + name : str + Name of the surface + type : str + Type of the surface + + """ + + _type = 'x-cylinder' + _coeff_keys = ('y0', 'z0', 'r') + + def __init__(self, surface_id=None, boundary_type='transmission', + y0=0., z0=0., r=1., name='', *, R=None): + if R is not None: + warn(_WARNING_UPPER.format(type(self).__name__, 'r', 'R'), FutureWarning) + r = R + super().__init__(surface_id, boundary_type, r, name=name) + self.y0 = y0 + self.z0 = z0 + + @property + def y0(self): + return self.coefficients['y0'] + + @property + def z0(self): + return self.coefficients['z0'] + + @y0.setter + def y0(self, y0): + check_type('y0 coefficient', y0, Real) + self._coefficients['y0'] = y0 + + @z0.setter + def z0(self, z0): + check_type('z0 coefficient', z0, Real) + self._coefficients['z0'] = z0 + + def bounding_box(self, side): + """Determine an axis-aligned bounding box. + + An axis-aligned bounding box for surface half-spaces is represented by + its lower-left and upper-right coordinates. For the x-cylinder surface, + the negative half-space is unbounded in the x- direction and the + positive half-space is unbounded in all directions. To represent + infinity, numpy.inf is used. + + Parameters + ---------- + side : {'+', '-'} + Indicates the negative or positive half-space + + Returns + ------- + numpy.ndarray + Lower-left coordinates of the axis-aligned bounding box for the + desired half-space + numpy.ndarray + Upper-right coordinates of the axis-aligned bounding box for the + desired half-space + + """ + + if side == '-': + return (np.array([-np.inf, self.y0 - self.r, self.z0 - self.r]), + np.array([np.inf, self.y0 + self.r, self.z0 + self.r])) + elif side == '+': + return (np.array([-np.inf, -np.inf, -np.inf]), + np.array([np.inf, np.inf, np.inf])) + + def evaluate(self, point): + """Evaluate the surface equation at a given point. + + Parameters + ---------- + point : 3-tuple of float + The Cartesian coordinates, :math:`(x',y',z')`, at which the surface + equation should be evaluated. + + Returns + ------- + float + :math:`(y' - y_0)^2 + (z' - z_0)^2 - r^2` + + """ + y = point[1] - self.y0 + z = point[2] - self.z0 + return y**2 + z**2 - self.r**2 + + def translate(self, vector): + """Translate surface in given direction + + Parameters + ---------- + vector : iterable of float + Direction in which surface should be translated + + Returns + ------- + openmc.XCylinder + Translated surface + + """ + vx, vy, vz = vector + if vy == 0.0 and vz == 0.0: + return self + else: + y0 = self.y0 + vy + z0 = self.z0 + vz + return type(self)(y0=y0, z0=z0, r=self.r) + + +class YCylinder(Cylinder): + """An infinite cylinder whose length is parallel to the y-axis of the form + :math:`(x - x_0)^2 + (z - z_0)^2 = r^2`. + + Parameters + ---------- + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. + boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + Boundary condition that defines the behavior for particles hitting the + surface. Defaults to transmissive boundary condition where particles + freely pass through the surface. + x0 : float, optional + x-coordinate of the center of the cylinder. Defaults to 0. + z0 : float, optional + z-coordinate of the center of the cylinder. Defaults to 0. + r : float, optional + Radius of the cylinder. Defaults to 1. + name : str, optional + Name of the cylinder. If not specified, the name will be the empty + string. + + Attributes + ---------- + x0 : float + x-coordinate of the center of the cylinder + z0 : float + z-coordinate of the center of the cylinder + boundary_type : {'transmission, 'vacuum', 'reflective'} + Boundary condition that defines the behavior for particles hitting the + surface. + coefficients : dict + Dictionary of surface coefficients + id : int + Unique identifier for the surface + name : str + Name of the surface + type : str + Type of the surface + + """ + + _type = 'y-cylinder' + _coeff_keys = ('x0', 'z0', 'r') + + def __init__(self, surface_id=None, boundary_type='transmission', + x0=0., z0=0., r=1., name='', *, R=None): + if R is not None: + warn(_WARNING_UPPER.format(type(self).__name__, 'r', 'R'), FutureWarning) + r = R + super().__init__(surface_id, boundary_type, r, name=name) + self.x0 = x0 + self.z0 = z0 + + @property + def x0(self): + return self.coefficients['x0'] + + @property + def z0(self): + return self.coefficients['z0'] + + @x0.setter + def x0(self, x0): + check_type('x0 coefficient', x0, Real) + self._coefficients['x0'] = x0 + + @z0.setter + def z0(self, z0): + check_type('z0 coefficient', z0, Real) + self._coefficients['z0'] = z0 + + def bounding_box(self, side): + """Determine an axis-aligned bounding box. + + An axis-aligned bounding box for surface half-spaces is represented by + its lower-left and upper-right coordinates. For the y-cylinder surface, + the negative half-space is unbounded in the y- direction and the + positive half-space is unbounded in all directions. To represent + infinity, numpy.inf is used. + + Parameters + ---------- + side : {'+', '-'} + Indicates the negative or positive half-space + + Returns + ------- + numpy.ndarray + Lower-left coordinates of the axis-aligned bounding box for the + desired half-space + numpy.ndarray + Upper-right coordinates of the axis-aligned bounding box for the + desired half-space + + """ + + if side == '-': + return (np.array([self.x0 - self.r, -np.inf, self.z0 - self.r]), + np.array([self.x0 + self.r, np.inf, self.z0 + self.r])) + elif side == '+': + return (np.array([-np.inf, -np.inf, -np.inf]), + np.array([np.inf, np.inf, np.inf])) + + def evaluate(self, point): + """Evaluate the surface equation at a given point. + + Parameters + ---------- + point : 3-tuple of float + The Cartesian coordinates, :math:`(x',y',z')`, at which the surface + equation should be evaluated. + + Returns + ------- + float + :math:`(x' - x_0)^2 + (z' - z_0)^2 - r^2` + + """ + x = point[0] - self.x0 + z = point[2] - self.z0 + return x**2 + z**2 - self.r**2 + + def translate(self, vector): + """Translate surface in given direction + + Parameters + ---------- + vector : iterable of float + Direction in which surface should be translated + + Returns + ------- + openmc.YCylinder + Translated surface + + """ + vx, vy, vz = vector + if vx == 0.0 and vz == 0.0: + return self + else: + x0 = self.x0 + vx + z0 = self.z0 + vz + return type(self)(x0=x0, z0=z0, r=self.r) + + +class ZCylinder(Cylinder): + """An infinite cylinder whose length is parallel to the z-axis of the form + :math:`(x - x_0)^2 + (y - y_0)^2 = r^2`. + + Parameters + ---------- + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. + boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + Boundary condition that defines the behavior for particles hitting the + surface. Defaults to transmissive boundary condition where particles + freely pass through the surface. + x0 : float, optional + x-coordinate of the center of the cylinder. Defaults to 0. + y0 : float, optional + y-coordinate of the center of the cylinder. Defaults to 0. + r : float, optional + Radius of the cylinder. Defaults to 1. + name : str, optional + Name of the cylinder. If not specified, the name will be the empty + string. + + Attributes + ---------- + x0 : float + x-coordinate of the center of the cylinder + y0 : float + y-coordinate of the center of the cylinder + boundary_type : {'transmission, 'vacuum', 'reflective'} + Boundary condition that defines the behavior for particles hitting the + surface. + coefficients : dict + Dictionary of surface coefficients + id : int + Unique identifier for the surface + name : str + Name of the surface + type : str + Type of the surface + + """ + + _type = 'z-cylinder' + _coeff_keys = ('x0', 'y0', 'r') + + def __init__(self, surface_id=None, boundary_type='transmission', + x0=0., y0=0., r=1., name='', *, R=None): + if R is not None: + warn(_WARNING_UPPER.format(type(self).__name__, 'r', 'R'), FutureWarning) + r = R + super().__init__(surface_id, boundary_type, r, name=name) + self.x0 = x0 + self.y0 = y0 + + @property + def x0(self): + return self.coefficients['x0'] + + @property + def y0(self): + return self.coefficients['y0'] + + @x0.setter + def x0(self, x0): + check_type('x0 coefficient', x0, Real) + self._coefficients['x0'] = x0 + + @y0.setter + def y0(self, y0): + check_type('y0 coefficient', y0, Real) + self._coefficients['y0'] = y0 + + def bounding_box(self, side): + """Determine an axis-aligned bounding box. + + An axis-aligned bounding box for surface half-spaces is represented by + its lower-left and upper-right coordinates. For the z-cylinder surface, + the negative half-space is unbounded in the z- direction and the + positive half-space is unbounded in all directions. To represent + infinity, numpy.inf is used. + + Parameters + ---------- + side : {'+', '-'} + Indicates the negative or positive half-space + + Returns + ------- + numpy.ndarray + Lower-left coordinates of the axis-aligned bounding box for the + desired half-space + numpy.ndarray + Upper-right coordinates of the axis-aligned bounding box for the + desired half-space + + """ + + if side == '-': + return (np.array([self.x0 - self.r, self.y0 - self.r, -np.inf]), + np.array([self.x0 + self.r, self.y0 + self.r, np.inf])) + elif side == '+': + return (np.array([-np.inf, -np.inf, -np.inf]), + np.array([np.inf, np.inf, np.inf])) + + def evaluate(self, point): + """Evaluate the surface equation at a given point. + + Parameters + ---------- + point : 3-tuple of float + The Cartesian coordinates, :math:`(x',y',z')`, at which the surface + equation should be evaluated. + + Returns + ------- + float + :math:`(x' - x_0)^2 + (y' - y_0)^2 - r^2` + + """ + x = point[0] - self.x0 + y = point[1] - self.y0 + return x**2 + y**2 - self.r**2 + + def translate(self, vector): + """Translate surface in given direction + + Parameters + ---------- + vector : iterable of float + Direction in which surface should be translated + + Returns + ------- + openmc.ZCylinder + Translated surface + + """ + vx, vy, vz = vector + if vx == 0.0 and vy == 0.0: + return self + else: + x0 = self.x0 + vx + y0 = self.y0 + vy + return type(self)(x0=x0, y0=y0, r=self.r) + + +class Sphere(Surface): + """A sphere of the form :math:`(x - x_0)^2 + (y - y_0)^2 + (z - z_0)^2 = r^2`. + + Parameters + ---------- + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. + boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + Boundary condition that defines the behavior for particles hitting the + surface. Defaults to transmissive boundary condition where particles + freely pass through the surface. + x0 : float, optional + x-coordinate of the center of the sphere. Defaults to 0. + y0 : float, optional + y-coordinate of the center of the sphere. Defaults to 0. + z0 : float, optional + z-coordinate of the center of the sphere. Defaults to 0. + r : float, optional + Radius of the sphere. Defaults to 1. + name : str, optional + Name of the sphere. If not specified, the name will be the empty string. + + Attributes + ---------- + x0 : float + x-coordinate of the center of the sphere + y0 : float + y-coordinate of the center of the sphere + z0 : float + z-coordinate of the center of the sphere + r : float + Radius of the sphere + boundary_type : {'transmission, 'vacuum', 'reflective'} + Boundary condition that defines the behavior for particles hitting the + surface. + coefficients : dict + Dictionary of surface coefficients + id : int + Unique identifier for the surface + name : str + Name of the surface + type : str + Type of the surface + + """ + + _type = 'sphere' + _coeff_keys = ('x0', 'y0', 'z0', 'r') + + def __init__(self, surface_id=None, boundary_type='transmission', + x0=0., y0=0., z0=0., r=1., name='', *, R=None): + if R is not None: + warn(_WARNING_UPPER.format(type(self).__name__, 'r', 'R'), FutureWarning) + r = R + super().__init__(surface_id, boundary_type, name=name) + self.x0 = x0 + self.y0 = y0 + self.z0 = z0 + self.r = r + + @property + def x0(self): + return self.coefficients['x0'] + + @property + def y0(self): + return self.coefficients['y0'] + + @property + def z0(self): + return self.coefficients['z0'] + + @property + def r(self): + return self.coefficients['r'] + + @x0.setter + def x0(self, x0): + check_type('x0 coefficient', x0, Real) + self._coefficients['x0'] = x0 + + @y0.setter + def y0(self, y0): + check_type('y0 coefficient', y0, Real) + self._coefficients['y0'] = y0 + + @z0.setter + def z0(self, z0): + check_type('z0 coefficient', z0, Real) + self._coefficients['z0'] = z0 + + @r.setter + def r(self, r): + check_type('r coefficient', r, Real) + self._coefficients['r'] = r + + def bounding_box(self, side): + """Determine an axis-aligned bounding box. + + An axis-aligned bounding box for surface half-spaces is represented by + its lower-left and upper-right coordinates. The positive half-space of a + sphere is unbounded in all directions. To represent infinity, numpy.inf + is used. + + Parameters + ---------- + side : {'+', '-'} + Indicates the negative or positive half-space + + Returns + ------- + numpy.ndarray + Lower-left coordinates of the axis-aligned bounding box for the + desired half-space + numpy.ndarray + Upper-right coordinates of the axis-aligned bounding box for the + desired half-space + + """ + + if side == '-': + return (np.array([self.x0 - self.r, self.y0 - self.r, + self.z0 - self.r]), + np.array([self.x0 + self.r, self.y0 + self.r, + self.z0 + self.r])) + elif side == '+': + return (np.array([-np.inf, -np.inf, -np.inf]), + np.array([np.inf, np.inf, np.inf])) + + def evaluate(self, point): + """Evaluate the surface equation at a given point. + + Parameters + ---------- + point : 3-tuple of float + The Cartesian coordinates, :math:`(x',y',z')`, at which the surface + equation should be evaluated. + + Returns + ------- + float + :math:`(x' - x_0)^2 + (y' - y_0)^2 + (z' - z_0)^2 - r^2` + + """ + x = point[0] - self.x0 + y = point[1] - self.y0 + z = point[2] - self.z0 + return x**2 + y**2 + z**2 - self.r**2 + + def translate(self, vector): + """Translate surface in given direction + + Parameters + ---------- + vector : iterable of float + Direction in which surface should be translated + + Returns + ------- + openmc.Sphere + Translated surface + + """ + vx, vy, vz = vector + if vx == 0.0 and vy == 0.0 and vz == 0.0: + return self + else: + x0 = self.x0 + vx + y0 = self.y0 + vy + z0 = self.z0 + vz + return type(self)(x0=x0, y0=y0, z0=z0, r=self.r) + + +class Cone(Surface): + """A conical surface parallel to the x-, y-, or z-axis. + + Parameters + ---------- + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. + boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + Boundary condition that defines the behavior for particles hitting the + surface. Defaults to transmissive boundary condition where particles + freely pass through the surface. + x0 : float, optional + x-coordinate of the apex. Defaults to 0. + y0 : float + y-coordinate of the apex. Defaults to 0. + z0 : float + z-coordinate of the apex. Defaults to 0. + r2 : float + Parameter related to the aperature. Defaults to 1. + name : str + Name of the cone. If not specified, the name will be the empty string. + + Attributes + ---------- + x0 : float + x-coordinate of the apex + y0 : float + y-coordinate of the apex + z0 : float + z-coordinate of the apex + r2 : float + Parameter related to the aperature + boundary_type : {'transmission, 'vacuum', 'reflective'} + Boundary condition that defines the behavior for particles hitting the + surface. + coefficients : dict + Dictionary of surface coefficients + id : int + Unique identifier for the surface + name : str + Name of the surface + type : str + Type of the surface + + """ + + _coeff_keys = ('x0', 'y0', 'z0', 'r2') + + def __init__(self, surface_id=None, boundary_type='transmission', + x0=0., y0=0., z0=0., r2=1., name='', *, R2=None): + if R2 is not None: + warn(_WARNING_UPPER.format(type(self).__name__, 'r2', 'R2'), FutureWarning) + r2 = R2 + super().__init__(surface_id, boundary_type, name=name) + self.x0 = x0 + self.y0 = y0 + self.z0 = z0 + self.r2 = r2 + + @property + def x0(self): + return self.coefficients['x0'] + + @property + def y0(self): + return self.coefficients['y0'] + + @property + def z0(self): + return self.coefficients['z0'] + + @property + def r2(self): + return self.coefficients['r2'] + + @x0.setter + def x0(self, x0): + check_type('x0 coefficient', x0, Real) + self._coefficients['x0'] = x0 + + @y0.setter + def y0(self, y0): + check_type('y0 coefficient', y0, Real) + self._coefficients['y0'] = y0 + + @z0.setter + def z0(self, z0): + check_type('z0 coefficient', z0, Real) + self._coefficients['z0'] = z0 + + @r2.setter + def r2(self, r2): + check_type('r^2 coefficient', r2, Real) + self._coefficients['r2'] = r2 + + def translate(self, vector): + """Translate surface in given direction + + Parameters + ---------- + vector : iterable of float + Direction in which surface should be translated + + Returns + ------- + openmc.Cone + Translated surface + + """ + vx, vy, vz = vector + if vx == 0.0 and vy == 0.0 and vz == 0.0: + return self + else: + x0 = self.x0 + vx + y0 = self.y0 + vy + z0 = self.z0 + vz + return type(self)(x0=x0, y0=y0, z0=z0, r2=self.r2) + + +class XCone(Cone): + """A cone parallel to the x-axis of the form :math:`(y - y_0)^2 + (z - z_0)^2 = + r^2 (x - x_0)^2`. + + Parameters + ---------- + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. + boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + Boundary condition that defines the behavior for particles hitting the + surface. Defaults to transmissive boundary condition where particles + freely pass through the surface. + x0 : float, optional + x-coordinate of the apex. Defaults to 0. + y0 : float, optional + y-coordinate of the apex. Defaults to 0. + z0 : float, optional + z-coordinate of the apex. Defaults to 0. + r2 : float, optional + Parameter related to the aperature. Defaults to 1. + name : str, optional + Name of the cone. If not specified, the name will be the empty string. + + Attributes + ---------- + x0 : float + x-coordinate of the apex + y0 : float + y-coordinate of the apex + z0 : float + z-coordinate of the apex + r2 : float + Parameter related to the aperature + boundary_type : {'transmission, 'vacuum', 'reflective'} + Boundary condition that defines the behavior for particles hitting the + surface. + coefficients : dict + Dictionary of surface coefficients + id : int + Unique identifier for the surface + name : str + Name of the surface + type : str + Type of the surface + + """ + + _type = 'x-cone' + + def evaluate(self, point): + """Evaluate the surface equation at a given point. + + Parameters + ---------- + point : 3-tuple of float + The Cartesian coordinates, :math:`(x',y',z')`, at which the surface + equation should be evaluated. + + Returns + ------- + float + :math:`(y' - y_0)^2 + (z' - z_0)^2 - r^2(x' - x_0)^2` + + """ + x = point[0] - self.x0 + y = point[1] - self.y0 + z = point[2] - self.z0 + return y**2 + z**2 - self.r2*x**2 + + +class YCone(Cone): + """A cone parallel to the y-axis of the form :math:`(x - x_0)^2 + (z - z_0)^2 = + r^2 (y - y_0)^2`. + + Parameters + ---------- + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. + boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + Boundary condition that defines the behavior for particles hitting the + surface. Defaults to transmissive boundary condition where particles + freely pass through the surface. + x0 : float, optional + x-coordinate of the apex. Defaults to 0. + y0 : float, optional + y-coordinate of the apex. Defaults to 0. + z0 : float, optional + z-coordinate of the apex. Defaults to 0. + r2 : float, optional + Parameter related to the aperature. Defaults to 1. + name : str, optional + Name of the cone. If not specified, the name will be the empty string. + + Attributes + ---------- + x0 : float + x-coordinate of the apex + y0 : float + y-coordinate of the apex + z0 : float + z-coordinate of the apex + r2 : float + Parameter related to the aperature + boundary_type : {'transmission, 'vacuum', 'reflective'} + Boundary condition that defines the behavior for particles hitting the + surface. + coefficients : dict + Dictionary of surface coefficients + id : int + Unique identifier for the surface + name : str + Name of the surface + type : str + Type of the surface + + """ + + _type = 'y-cone' + + def evaluate(self, point): + """Evaluate the surface equation at a given point. + + Parameters + ---------- + point : 3-tuple of float + The Cartesian coordinates, :math:`(x',y',z')`, at which the surface + equation should be evaluated. + + Returns + ------- + float + :math:`(x' - x_0)^2 + (z' - z_0)^2 - r^2(y' - y_0)^2` + + """ + x = point[0] - self.x0 + y = point[1] - self.y0 + z = point[2] - self.z0 + return x**2 + z**2 - self.r2*y**2 + + +class ZCone(Cone): + """A cone parallel to the x-axis of the form :math:`(x - x_0)^2 + (y - y_0)^2 = + r^2 (z - z_0)^2`. + + Parameters + ---------- + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. + boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + Boundary condition that defines the behavior for particles hitting the + surface. Defaults to transmissive boundary condition where particles + freely pass through the surface. + x0 : float, optional + x-coordinate of the apex. Defaults to 0. + y0 : float, optional + y-coordinate of the apex. Defaults to 0. + z0 : float, optional + z-coordinate of the apex. Defaults to 0. + r2 : float, optional + Parameter related to the aperature. Defaults to 1. + name : str, optional + Name of the cone. If not specified, the name will be the empty string. + + Attributes + ---------- + x0 : float + x-coordinate of the apex + y0 : float + y-coordinate of the apex + z0 : float + z-coordinate of the apex + r2 : float + Parameter related to the aperature + boundary_type : {'transmission, 'vacuum', 'reflective'} + Boundary condition that defines the behavior for particles hitting the + surface. + coefficients : dict + Dictionary of surface coefficients + id : int + Unique identifier for the surface + name : str + Name of the surface + type : str + Type of the surface + + """ + + _type = 'z-cone' + + def evaluate(self, point): + """Evaluate the surface equation at a given point. + + Parameters + ---------- + point : 3-tuple of float + The Cartesian coordinates, :math:`(x',y',z')`, at which the surface + equation should be evaluated. + + Returns + ------- + float + :math:`(x' - x_0)^2 + (y' - y_0)^2 - r^2(z' - z_0)^2` + + """ + x = point[0] - self.x0 + y = point[1] - self.y0 + z = point[2] - self.z0 + return x**2 + y**2 - self.r2*z**2 + + +class Quadric(Surface): + """A surface of the form :math:`Ax^2 + By^2 + Cz^2 + Dxy + Eyz + Fxz + Gx + Hy + + Jz + K = 0`. + + Parameters + ---------- + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. + boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'}, optional + Boundary condition that defines the behavior for particles hitting the + surface. Defaults to transmissive boundary condition where particles + freely pass through the surface. + a, b, c, d, e, f, g, h, j, k : float, optional + coefficients for the surface. All default to 0. + name : str, optional + Name of the surface. If not specified, the name will be the empty string. + + Attributes + ---------- + a, b, c, d, e, f, g, h, j, k : float + coefficients for the surface + boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'} + Boundary condition that defines the behavior for particles hitting the + surface. + coefficients : dict + Dictionary of surface coefficients + id : int + Unique identifier for the surface + name : str + Name of the surface + type : str + Type of the surface + + """ + + _type = 'quadric' + _coeff_keys = ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k') + + def __init__(self, surface_id=None, boundary_type='transmission', + a=0., b=0., c=0., d=0., e=0., f=0., g=0., + h=0., j=0., k=0., name=''): + super().__init__(surface_id, boundary_type, name=name) + self.a = a + self.b = b + self.c = c + self.d = d + self.e = e + self.f = f + self.g = g + self.h = h + self.j = j + self.k = k + + @property + def a(self): + return self.coefficients['a'] + + @property + def b(self): + return self.coefficients['b'] + + @property + def c(self): + return self.coefficients['c'] + + @property + def d(self): + return self.coefficients['d'] + + @property + def e(self): + return self.coefficients['e'] + + @property + def f(self): + return self.coefficients['f'] + + @property + def g(self): + return self.coefficients['g'] + + @property + def h(self): + return self.coefficients['h'] + + @property + def j(self): + return self.coefficients['j'] + + @property + def k(self): + return self.coefficients['k'] + + @a.setter + def a(self, a): + check_type('a coefficient', a, Real) + self._coefficients['a'] = a + + @b.setter + def b(self, b): + check_type('b coefficient', b, Real) + self._coefficients['b'] = b + + @c.setter + def c(self, c): + check_type('c coefficient', c, Real) + self._coefficients['c'] = c + + @d.setter + def d(self, d): + check_type('d coefficient', d, Real) + self._coefficients['d'] = d + + @e.setter + def e(self, e): + check_type('e coefficient', e, Real) + self._coefficients['e'] = e + + @f.setter + def f(self, f): + check_type('f coefficient', f, Real) + self._coefficients['f'] = f + + @g.setter + def g(self, g): + check_type('g coefficient', g, Real) + self._coefficients['g'] = g + + @h.setter + def h(self, h): + check_type('h coefficient', h, Real) + self._coefficients['h'] = h + + @j.setter + def j(self, j): + check_type('j coefficient', j, Real) + self._coefficients['j'] = j + + @k.setter + def k(self, k): + check_type('k coefficient', k, Real) + self._coefficients['k'] = k + + def evaluate(self, point): + """Evaluate the surface equation at a given point. + + Parameters + ---------- + point : 3-tuple of float + The Cartesian coordinates, :math:`(x',y',z')`, at which the surface + equation should be evaluated. + + Returns + ------- + float + :math:`Ax'^2 + By'^2 + Cz'^2 + Dx'y' + Ey'z' + Fx'z' + Gx' + Hy' + + Jz' + K = 0` + + """ + x, y, z = point + return x*(self.a*x + self.d*y + self.g) + \ + y*(self.b*y + self.e*z + self.h) + \ + z*(self.c*z + self.f*x + self.j) + self.k + + def translate(self, vector): + """Translate surface in given direction + + Parameters + ---------- + vector : iterable of float + Direction in which surface should be translated + + Returns + ------- + openmc.Quadric + Translated surface + + """ + vx, vy, vz = vector + a, b, c, d, e, f, g, h, j, k = (getattr(self, key) for key in + self._coeff_keys) + k = (k + vx*vx + vy*vy + vz*vz + d*vx*vy + e*vy*vz + f*vx*vz + - g*vx - h*vy - j*vz) + g = g - 2*a*vx - d*vy - f*vz + h = h - 2*b*vy - d*vx - e*vz + j = j - 2*c*vz - e*vy - f*vx + return type(self)(a=a, b=b, c=c, d=d, e=e, f=f, g=g, h=h, j=j, k=k) + + +class Halfspace(Region): + """A positive or negative half-space region. + + A half-space is either of the two parts into which a two-dimension surface + divides the three-dimensional Euclidean space. If the equation of the + surface is :math:`f(x,y,z) = 0`, the region for which :math:`f(x,y,z) < 0` + is referred to as the negative half-space and the region for which + :math:`f(x,y,z) > 0` is referred to as the positive half-space. + + Instances of Halfspace are generally not instantiated directly. Rather, they + can be created from an existing Surface through the __neg__ and __pos__ + operators, as the following example demonstrates: + + >>> sphere = openmc.Sphere(surface_id=1, r=10.0) + >>> inside_sphere = -sphere + >>> outside_sphere = +sphere + >>> type(inside_sphere) + + + Parameters + ---------- + surface : openmc.Surface + Surface which divides Euclidean space. + side : {'+', '-'} + Indicates whether the positive or negative half-space is used. + + Attributes + ---------- + surface : openmc.Surface + Surface which divides Euclidean space. + side : {'+', '-'} + Indicates whether the positive or negative half-space is used. + bounding_box : tuple of numpy.ndarray + Lower-left and upper-right coordinates of an axis-aligned bounding box + + """ + + def __init__(self, surface, side): + self.surface = surface + self.side = side + + def __and__(self, other): + if isinstance(other, Intersection): + return Intersection([self] + other[:]) + else: + return Intersection((self, other)) + + def __or__(self, other): + if isinstance(other, Union): + return Union([self] + other[:]) + else: + return Union((self, other)) + + def __invert__(self): + return -self.surface if self.side == '+' else +self.surface + + def __contains__(self, point): + """Check whether a point is contained in the half-space. + + Parameters + ---------- + point : 3-tuple of float + Cartesian coordinates, :math:`(x',y',z')`, of the point + + Returns + ------- + bool + Whether the point is in the half-space + + """ + + val = self.surface.evaluate(point) + return val >= 0. if self.side == '+' else val < 0. + + @property + def surface(self): + return self._surface + + @surface.setter + def surface(self, surface): + check_type('surface', surface, Surface) + self._surface = surface + + @property + def side(self): + return self._side + + @side.setter + def side(self, side): + check_value('side', side, ('+', '-')) + self._side = side + + @property + def bounding_box(self): + return self.surface.bounding_box(self.side) + + def __str__(self): + return '-' + str(self.surface.id) if self.side == '-' \ + else str(self.surface.id) + + def get_surfaces(self, surfaces=None): + """ + Returns the surface that this is a halfspace of. + + Parameters + ---------- + surfaces: collections.OrderedDict, optional + Dictionary mapping surface IDs to :class:`openmc.Surface` instances + + Returns + ------- + surfaces: collections.OrderedDict + Dictionary mapping surface IDs to :class:`openmc.Surface` instances + + """ + if surfaces is None: + surfaces = OrderedDict() + + surfaces[self.surface.id] = self.surface + return surfaces + + def clone(self, memo=None): + """Create a copy of this halfspace, with a cloned surface with a + unique ID. + + Parameters + ---------- + memo : dict or None + A nested dictionary of previously cloned objects. This parameter + is used internally and should not be specified by the user. + + Returns + ------- + clone : openmc.Halfspace + The clone of this halfspace + + """ + + if memo is None: + memo = dict + + clone = deepcopy(self) + clone.surface = self.surface.clone(memo) + return clone + + def translate(self, vector, memo=None): + """Translate half-space in given direction + + Parameters + ---------- + vector : iterable of float + Direction in which region should be translated + memo : dict or None + Dictionary used for memoization + + Returns + ------- + openmc.Halfspace + Translated half-space + + """ + if memo is None: + memo = {} + + # If translated surface not in memo, add it + key = (self.surface, tuple(vector)) + if key not in memo: + memo[key] = self.surface.translate(vector) + + # Return translated surface + return type(self)(memo[key], self.side) From c88f0137953d11ce5987ea3cc555c3e10fb8362e Mon Sep 17 00:00:00 2001 From: rockfool Date: Mon, 16 Sep 2019 14:23:31 -0400 Subject: [PATCH 041/158] add white in surface.py --- openmc/surface.py | 62 +++++++++++++++++++++++------------------------ 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/openmc/surface.py b/openmc/surface.py index dc251485d2..4bbcb5e4c4 100644 --- a/openmc/surface.py +++ b/openmc/surface.py @@ -12,7 +12,7 @@ from openmc.region import Region, Intersection, Union from openmc.mixin import IDManagerMixin -_BOUNDARY_TYPES = ['transmission', 'vacuum', 'reflective', 'periodic'] +_BOUNDARY_TYPES = ['transmission', 'vacuum', 'reflective', 'periodic', 'white'] _WARNING_UPPER = """\ "{}(...) accepts an argument named '{}', not '{}'. Future versions of OpenMC \ @@ -32,7 +32,7 @@ class Surface(IDManagerMixin, metaclass=ABCMeta): surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'}, optional + boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. Note that periodic boundary conditions @@ -44,7 +44,7 @@ class Surface(IDManagerMixin, metaclass=ABCMeta): Attributes ---------- - boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'} + boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'} Boundary condition that defines the behavior for particles hitting the surface. coefficients : dict @@ -332,7 +332,7 @@ class Plane(Surface): surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. @@ -357,7 +357,7 @@ class Plane(Surface): The 'C' parameter for the plane d : float The 'D' parameter for the plane - boundary_type : {'transmission, 'vacuum', 'reflective'} + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. periodic_surface : openmc.Surface @@ -535,7 +535,7 @@ class XPlane(Plane): surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'}, optional + boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. Only axis-aligned periodicity is @@ -549,7 +549,7 @@ class XPlane(Plane): ---------- x0 : float Location of the plane - boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'} + boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'} Boundary condition that defines the behavior for particles hitting the surface. periodic_surface : openmc.Surface @@ -660,7 +660,7 @@ class YPlane(Plane): surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'}, optional + boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. Only axis-aligned periodicity is @@ -674,7 +674,7 @@ class YPlane(Plane): ---------- y0 : float Location of the plane - boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'} + boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'} Boundary condition that defines the behavior for particles hitting the surface. periodic_surface : openmc.Surface @@ -786,7 +786,7 @@ class ZPlane(Plane): surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'}, optional + boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. Only axis-aligned periodicity is @@ -800,7 +800,7 @@ class ZPlane(Plane): ---------- z0 : float Location of the plane - boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'} + boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'} Boundary condition that defines the behavior for particles hitting the surface. periodic_surface : openmc.Surface @@ -912,7 +912,7 @@ class Cylinder(Surface): surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. @@ -926,7 +926,7 @@ class Cylinder(Surface): ---------- r : float Radius of the cylinder - boundary_type : {'transmission, 'vacuum', 'reflective'} + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. coefficients : dict @@ -963,7 +963,7 @@ class XCylinder(Cylinder): surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. @@ -983,7 +983,7 @@ class XCylinder(Cylinder): y-coordinate of the center of the cylinder z0 : float z-coordinate of the center of the cylinder - boundary_type : {'transmission, 'vacuum', 'reflective'} + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. coefficients : dict @@ -1110,7 +1110,7 @@ class YCylinder(Cylinder): surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. @@ -1130,7 +1130,7 @@ class YCylinder(Cylinder): x-coordinate of the center of the cylinder z0 : float z-coordinate of the center of the cylinder - boundary_type : {'transmission, 'vacuum', 'reflective'} + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. coefficients : dict @@ -1257,7 +1257,7 @@ class ZCylinder(Cylinder): surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. @@ -1277,7 +1277,7 @@ class ZCylinder(Cylinder): x-coordinate of the center of the cylinder y0 : float y-coordinate of the center of the cylinder - boundary_type : {'transmission, 'vacuum', 'reflective'} + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. coefficients : dict @@ -1403,7 +1403,7 @@ class Sphere(Surface): surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. @@ -1428,7 +1428,7 @@ class Sphere(Surface): z-coordinate of the center of the sphere r : float Radius of the sphere - boundary_type : {'transmission, 'vacuum', 'reflective'} + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. coefficients : dict @@ -1577,7 +1577,7 @@ class Cone(Surface): surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. @@ -1602,7 +1602,7 @@ class Cone(Surface): z-coordinate of the apex r2 : float Parameter related to the aperature - boundary_type : {'transmission, 'vacuum', 'reflective'} + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. coefficients : dict @@ -1698,7 +1698,7 @@ class XCone(Cone): surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. @@ -1723,7 +1723,7 @@ class XCone(Cone): z-coordinate of the apex r2 : float Parameter related to the aperature - boundary_type : {'transmission, 'vacuum', 'reflective'} + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. coefficients : dict @@ -1769,7 +1769,7 @@ class YCone(Cone): surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. @@ -1794,7 +1794,7 @@ class YCone(Cone): z-coordinate of the apex r2 : float Parameter related to the aperature - boundary_type : {'transmission, 'vacuum', 'reflective'} + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. coefficients : dict @@ -1840,7 +1840,7 @@ class ZCone(Cone): surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective'}, optional + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. @@ -1865,7 +1865,7 @@ class ZCone(Cone): z-coordinate of the apex r2 : float Parameter related to the aperature - boundary_type : {'transmission, 'vacuum', 'reflective'} + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. coefficients : dict @@ -1911,7 +1911,7 @@ class Quadric(Surface): surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'}, optional + boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. @@ -1924,7 +1924,7 @@ class Quadric(Surface): ---------- a, b, c, d, e, f, g, h, j, k : float coefficients for the surface - boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'} + boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'} Boundary condition that defines the behavior for particles hitting the surface. coefficients : dict From 0c02e58b7854e8df82b73f261ff1916cadb51779 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 13 Sep 2019 14:06:42 -0500 Subject: [PATCH 042/158] Documentation for modifications to total heating --- docs/source/methods/energy_deposition.rst | 134 ++++++++++++++++++++++ docs/source/methods/index.rst | 1 + docs/source/usersguide/tallies.rst | 5 +- 3 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 docs/source/methods/energy_deposition.rst diff --git a/docs/source/methods/energy_deposition.rst b/docs/source/methods/energy_deposition.rst new file mode 100644 index 0000000000..5bd16802db --- /dev/null +++ b/docs/source/methods/energy_deposition.rst @@ -0,0 +1,134 @@ +.. _methods_heating: + +============================= +Heating and Energy Deposition +============================= + +As particles traverse a problem, some portion of their energy is deposited at +collision sites. There are a variety of mechanisms that contribute to the +energy deposition, including down-scattering from higher to lower energies, +fission events, and reactions with positive Q-values. The information describing +how much energy is deposited for a specific reaction is referred to as +"heating numbers" and can be computed using a program like NJOY with the +``heatr`` module. + +These heating numbers are the product of reaction-specific coefficients and +a reaction cross section + +.. math:: + + H(E) = \phi(E)\sum_i\rho_i\sum_rk_{i, r} + +and has units energy per time, typically eV / s. +Here, :math:`k_{i, r}` are the KERMA [Kinetic Energy Release in Materials] +coefficients for reaction :math:`r` of isotope :math:`i`. +The KERMA coefficients have units energy :math:`\times` cross-section, e.g. +eV-barn, and can be used much like a reaction cross section for the purpose +of tallying energy deposition. + +KERMA coefficients can be computed using the energy-balance method with +a nuclear data processing code like NJOY, which performs the following +iteration over all reactions :math:`r` for all isotopes :math:`i` +requested + +.. math:: + + k_{i, r}(E) = \left(E + Q_{i, r} - \bar{E}_{i, r, n} - \bar{E}_{i, r, \gamma}\right)\sigma_{i, r}(E) + +removing the energy of secondary neutrons and photons from the sum of +incident neutron energy, :math:`E`, and the reaction :math:`Q` value. + +--------------------------- +The Special Case of Fission +--------------------------- + +During a fission event, there are potentially many secondary particles, and all +must be considered. The total energy released in a fission event is typically +broken up into the following categories: + +- :math:`E_{fr}` - kinetic energy of fission fragments +- :math:`E_{n,p}` - energy of prompt fission neutrons +- :math:`E_{n,d}` - energy of delayed fission neutrons +- :math:`E_{\gamma,p}` - energy of prompt fission photons +- :math:`E_{\gamma,d}` - energy of delayed fission photons +- :math:`E_{\beta}` - energy of released :math:`\beta` particles +- :math:`E_{\nu}` - energy of neutrinos + +These components are defined in MT=458 data in a standard ENDF/B-VII file. +All these quantities have some energy dependence, but this dependence is not shown to +make the following demonstrations cleaner. +As neutrinos scarcely interact with matter, the recoverable energy from fission is defined as + +.. math:: + + E_r\equiv E_{fr} + E_{n,p} + E_{n, d} + E_{\gamma, p} + E_{\gamma, d} + E_{\beta} + +Furthermore, the energy of the secondary neutrons and photons is given as +:math:`E_{n, p}` and :math:`E_{\gamma, p}`, respectively. + +NJOY computes the fission KERMA coefficient using this energy-balance method to be + +.. math:: + + k_{i, f}(E) = \left[E + Q(E) - \bar{E}(E)\right]\sigma_{i, f}(E) + = \left[E_{fr} + E_{\gamma, p}\right]\sigma_{i, j}(E) + +.. note:: + + The energy from delayed neutrons and photons and beta particles are intentionally + left out from the NJOY calculations + +--------------------- +OpenMC Implementation +--------------------- + +For fissile isotopes, OpenMC makes modifications to the heating reaction to include +all relevant components of fission energy release. These modifications are made to +the total heating reaction, MT=301. Breaking the total heating number into +a fission and non-fission section, one can write + +.. math:: + + H_i(E) = H_{i, nf}(E) + \left[E_{fr}(E) + E_{\gamma, p}\right]\sigma_{i, f}(E) + +OpenMC seeks to modify the total heating data to include energy from :math:`\beta` particles +and, conditionally, delayed photons. This conditional inclusion depends on the simulation +mode: neutron transport, or coupled neutron-photon transport. The heating due to fission +is removed using MT=318 data, and then re-built using the desired components of fission +energy release from MT=458 data. + +Neutron Transport +----------------- + +For this case, OpenMC instructs ``heatr`` to produce heating coefficients assuming +that energy from photons, :math:`E_{\gamma, p}` and :math:`E_{\gamma, d}`, +is deposited at the fission site. +Let :math:`N901` represent the total heating number returned from this ``heatr`` +run with :math:`N918` reflecting fission heating computed from NJOY. +:math:`M901` represent the following modification + +.. math:: + + M901_{i}(E)\equiv N901_{i}(E) - N918_{i}(E) + + \left[E_{i, fr} + E_{i, \beta} + E_{i, \gamma, p} + + E_{i, \gamma, d}\right]\sigma_{i, f}(E). + +This modified heating data is stored as the MT=901 reaction and will be scored +if ``901`` is included in :attr:`openmc.Tally.scores`. + +Coupled neutron-photon transport +-------------------------------- + +Here, OpenMC instructs ``heatr`` to remove the assumption of local photon energy. +However, the definitions provided in the NJOY manual indicate that, regardless of +this mode, the prompt photon energy is still included in :math:`k_{i, f}`, +and therefore must be manually removed. Let :math:`N301` represent the total +heating number returned from this ``heatr`` run and :math:`M301` be + +.. math:: + + M301_{i}(E)\equiv N301_{i}(E) - N318_{i}(E) + + \left[E_{i, fr}(E) + E_{i, \beta}(E)\right]\sigma_{i, f}(E). + +This modified heating data is stored as the MT=301 reaction and will be scored +if ``301`` is included in :attr:`openmc.Tally.scores`. diff --git a/docs/source/methods/index.rst b/docs/source/methods/index.rst index d8e6ee8190..5eff6c50e6 100644 --- a/docs/source/methods/index.rst +++ b/docs/source/methods/index.rst @@ -18,3 +18,4 @@ Theory and Methodology eigenvalue parallelization cmfd + energy_deposition diff --git a/docs/source/usersguide/tallies.rst b/docs/source/usersguide/tallies.rst index 1676417c66..c147858eb1 100644 --- a/docs/source/usersguide/tallies.rst +++ b/docs/source/usersguide/tallies.rst @@ -261,12 +261,13 @@ The following tables show all valid scores: | |produced by NJOY's HEATR module while for photons, | | |this is tallied from either direct photon energy | | |deposition (analog estimator) or pre-generated | - | |photon heating number. | + | |photon heating number. See :ref:`methods_heating` | +----------------------+---------------------------------------------------+ |heating-local |Total nuclear heating in units of eV per source | | |particle assuming energy from secondary photons is | | |deposited locally. Note that this score should only| - | |be used for incident neutrons. | + | |be used for incident neutrons. See | + | |:ref:`methods_heating`. | +----------------------+---------------------------------------------------+ |kappa-fission |The recoverable energy production rate due to | | |fission. The recoverable energy is defined as the | From 016fc0e43e8de91827f5409411839b7473efacb5 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 10 Sep 2019 15:22:23 -0500 Subject: [PATCH 043/158] Use energy deposition score for depletion Introduce a new subclass of EnergyHelper, EnergyScoreHelper, that computes the system energy using the energy-deposition score. This energy is fed back to the Operator to normalize reaction rates. The energy from the tally is only stored on the helper on the MPI process 0 as to avoid scaling the system energy by the number of processes. During the Operator unpacking, the energy reported by each process is reduced, as the previous implementations took fission reaction rates from the "local materials", e.g. the materials each process is responsible for depleting. The tally results are shared across all processes and only contains a single quantity, the tallied energy deposition across all materials. This mode is controlled by passing the "energy_mode" argument passed to the Operator. The two options are "fission-q" [default and previous behavior] and "energy-deposition" [new features]. If energy_mode indicates using the energy deposition score, the user-supplied fission-q dictionary is not used. (cherry picked from commit d566f3080f05bf9765ebc4b7210c134c0c7bb8aa) --- docs/source/pythonapi/deplete.rst | 1 + openmc/deplete/helpers.py | 42 +++++++++++++++++++++++++++++++ openmc/deplete/operator.py | 31 ++++++++++++++++++----- 3 files changed, 68 insertions(+), 6 deletions(-) diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 5b26eb38aa..985dc05d61 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -95,6 +95,7 @@ total system energy. helpers.ChainFissionHelper helpers.ConstantFissionYieldHelper helpers.DirectReactionRateHelper + helpers.EnergyScoreHelper helpers.FissionYieldCutoffHelper The following classes are abstract classes that can be used to extend the diff --git a/openmc/deplete/helpers.py b/openmc/deplete/helpers.py index c50ffd6ab6..5fa9f0654d 100644 --- a/openmc/deplete/helpers.py +++ b/openmc/deplete/helpers.py @@ -9,6 +9,7 @@ from collections import defaultdict from numpy import dot, zeros, newaxis +from . import comm from openmc.checkvalue import check_type, check_greater_than from openmc.lib import ( Tally, MaterialFilter, EnergyFilter, EnergyFunctionFilter) @@ -157,6 +158,47 @@ class ChainFissionHelper(EnergyHelper): self._energy += dot(fission_rates, self._fission_q_vector) +class EnergyScoreHelper(EnergyHelper): + """Class responsible for obtaining system energy via a tally score + + Attributes + ---------- + nuclides : list of str + List of nuclides with reaction rates. Not needed, but provided + for a consistent API across other :class:`EnergyHelper` + energy : float + System energy [eV] computed from the tally. Will be zero for + all MPI processes that are not the "master" process to avoid + artificially increasing the tallied energy. + + """ + + def __init__(self): + super().__init__() + self._tally = None + + def prepare(self, *args, **kwargs): + """Create a tally for system energy production + + Input arguments are not used, as the only information needed + is :attr:`score` + + """ + self._tally = Tally() + self._tally.scores = ["energy-deposition"] + + def reset(self): + """Obtain system energy from tally + + Only the master process, ``comm.rank == 0`` will + have a non-zero :attr:`energy` taken from the tally. + This avoids accidentally scaling the system power by + the number of MPI processes + """ + super().reset() + if not comm.rank: + self._energy = self._tally.results[0, 0, 1] + # ------------------------------------ # Helper for collapsing fission yields # ------------------------------------ diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index 86dbeb7550..110d7a7a3f 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -13,6 +13,7 @@ from itertools import chain import os import time import xml.etree.ElementTree as ET +from warnings import warn import h5py import numpy as np @@ -27,7 +28,7 @@ from .reaction_rates import ReactionRates from .results_list import ResultsList from .helpers import ( DirectReactionRateHelper, ChainFissionHelper, ConstantFissionYieldHelper, - FissionYieldCutoffHelper, AveragedFissionYieldHelper) + FissionYieldCutoffHelper, AveragedFissionYieldHelper, EnergyScoreHelper) def _distribute(items): @@ -78,9 +79,16 @@ class Operator(TransportOperator): diff_burnable_mats : bool, optional Whether to differentiate burnable materials with multiple instances. Default: False. + energy_mode : {"energy-deposition", "fission-q"} + Indicator for computing system energy. ``"energy-deposition"`` will + compute with a single energy deposition tally, taking fission energy + release data and heating into consideration. ``"fission-q"`` will + use the fission Q values from the depletion chain, not taking + heating into consideration. fission_q : dict, optional Dictionary of nuclides and their fission Q values [eV]. If not given, - values will be pulled from the ``chain_file``. + values will be pulled from the ``chain_file``. Only applicable + if ``"energy_mode" == "fission-q"`` dilute_initial : float, optional Initial atom density [atoms/cm^3] to add for nuclides that are zero in initial condition to ensure they exist in the decay chain. @@ -144,13 +152,22 @@ class Operator(TransportOperator): } def __init__(self, geometry, settings, chain_file=None, prev_results=None, - diff_burnable_mats=False, fission_q=None, - dilute_initial=1.0e3, fission_yield_mode="constant", - fission_yield_opts=None): + diff_burnable_mats=False, energy_mode="fission-q", + fission_q=None, dilute_initial=1.0e3, + fission_yield_mode="constant", fission_yield_opts=None): if fission_yield_mode not in self._fission_helpers: raise KeyError( "fission_yield_mode must be one of {}, not {}".format( ", ".join(self._fission_helpers), fission_yield_mode)) + if energy_mode == "energy-deposition": + if fission_q is not None: + warn("Fission Q dictionary not used if energy deposition " + "is used") + fission_q = None + elif energy_mode != "fission-q": + raise ValueError( + "energy_mode {} not supported. Must be energy-deposition " + "or fission-q".format(energy_mode)) super().__init__(chain_file, fission_q, dilute_initial, prev_results) self.round_number = False self.prev_res = None @@ -204,7 +221,9 @@ class Operator(TransportOperator): # Get classes to assist working with tallies self._rate_helper = DirectReactionRateHelper( self.reaction_rates.n_nuc, self.reaction_rates.n_react) - self._energy_helper = ChainFissionHelper() + self._energy_helper = ( + ChainFissionHelper() if energy_mode == "fission-q" + else EnergyScoreHelper()) # Select and create fission yield helper fission_helper = self._fission_helpers[fission_yield_mode] From ca410a82fe88c4103e1b6a3715881d3fe5eefae3 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Mon, 16 Sep 2019 15:12:42 -0500 Subject: [PATCH 044/158] Ensure strings when setting openmc.lib.Tally.scores In using lists of containing integer reactions as the scores, e.g. ``` t = openmc.lib.Tally() t.scores = [901] ``` The setter would fail as the integers do not have an encode method. This converts all incoming scores to strings prior to passing values to the shared library. --- openmc/lib/tally.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/lib/tally.py b/openmc/lib/tally.py index 97b7437ea3..e6dd3ddbff 100644 --- a/openmc/lib/tally.py +++ b/openmc/lib/tally.py @@ -319,7 +319,7 @@ class Tally(_FortranObjectWithID): @scores.setter def scores(self, scores): scores_ = (c_char_p * len(scores))() - scores_[:] = [x.encode() for x in scores] + scores_[:] = [str(x).encode() for x in scores] _dll.openmc_tally_set_scores(self._index, len(scores), scores_) @property From 74e48521a8cfddf921b680a5b322c911521a0129 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Mon, 16 Sep 2019 15:24:06 -0500 Subject: [PATCH 045/158] Pass MT301 or MT901 to EnergyHelper from Operator Depending on settings.photon_transport, obtain the total system energy from the 301 [heating] or 901 [heating-local] scores. The former is used in coupled neutron-photon transport, as this does not include any energy from neutrons or photons, assuming these particles deposit their energy along their life. MT901 is used for neutron transport and is the default, e.g. if scores is None. This score includes energy from prompt and delayed photons taken from MT458 data. Related PR: #1344 - Ability to generate KERMAs assuming local photon energy deposition --- openmc/deplete/helpers.py | 26 ++++++++++++++++++++++++-- openmc/deplete/operator.py | 8 +++++--- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/openmc/deplete/helpers.py b/openmc/deplete/helpers.py index 5fa9f0654d..d994ab68c5 100644 --- a/openmc/deplete/helpers.py +++ b/openmc/deplete/helpers.py @@ -10,6 +10,7 @@ from collections import defaultdict from numpy import dot, zeros, newaxis from . import comm +import openmc.data from openmc.checkvalue import check_type, check_greater_than from openmc.lib import ( Tally, MaterialFilter, EnergyFilter, EnergyFunctionFilter) @@ -161,6 +162,12 @@ class ChainFissionHelper(EnergyHelper): class EnergyScoreHelper(EnergyHelper): """Class responsible for obtaining system energy via a tally score + Parameters + ---------- + reaction_mt : int or None + Valid score to use when obtaining system energy from openmc. + Defaults to 901 [heating assuming local photons] + Attributes ---------- nuclides : list of str @@ -170,13 +177,28 @@ class EnergyScoreHelper(EnergyHelper): System energy [eV] computed from the tally. Will be zero for all MPI processes that are not the "master" process to avoid artificially increasing the tallied energy. + score : int + MT reaction number that is scored. """ - def __init__(self): + def __init__(self, score=None): super().__init__() + self.score = score self._tally = None + @property + def score(self): + return self._score + + @score.setter + def score(self, value): + if value is None: + self._score = 901 + else: + check_type("score", value, int) + self._score = value + def prepare(self, *args, **kwargs): """Create a tally for system energy production @@ -185,7 +207,7 @@ class EnergyScoreHelper(EnergyHelper): """ self._tally = Tally() - self._tally.scores = ["energy-deposition"] + self._tally.scores = [self.score] def reset(self): """Obtain system energy from tally diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index 110d7a7a3f..eef4acdedf 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -221,9 +221,11 @@ class Operator(TransportOperator): # Get classes to assist working with tallies self._rate_helper = DirectReactionRateHelper( self.reaction_rates.n_nuc, self.reaction_rates.n_react) - self._energy_helper = ( - ChainFissionHelper() if energy_mode == "fission-q" - else EnergyScoreHelper()) + if energy_mode == "fission-q": + self._energy_helper = ChainFissionHelper() + else: + score = 301 if settings.photon_transport else 901 + self._energy_helper = EnergyScoreHelper(score) # Select and create fission yield helper fission_helper = self._fission_helpers[fission_yield_mode] From 0f730d213ec983eef20e2e354fd3db1a85e527cb Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Sun, 8 Sep 2019 22:55:41 -0500 Subject: [PATCH 046/158] Added is_valid method to BoundingBox. --- include/openmc/surface.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/openmc/surface.h b/include/openmc/surface.h index d61ae07cef..9cdc460bb9 100644 --- a/include/openmc/surface.h +++ b/include/openmc/surface.h @@ -83,6 +83,13 @@ struct BoundingBox return *this; } + inline bool is_valid() { + return xmin <= xmax && + ymin <= ymax && + zmin <= zmax; + } + + }; //============================================================================== From 26fbbb6874d9f9cf11547c2551116d45e2f0c65c Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Sun, 8 Sep 2019 23:26:20 -0500 Subject: [PATCH 047/158] Moving to iterator-based method for advancing through the RPN. --- src/cell.cpp | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/cell.cpp b/src/cell.cpp index cfb717c85e..225d2a53df 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -618,16 +618,16 @@ BoundingBox CSGCell::bounding_box_complex(std::vector rpn) { apply_demorgan(rpn); } - // reverse the rpn to make popping easier - std::reverse(rpn.begin(), rpn.end()); + auto it = rpn.begin(); + BoundingBox current = model::surfaces[abs(*it) - 1]->bounding_box(*it > 0); + it++; - BoundingBox current = model::surfaces[abs(rpn.back()) - 1]->bounding_box(rpn.back() > 0); - rpn.pop_back(); - - while (rpn.size()) { + while (it < rpn.end()) { // move through the rpn in twos - int32_t one = rpn.back(); rpn.pop_back(); - int32_t two = rpn.back(); rpn.pop_back(); + int32_t one = *it; + it++; + int32_t two = *it; + it++; // the first token should always be a surface Expects(one < OP_UNION); @@ -646,21 +646,24 @@ BoundingBox CSGCell::bounding_box_complex(std::vector rpn) { subrpn.push_back(two); // add until last two tokens in the sub-rpn are operators // (indicates a right parenthesis) - while (!((subrpn.back() >= OP_UNION) && (*(subrpn.rbegin() + 1) >= OP_UNION))) { - subrpn.push_back(rpn.back()); - rpn.pop_back(); + while (!((*it >= OP_UNION) && (*(it + 1) >= OP_UNION))) { + subrpn.push_back(*it); + it++; } + // add first operator to the subrpn + subrpn.push_back(*it); + it++; + // handle complement case using De Morgan's laws - if (subrpn.back() == OP_COMPLEMENT) { - subrpn.pop_back(); + if (*it == OP_COMPLEMENT) { apply_demorgan(subrpn); - subrpn.push_back(rpn.back()); - rpn.pop_back(); + it++; } // save the last operator, tells us how to combine this region // with our current bounding box - int32_t op = subrpn.back(); subrpn.pop_back(); + int32_t op = *it; + it++; // get bounding box for the subrpn BoundingBox sub_box = bounding_box_complex(subrpn); // combine the sub-rpn bounding box with our current cell box From 85fa32150ad0788ed64ba1cd6011398d33bfd76f Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Mon, 9 Sep 2019 00:15:23 -0500 Subject: [PATCH 048/158] Still needs work --- include/openmc/cell.h | 3 +- src/cell.cpp | 139 ++++++++++++++++++++++++++++++------------ 2 files changed, 101 insertions(+), 41 deletions(-) diff --git a/include/openmc/cell.h b/include/openmc/cell.h index db8d60ed48..2c84955602 100644 --- a/include/openmc/cell.h +++ b/include/openmc/cell.h @@ -212,7 +212,8 @@ protected: bool contains_complex(Position r, Direction u, int32_t on_surface) const; BoundingBox bounding_box_simple() const; static BoundingBox bounding_box_complex(std::vector rpn); - static void apply_demorgan(std::vector& rpn); + static void apply_demorgan(std::vector::iterator start, + std::vector::iterator end); }; //============================================================================== diff --git a/src/cell.cpp b/src/cell.cpp index 225d2a53df..99ac9f43f4 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -598,12 +598,48 @@ BoundingBox CSGCell::bounding_box_simple() const { return bbox; } +auto find_right_parenthesis(std::vector::iterator start, + std::vector& rpn) +{ + // parenthesis level counter + int counter = 1; + std::cout << "FINDING" << std::endl; + // start at the beginning + auto it = start; + while(it < rpn.end()) { + int32_t one = *it; + int32_t two = *(it+1); -void CSGCell::apply_demorgan(std::vector& rpn) { - for (auto& token : rpn) { + std::cout << "Counter: " << counter << std::endl; + std::cout << "One: " << one << std::endl; + std::cout << "Two: " << two << std::endl; + + if (one < OP_UNION && two < OP_UNION) { + counter++; + } else if (one >= OP_UNION && two >= OP_UNION) { + counter--; + } + + if (counter == 0) { + if (two == OP_COMPLEMENT) { it++; } + std::cout << "BREAK" << std::endl; + break; + } + it++; + } + std::cout << "DONE" << std::endl; + return it; +} + +void CSGCell::apply_demorgan(std::vector::iterator start, + std::vector::iterator end) { + auto it = start; + while (it <= end) { + int32_t token = *it; if (token < OP_UNION) { token *= -1; } else if (token == OP_UNION) { token = OP_INTERSECTION; } else if (token == OP_INTERSECTION) { token = OP_UNION; } + it++; } } @@ -615,19 +651,26 @@ BoundingBox CSGCell::bounding_box_complex(std::vector rpn) { // De Morgan's laws immediately if (rpn.back() == OP_COMPLEMENT) { rpn.pop_back(); - apply_demorgan(rpn); + apply_demorgan(rpn.begin(), rpn.end()); } - auto it = rpn.begin(); - BoundingBox current = model::surfaces[abs(*it) - 1]->bounding_box(*it > 0); - it++; + std::cout << "RPN" << std::endl; + for (auto i : rpn) { std::cout << i << " "; } + std::cout << std::endl; + // start with an invalid box + BoundingBox current; + current.xmin = INFTY; + current.xmax = -INFTY; + + auto it = rpn.begin(); while (it < rpn.end()) { // move through the rpn in twos - int32_t one = *it; - it++; - int32_t two = *it; - it++; + int32_t one = *it; it++; + int32_t two = *it; it++; + + std::cout << "One: " << one << std::endl; + std::cout << "Two: " << two << std::endl; // the first token should always be a surface Expects(one < OP_UNION); @@ -639,39 +682,55 @@ BoundingBox CSGCell::bounding_box_complex(std::vector rpn) { current &= model::surfaces[abs(one)-1]->bounding_box(one > 0); } } else { - // two surfaces in a row (left parenthesis), - // create sub-rpn for region in parenthesis - std::vector subrpn; - subrpn.push_back(one); - subrpn.push_back(two); - // add until last two tokens in the sub-rpn are operators - // (indicates a right parenthesis) - while (!((*it >= OP_UNION) && (*(it + 1) >= OP_UNION))) { - subrpn.push_back(*it); - it++; - } - // add first operator to the subrpn - subrpn.push_back(*it); - it++; + auto close = find_right_parenthesis(it, rpn); - // handle complement case using De Morgan's laws - if (*it == OP_COMPLEMENT) { - apply_demorgan(subrpn); - it++; - } - // save the last operator, tells us how to combine this region - // with our current bounding box - int32_t op = *it; - it++; - // get bounding box for the subrpn - BoundingBox sub_box = bounding_box_complex(subrpn); - // combine the sub-rpn bounding box with our current cell box - if (op == OP_UNION) { - current |= sub_box; - } else if (op == OP_INTERSECTION) { - current &= sub_box; + std::vector subrpn(it - 2, close); + + if (*close == OP_COMPLEMENT && *it < OP_UNION) { + apply_demorgan(it - 2, close); + it -= 2; + } else if (close == rpn.end()) { + current = model::surfaces[abs(one)-1]->bounding_box(one > 0); + it--; + } else { + int32_t op = *it; + BoundingBox sub_box = bounding_box_complex(subrpn); + // combine the sub-rpn bounding box with our current cell box + if (op == OP_UNION) { + current |= sub_box; + } else if (op == OP_INTERSECTION) { + current &= sub_box; + } + it = close; } + // // two surfaces in a row (left parenthesis), + // // create sub-rpn for region in parenthesis + // std::vector subrpn; + // subrpn.push_back(one); + // subrpn.push_back(two); + // // add until last two tokens in the sub-rpn are operators + // // (indicates a right parenthesis) + // while (!((*it >= OP_UNION) && (*(it + 1) >= OP_UNION))) { + // subrpn.push_back(*it); + // it++; + // } + + // // add first operator to the subrpn + // subrpn.push_back(*it); + // it++; + + // // handle complement case using De Morgan's laws + // if (*it == OP_COMPLEMENT) { + // apply_demorgan(subrpn); + // it++; + // } + // // save the last operator, tells us how to combine this region + // // with our current bounding box + // int32_t op = *it; + // it++; + // // get bounding box for the subrpn + // BoundingBox sub_box = bounding_box_complex(subrpn); } } From 0d02f8b2d29c0a343eff560f2492ab23f7d13ba0 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Mon, 9 Sep 2019 16:11:48 -0500 Subject: [PATCH 049/158] Working now. Needs cleanup. --- include/openmc/cell.h | 2 +- src/cell.cpp | 144 ++++++++++------------- tests/unit_tests/test_complex_cell_bb.py | 2 +- 3 files changed, 62 insertions(+), 86 deletions(-) diff --git a/include/openmc/cell.h b/include/openmc/cell.h index 2c84955602..471b5c4f85 100644 --- a/include/openmc/cell.h +++ b/include/openmc/cell.h @@ -213,7 +213,7 @@ protected: BoundingBox bounding_box_simple() const; static BoundingBox bounding_box_complex(std::vector rpn); static void apply_demorgan(std::vector::iterator start, - std::vector::iterator end); + std::vector::iterator stop); }; //============================================================================== diff --git a/src/cell.cpp b/src/cell.cpp index 99ac9f43f4..40615c1e89 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -598,53 +598,45 @@ BoundingBox CSGCell::bounding_box_simple() const { return bbox; } + +void CSGCell::apply_demorgan(std::vector::iterator start, + std::vector::iterator stop) +{ + while(start < stop) { + if (*start < OP_UNION) { *start *= -1; } + else if (*start == OP_UNION) { *start = OP_INTERSECTION; } + else if (*start == OP_INTERSECTION) { *start = OP_UNION; } + start++; + } +} + auto find_right_parenthesis(std::vector::iterator start, std::vector& rpn) { // parenthesis level counter - int counter = 1; - std::cout << "FINDING" << std::endl; + int counter = 0; + // start at the beginning auto it = start; while(it < rpn.end()) { int32_t one = *it; int32_t two = *(it+1); - std::cout << "Counter: " << counter << std::endl; - std::cout << "One: " << one << std::endl; - std::cout << "Two: " << two << std::endl; - if (one < OP_UNION && two < OP_UNION) { counter++; } else if (one >= OP_UNION && two >= OP_UNION) { counter--; + while(*it >= OP_UNION) { it++; } + it--; } - if (counter == 0) { - if (two == OP_COMPLEMENT) { it++; } - std::cout << "BREAK" << std::endl; - break; - } + if (counter == 0) { break; } it++; } - std::cout << "DONE" << std::endl; return it; } -void CSGCell::apply_demorgan(std::vector::iterator start, - std::vector::iterator end) { - auto it = start; - while (it <= end) { - int32_t token = *it; - if (token < OP_UNION) { token *= -1; } - else if (token == OP_UNION) { token = OP_INTERSECTION; } - else if (token == OP_INTERSECTION) { token = OP_UNION; } - it++; - } -} - BoundingBox CSGCell::bounding_box_complex(std::vector rpn) { - // if the last operator is a complement op, there is no // sub-region that the complement connects to. This indicates // that the entire region is a complement and we can apply @@ -654,23 +646,23 @@ BoundingBox CSGCell::bounding_box_complex(std::vector rpn) { apply_demorgan(rpn.begin(), rpn.end()); } - std::cout << "RPN" << std::endl; - for (auto i : rpn) { std::cout << i << " "; } - std::cout << std::endl; - - // start with an invalid box - BoundingBox current; - current.xmin = INFTY; - current.xmax = -INFTY; + // check for parenthesis with complement at the start of the rpn + auto initial_right = find_right_parenthesis(rpn.begin(), rpn); + if (initial_right != rpn.end() && *initial_right == OP_COMPLEMENT) { + apply_demorgan(rpn.begin(), initial_right); + rpn.erase(initial_right); + } auto it = rpn.begin(); + BoundingBox current = model::surfaces[abs(*it) - 1]->bounding_box(*it > 0); + it++; + while (it < rpn.end()) { // move through the rpn in twos - int32_t one = *it; it++; - int32_t two = *it; it++; - - std::cout << "One: " << one << std::endl; - std::cout << "Two: " << two << std::endl; + int32_t one = *it; + it++; + int32_t two = *it; + it++; // the first token should always be a surface Expects(one < OP_UNION); @@ -682,55 +674,39 @@ BoundingBox CSGCell::bounding_box_complex(std::vector rpn) { current &= model::surfaces[abs(one)-1]->bounding_box(one > 0); } } else { - - auto close = find_right_parenthesis(it, rpn); - - std::vector subrpn(it - 2, close); - - if (*close == OP_COMPLEMENT && *it < OP_UNION) { - apply_demorgan(it - 2, close); - it -= 2; - } else if (close == rpn.end()) { - current = model::surfaces[abs(one)-1]->bounding_box(one > 0); - it--; - } else { - int32_t op = *it; - BoundingBox sub_box = bounding_box_complex(subrpn); - // combine the sub-rpn bounding box with our current cell box - if (op == OP_UNION) { - current |= sub_box; - } else if (op == OP_INTERSECTION) { - current &= sub_box; - } - it = close; + // two surfaces in a row (left parenthesis), + // create sub-rpn for region in parenthesis + std::vector subrpn; + subrpn.push_back(one); + subrpn.push_back(two); + // add until last two tokens in the sub-rpn are operators + // (indicates a right parenthesis) + while (!((*it >= OP_UNION) && (*(it + 1) >= OP_UNION))) { + subrpn.push_back(*it); + it++; } - // // two surfaces in a row (left parenthesis), - // // create sub-rpn for region in parenthesis - // std::vector subrpn; - // subrpn.push_back(one); - // subrpn.push_back(two); - // // add until last two tokens in the sub-rpn are operators - // // (indicates a right parenthesis) - // while (!((*it >= OP_UNION) && (*(it + 1) >= OP_UNION))) { - // subrpn.push_back(*it); - // it++; - // } - // // add first operator to the subrpn - // subrpn.push_back(*it); - // it++; + // add first operator to the subrpn + subrpn.push_back(*it); + it++; - // // handle complement case using De Morgan's laws - // if (*it == OP_COMPLEMENT) { - // apply_demorgan(subrpn); - // it++; - // } - // // save the last operator, tells us how to combine this region - // // with our current bounding box - // int32_t op = *it; - // it++; - // // get bounding box for the subrpn - // BoundingBox sub_box = bounding_box_complex(subrpn); + // handle complement case using De Morgan's laws + if (*it == OP_COMPLEMENT) { + apply_demorgan(subrpn.begin(), subrpn.end()); + it++; + } + // save the last operator, tells us how to combine this region + // with our current bounding box + int32_t op = *it; + it++; + // get bounding box for the subrpn + BoundingBox sub_box = bounding_box_complex(subrpn); + // combine the sub-rpn bounding box with our current cell box + if (op == OP_UNION) { + current |= sub_box; + } else if (op == OP_INTERSECTION) { + current &= sub_box; + } } } diff --git a/tests/unit_tests/test_complex_cell_bb.py b/tests/unit_tests/test_complex_cell_bb.py index 39431684d9..8db20f05c1 100644 --- a/tests/unit_tests/test_complex_cell_bb.py +++ b/tests/unit_tests/test_complex_cell_bb.py @@ -47,7 +47,7 @@ def complex_cell(run_in_tmpdir, mpi_intracomm): c1.region = ~(-s3 | +s4 | ~(+s13 & -s14)) c2 = openmc.Cell(fill=u238) - c2.region = +s2 & -s5 & +s12 & -s15 & ~(+s3 & -s4 & +s13 & -s14) + c2.region = ~(+s3 & -s4 & +s13 & -s14) & +s2 & -s5 & +s12 & -s15 c3 = openmc.Cell(fill=zr90) c3.region = ((+s1 & -s7 & +s17 & -s16) | (+s7 & -s6 & +s11 & -s17)) \ From 789d55a35a6ef75e3aa67aac76bd3cbed60e24d7 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Wed, 11 Sep 2019 09:32:26 -0500 Subject: [PATCH 050/158] Removing complement operator in a separate step. --- include/openmc/cell.h | 4 +++ src/cell.cpp | 84 ++++++++++++++++++++++--------------------- 2 files changed, 48 insertions(+), 40 deletions(-) diff --git a/include/openmc/cell.h b/include/openmc/cell.h index 471b5c4f85..90c03e3a90 100644 --- a/include/openmc/cell.h +++ b/include/openmc/cell.h @@ -214,6 +214,10 @@ protected: static BoundingBox bounding_box_complex(std::vector rpn); static void apply_demorgan(std::vector::iterator start, std::vector::iterator stop); + static void remove_complements(std::vector& rpn); + static std::vector::iterator + find_left_parenthesis(std::vector::iterator start, + const std::vector& rpn); }; //============================================================================== diff --git a/src/cell.cpp b/src/cell.cpp index 40615c1e89..f33926b303 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -598,7 +598,6 @@ BoundingBox CSGCell::bounding_box_simple() const { return bbox; } - void CSGCell::apply_demorgan(std::vector::iterator start, std::vector::iterator stop) { @@ -610,48 +609,62 @@ void CSGCell::apply_demorgan(std::vector::iterator start, } } -auto find_right_parenthesis(std::vector::iterator start, - std::vector& rpn) -{ - // parenthesis level counter - int counter = 0; +std::vector::iterator +CSGCell::find_left_parenthesis(std::vector::iterator start, + const std::vector& rpn) { - // start at the beginning + int level = 0; auto it = start; - while(it < rpn.end()) { + while (it != rpn.begin()) { + // move through the rpn two at a time int32_t one = *it; - int32_t two = *(it+1); + int32_t two = *(it - 1); + // decrement parenthesis level if there are two adjacent surfaces if (one < OP_UNION && two < OP_UNION) { - counter++; + level--; + // increment if there are two adjacent operators } else if (one >= OP_UNION && two >= OP_UNION) { - counter--; - while(*it >= OP_UNION) { it++; } - it--; + level++; } - if (counter == 0) { break; } - it++; + // if the level gets to zero, return the position + if (level == 0) { + // move the iterator back one before leaving the loop + // so that all tokens in the parenthesis are included + it--; + break; + } + + // continue loop + it--; } return it; } -BoundingBox CSGCell::bounding_box_complex(std::vector rpn) { - // if the last operator is a complement op, there is no - // sub-region that the complement connects to. This indicates - // that the entire region is a complement and we can apply - // De Morgan's laws immediately +void CSGCell::remove_complements(std::vector& rpn) { if (rpn.back() == OP_COMPLEMENT) { - rpn.pop_back(); - apply_demorgan(rpn.begin(), rpn.end()); + apply_demorgan(rpn.begin(), rpn.end()); + rpn.pop_back(); } - // check for parenthesis with complement at the start of the rpn - auto initial_right = find_right_parenthesis(rpn.begin(), rpn); - if (initial_right != rpn.end() && *initial_right == OP_COMPLEMENT) { - apply_demorgan(rpn.begin(), initial_right); - rpn.erase(initial_right); + std::vector::iterator it = std::find(rpn.begin(), rpn.end(), OP_COMPLEMENT); + while (it != rpn.end()) { + // find the opening parenthesis (if any) + auto left = find_left_parenthesis(it, rpn); + // apply DeMorgan's law to any surfaces/operators between these + // positions in the RPN + apply_demorgan(left, it); + rpn.erase(it); + + // update iterator position + it = std::find(rpn.begin(), rpn.end(), OP_COMPLEMENT); } +} + +BoundingBox CSGCell::bounding_box_complex(std::vector rpn) { + + remove_complements(rpn); auto it = rpn.begin(); BoundingBox current = model::surfaces[abs(*it) - 1]->bounding_box(*it > 0); @@ -659,10 +672,8 @@ BoundingBox CSGCell::bounding_box_complex(std::vector rpn) { while (it < rpn.end()) { // move through the rpn in twos - int32_t one = *it; - it++; - int32_t two = *it; - it++; + int32_t one = *it; it++; + int32_t two = *it; it++; // the first token should always be a surface Expects(one < OP_UNION); @@ -682,19 +693,12 @@ BoundingBox CSGCell::bounding_box_complex(std::vector rpn) { // add until last two tokens in the sub-rpn are operators // (indicates a right parenthesis) while (!((*it >= OP_UNION) && (*(it + 1) >= OP_UNION))) { - subrpn.push_back(*it); - it++; + subrpn.push_back(*it); it++; } // add first operator to the subrpn - subrpn.push_back(*it); - it++; + subrpn.push_back(*it); it++; - // handle complement case using De Morgan's laws - if (*it == OP_COMPLEMENT) { - apply_demorgan(subrpn.begin(), subrpn.end()); - it++; - } // save the last operator, tells us how to combine this region // with our current bounding box int32_t op = *it; From c7471af7bfacbb742973ab5fa143678bcec3d888 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Wed, 11 Sep 2019 10:01:34 -0500 Subject: [PATCH 051/158] Clenaing and comments. No longer need to address the special case of a complement at the back of the RPN. --- src/cell.cpp | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/cell.cpp b/src/cell.cpp index f33926b303..69d398fda6 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -612,7 +612,7 @@ void CSGCell::apply_demorgan(std::vector::iterator start, std::vector::iterator CSGCell::find_left_parenthesis(std::vector::iterator start, const std::vector& rpn) { - + // start search at zero int level = 0; auto it = start; while (it != rpn.begin()) { @@ -643,12 +643,7 @@ CSGCell::find_left_parenthesis(std::vector::iterator start, } void CSGCell::remove_complements(std::vector& rpn) { - if (rpn.back() == OP_COMPLEMENT) { - apply_demorgan(rpn.begin(), rpn.end()); - rpn.pop_back(); - } - - std::vector::iterator it = std::find(rpn.begin(), rpn.end(), OP_COMPLEMENT); + auto it = std::find(rpn.begin(), rpn.end(), OP_COMPLEMENT); while (it != rpn.end()) { // find the opening parenthesis (if any) auto left = find_left_parenthesis(it, rpn); @@ -663,13 +658,15 @@ void CSGCell::remove_complements(std::vector& rpn) { } BoundingBox CSGCell::bounding_box_complex(std::vector rpn) { - + // remove complements by adjusting surface signs and operators remove_complements(rpn); + // use the first token to set the bounding box auto it = rpn.begin(); BoundingBox current = model::surfaces[abs(*it) - 1]->bounding_box(*it > 0); it++; + // loop over tokens while (it < rpn.end()) { // move through the rpn in twos int32_t one = *it; it++; @@ -713,7 +710,6 @@ BoundingBox CSGCell::bounding_box_complex(std::vector rpn) { } } } - return current; } From 3dfc2da2a5e82ec12cc3cca0cb6f8493c7de71e2 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Wed, 11 Sep 2019 11:00:08 -0500 Subject: [PATCH 052/158] Removing unused bounding box method. --- include/openmc/surface.h | 7 ------- 1 file changed, 7 deletions(-) diff --git a/include/openmc/surface.h b/include/openmc/surface.h index 9cdc460bb9..d61ae07cef 100644 --- a/include/openmc/surface.h +++ b/include/openmc/surface.h @@ -83,13 +83,6 @@ struct BoundingBox return *this; } - inline bool is_valid() { - return xmin <= xmax && - ymin <= ymax && - zmin <= zmax; - } - - }; //============================================================================== From 5bb3d582c9c1ae815c332861ea5df0063b3ca337 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Thu, 12 Sep 2019 09:24:26 -0500 Subject: [PATCH 053/158] Consolidating increment operators. Using iterator to setup the subrpn to avoid eating memory if something goes wrong. --- src/cell.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/cell.cpp b/src/cell.cpp index 69d398fda6..f560698aa2 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -616,7 +616,7 @@ CSGCell::find_left_parenthesis(std::vector::iterator start, int level = 0; auto it = start; while (it != rpn.begin()) { - // move through the rpn two at a time + // look at two tokens at a time int32_t one = *it; int32_t two = *(it - 1); @@ -636,7 +636,7 @@ CSGCell::find_left_parenthesis(std::vector::iterator start, break; } - // continue loop + // continue loop, one token at a time it--; } return it; @@ -669,8 +669,8 @@ BoundingBox CSGCell::bounding_box_complex(std::vector rpn) { // loop over tokens while (it < rpn.end()) { // move through the rpn in twos - int32_t one = *it; it++; - int32_t two = *it; it++; + int32_t one = *it++; + int32_t two = *it++; // the first token should always be a surface Expects(one < OP_UNION); @@ -684,22 +684,22 @@ BoundingBox CSGCell::bounding_box_complex(std::vector rpn) { } else { // two surfaces in a row (left parenthesis), // create sub-rpn for region in parenthesis - std::vector subrpn; - subrpn.push_back(one); - subrpn.push_back(two); // add until last two tokens in the sub-rpn are operators // (indicates a right parenthesis) - while (!((*it >= OP_UNION) && (*(it + 1) >= OP_UNION))) { - subrpn.push_back(*it); it++; + auto subrpn_start = it - 2; // include current tokens + auto subrpn_end = it; + while (!((*subrpn_end >= OP_UNION) && (*(subrpn_end + 1) >= OP_UNION))) { + subrpn_end++; } - // add first operator to the subrpn - subrpn.push_back(*it); it++; + // create the subrpn, including the first of our current tokens + std::vector subrpn(subrpn_start, ++subrpn_end); // save the last operator, tells us how to combine this region // with our current bounding box - int32_t op = *it; - it++; + int32_t op = *subrpn_end++; + it = subrpn_end; + // get bounding box for the subrpn BoundingBox sub_box = bounding_box_complex(subrpn); // combine the sub-rpn bounding box with our current cell box From 25df513a77819d275cc5d98d2a1d5100f3c0c037 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Thu, 12 Sep 2019 09:29:12 -0500 Subject: [PATCH 054/158] Renaming function. --- include/openmc/cell.h | 2 +- src/cell.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/openmc/cell.h b/include/openmc/cell.h index 90c03e3a90..2cd98ada1a 100644 --- a/include/openmc/cell.h +++ b/include/openmc/cell.h @@ -214,7 +214,7 @@ protected: static BoundingBox bounding_box_complex(std::vector rpn); static void apply_demorgan(std::vector::iterator start, std::vector::iterator stop); - static void remove_complements(std::vector& rpn); + static void remove_complement_ops(std::vector& rpn); static std::vector::iterator find_left_parenthesis(std::vector::iterator start, const std::vector& rpn); diff --git a/src/cell.cpp b/src/cell.cpp index f560698aa2..7ad48bd326 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -642,7 +642,7 @@ CSGCell::find_left_parenthesis(std::vector::iterator start, return it; } -void CSGCell::remove_complements(std::vector& rpn) { +void CSGCell::remove_complement_ops(std::vector& rpn) { auto it = std::find(rpn.begin(), rpn.end(), OP_COMPLEMENT); while (it != rpn.end()) { // find the opening parenthesis (if any) @@ -659,7 +659,7 @@ void CSGCell::remove_complements(std::vector& rpn) { BoundingBox CSGCell::bounding_box_complex(std::vector rpn) { // remove complements by adjusting surface signs and operators - remove_complements(rpn); + remove_complement_ops(rpn); // use the first token to set the bounding box auto it = rpn.begin(); From 36fb89dead8d79b5b144d3035ea940ae6de9a719 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Fri, 13 Sep 2019 07:58:27 -0500 Subject: [PATCH 055/158] Making analagous function for finding the right parenthesis. --- include/openmc/cell.h | 4 ++++ src/cell.cpp | 43 +++++++++++++++++++++++++++++++++++++------ 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/include/openmc/cell.h b/include/openmc/cell.h index 2cd98ada1a..f6524c6ee3 100644 --- a/include/openmc/cell.h +++ b/include/openmc/cell.h @@ -218,6 +218,10 @@ protected: static std::vector::iterator find_left_parenthesis(std::vector::iterator start, const std::vector& rpn); + static std::vector::iterator + find_right_parenthesis(std::vector::iterator start, + const std::vector& rpn); + }; //============================================================================== diff --git a/src/cell.cpp b/src/cell.cpp index 7ad48bd326..bc0acbe73d 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -642,6 +642,40 @@ CSGCell::find_left_parenthesis(std::vector::iterator start, return it; } +std::vector::iterator +CSGCell::find_right_parenthesis(std::vector::iterator start, + const std::vector& rpn) { + // start search at zero + int level = 0; + auto it = start; + while (it != rpn.end()) { + // look at two tokens at a time + int32_t one = *it; + int32_t two = *(it + 1); + + // decrement parenthesis level if there are two adjacent surfaces + if (one < OP_UNION && two < OP_UNION) { + level++; + // increment if there are two adjacent operators + } else if (one >= OP_UNION && two >= OP_UNION) { + level--; + } + + // if the level gets to zero, return the position + if (level == 0) { + // move the iterator back one before leaving the loop + // so that all tokens in the parenthesis are included + it++; + break; + } + + // continue loop, one token at a time + it++; + } + return it; +} + + void CSGCell::remove_complement_ops(std::vector& rpn) { auto it = std::find(rpn.begin(), rpn.end(), OP_COMPLEMENT); while (it != rpn.end()) { @@ -687,20 +721,17 @@ BoundingBox CSGCell::bounding_box_complex(std::vector rpn) { // add until last two tokens in the sub-rpn are operators // (indicates a right parenthesis) auto subrpn_start = it - 2; // include current tokens - auto subrpn_end = it; - while (!((*subrpn_end >= OP_UNION) && (*(subrpn_end + 1) >= OP_UNION))) { - subrpn_end++; - } + auto subrpn_end = find_right_parenthesis(subrpn_start, rpn); // create the subrpn, including the first of our current tokens - std::vector subrpn(subrpn_start, ++subrpn_end); + std::vector subrpn(subrpn_start, subrpn_end); // save the last operator, tells us how to combine this region // with our current bounding box int32_t op = *subrpn_end++; it = subrpn_end; - // get bounding box for the subrpn + // get bounding box for the sub-rpn BoundingBox sub_box = bounding_box_complex(subrpn); // combine the sub-rpn bounding box with our current cell box if (op == OP_UNION) { From 962f3d46f1947d3d8584f66dd9770c9a41bc1259 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Fri, 13 Sep 2019 08:29:22 -0500 Subject: [PATCH 056/158] Adding documentation for some of these new methods. --- include/openmc/cell.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/include/openmc/cell.h b/include/openmc/cell.h index f6524c6ee3..5e53e3bc43 100644 --- a/include/openmc/cell.h +++ b/include/openmc/cell.h @@ -212,12 +212,24 @@ protected: bool contains_complex(Position r, Direction u, int32_t on_surface) const; BoundingBox bounding_box_simple() const; static BoundingBox bounding_box_complex(std::vector rpn); + + //! Applies DeMorgan's laws to a section of the RPN static void apply_demorgan(std::vector::iterator start, std::vector::iterator stop); + + //! Removes complement operators from the RPN static void remove_complement_ops(std::vector& rpn); + + //! Returns the beginning position of a parenthesis block (immediately before + //! two surface tokens) in the RPN given a starting position at the end of + //! that block (immediately after two surface tokens) static std::vector::iterator find_left_parenthesis(std::vector::iterator start, const std::vector& rpn); + + //! Returns the ending position of a parenthesis block (immediately after two + //! operator tokens) in the RPN given a starting position at the beginning of + //! that block (immediately before two surface tokens) static std::vector::iterator find_right_parenthesis(std::vector::iterator start, const std::vector& rpn); From ec266e3e0b777bbe8a8e0238bf3809f8e34a60b7 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Fri, 13 Sep 2019 11:35:20 -0500 Subject: [PATCH 057/158] Updating comments and a variable name --- src/cell.cpp | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/cell.cpp b/src/cell.cpp index bc0acbe73d..7beb98cc2f 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -613,7 +613,7 @@ std::vector::iterator CSGCell::find_left_parenthesis(std::vector::iterator start, const std::vector& rpn) { // start search at zero - int level = 0; + int parenthesis_level = 0; auto it = start; while (it != rpn.begin()) { // look at two tokens at a time @@ -622,16 +622,16 @@ CSGCell::find_left_parenthesis(std::vector::iterator start, // decrement parenthesis level if there are two adjacent surfaces if (one < OP_UNION && two < OP_UNION) { - level--; + parenthesis_level--; // increment if there are two adjacent operators } else if (one >= OP_UNION && two >= OP_UNION) { - level++; + parenthesis_level++; } // if the level gets to zero, return the position - if (level == 0) { + if (parenthesis_level == 0) { // move the iterator back one before leaving the loop - // so that all tokens in the parenthesis are included + // so that all tokens in the parenthesis block are included it--; break; } @@ -645,26 +645,25 @@ CSGCell::find_left_parenthesis(std::vector::iterator start, std::vector::iterator CSGCell::find_right_parenthesis(std::vector::iterator start, const std::vector& rpn) { - // start search at zero - int level = 0; + int parenthesis_level = 0; auto it = start; while (it != rpn.end()) { // look at two tokens at a time int32_t one = *it; int32_t two = *(it + 1); - // decrement parenthesis level if there are two adjacent surfaces + // increment parenthesis level if there are two adjacent surfaces if (one < OP_UNION && two < OP_UNION) { - level++; - // increment if there are two adjacent operators + parenthesis_level++; + // decrement if there are two adjacent operators } else if (one >= OP_UNION && two >= OP_UNION) { - level--; + parenthesis_level--; } // if the level gets to zero, return the position - if (level == 0) { - // move the iterator back one before leaving the loop - // so that all tokens in the parenthesis are included + if (parenthesis_level == 0) { + // move the iterator forward one before leaving the loop + // so that all tokens in the parenthesis block are included it++; break; } @@ -675,17 +674,18 @@ CSGCell::find_right_parenthesis(std::vector::iterator start, return it; } - void CSGCell::remove_complement_ops(std::vector& rpn) { auto it = std::find(rpn.begin(), rpn.end(), OP_COMPLEMENT); while (it != rpn.end()) { // find the opening parenthesis (if any) auto left = find_left_parenthesis(it, rpn); + std::vector tmp(left, it+1); + // apply DeMorgan's law to any surfaces/operators between these // positions in the RPN apply_demorgan(left, it); + // remove complement operator rpn.erase(it); - // update iterator position it = std::find(rpn.begin(), rpn.end(), OP_COMPLEMENT); } @@ -718,8 +718,6 @@ BoundingBox CSGCell::bounding_box_complex(std::vector rpn) { } else { // two surfaces in a row (left parenthesis), // create sub-rpn for region in parenthesis - // add until last two tokens in the sub-rpn are operators - // (indicates a right parenthesis) auto subrpn_start = it - 2; // include current tokens auto subrpn_end = find_right_parenthesis(subrpn_start, rpn); From 8ebfdc3eb78ce68f7441c70f2c649ffe501c8e0e Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Fri, 13 Sep 2019 13:45:18 -0500 Subject: [PATCH 058/158] Improving documentation. --- include/openmc/cell.h | 7 +++++++ src/cell.cpp | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/include/openmc/cell.h b/include/openmc/cell.h index 5e53e3bc43..5ba714570a 100644 --- a/include/openmc/cell.h +++ b/include/openmc/cell.h @@ -214,15 +214,20 @@ protected: static BoundingBox bounding_box_complex(std::vector rpn); //! Applies DeMorgan's laws to a section of the RPN + //! \param start Starting point for token modification + //! \param stop Stopping point for token modification static void apply_demorgan(std::vector::iterator start, std::vector::iterator stop); //! Removes complement operators from the RPN + //! \param rpn The rpn to remove complement operators from. static void remove_complement_ops(std::vector& rpn); //! Returns the beginning position of a parenthesis block (immediately before //! two surface tokens) in the RPN given a starting position at the end of //! that block (immediately after two surface tokens) + //! \param start Starting position of the search + //! \param rpn The rpn being searched static std::vector::iterator find_left_parenthesis(std::vector::iterator start, const std::vector& rpn); @@ -230,6 +235,8 @@ protected: //! Returns the ending position of a parenthesis block (immediately after two //! operator tokens) in the RPN given a starting position at the beginning of //! that block (immediately before two surface tokens) + //! \param start Starting position of the search + //! \param rpn The rpn being searched static std::vector::iterator find_right_parenthesis(std::vector::iterator start, const std::vector& rpn); diff --git a/src/cell.cpp b/src/cell.cpp index 7beb98cc2f..d87af54e47 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -601,7 +601,7 @@ BoundingBox CSGCell::bounding_box_simple() const { void CSGCell::apply_demorgan(std::vector::iterator start, std::vector::iterator stop) { - while(start < stop) { + while (start < stop) { if (*start < OP_UNION) { *start *= -1; } else if (*start == OP_UNION) { *start = OP_INTERSECTION; } else if (*start == OP_INTERSECTION) { *start = OP_UNION; } From db2cf9eea693eb8725cff25db9fccafd6e5899b0 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Mon, 16 Sep 2019 11:25:59 -0500 Subject: [PATCH 059/158] Updating bounding box to a stack-based traversal of the RPN for complex cells. --- src/cell.cpp | 59 ++++++++++++++++------------------------------------ 1 file changed, 18 insertions(+), 41 deletions(-) diff --git a/src/cell.cpp b/src/cell.cpp index d87af54e47..914391ae22 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -695,51 +695,28 @@ BoundingBox CSGCell::bounding_box_complex(std::vector rpn) { // remove complements by adjusting surface signs and operators remove_complement_ops(rpn); - // use the first token to set the bounding box - auto it = rpn.begin(); - BoundingBox current = model::surfaces[abs(*it) - 1]->bounding_box(*it > 0); - it++; + std::vector stack(rpn.size()); + int i_stack = -1; - // loop over tokens - while (it < rpn.end()) { - // move through the rpn in twos - int32_t one = *it++; - int32_t two = *it++; - - // the first token should always be a surface - Expects(one < OP_UNION); - - if (two >= OP_UNION) { - if (two == OP_UNION) { - current |= model::surfaces[abs(one)-1]->bounding_box(one > 0); - } else if (two == OP_INTERSECTION) { - current &= model::surfaces[abs(one)-1]->bounding_box(one > 0); - } + for (auto& token : rpn) { + if (token == OP_UNION) { + stack[i_stack - 1] = stack[i_stack - 1] | stack[i_stack]; + i_stack--; + } else if (token == OP_INTERSECTION) { + stack[i_stack - 1] = stack[i_stack - 1] & stack[i_stack]; + i_stack--; } else { - // two surfaces in a row (left parenthesis), - // create sub-rpn for region in parenthesis - auto subrpn_start = it - 2; // include current tokens - auto subrpn_end = find_right_parenthesis(subrpn_start, rpn); - - // create the subrpn, including the first of our current tokens - std::vector subrpn(subrpn_start, subrpn_end); - - // save the last operator, tells us how to combine this region - // with our current bounding box - int32_t op = *subrpn_end++; - it = subrpn_end; - - // get bounding box for the sub-rpn - BoundingBox sub_box = bounding_box_complex(subrpn); - // combine the sub-rpn bounding box with our current cell box - if (op == OP_UNION) { - current |= sub_box; - } else if (op == OP_INTERSECTION) { - current &= sub_box; - } + i_stack++; + stack[i_stack] = model::surfaces[abs(token) - 1]->bounding_box(token > 0); } } - return current; + + if (i_stack == 0) { + return stack[i_stack]; + } else { + return BoundingBox(); + } + } BoundingBox CSGCell::bounding_box() const { From 452ad5ac7dd231c5f77b662c7b7e08c0a35822e3 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Mon, 16 Sep 2019 16:32:29 -0500 Subject: [PATCH 060/158] Avoid zero division when normalizing reaction rates If the system energy is found to be zero, then Operator.__call__ will exit the simulation using comm.Abort / sys.exit if no mpi4py. Without this, the reaction rates will be scaled by power / 0, causing errors downstream in setting material compositions. --- openmc/deplete/dummy_comm.py | 5 +++++ openmc/deplete/operator.py | 20 +++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/openmc/deplete/dummy_comm.py b/openmc/deplete/dummy_comm.py index b3fa272648..2648fdedce 100644 --- a/openmc/deplete/dummy_comm.py +++ b/openmc/deplete/dummy_comm.py @@ -1,3 +1,5 @@ +import sys + class DummyCommunicator(object): rank = 0 size = 1 @@ -25,3 +27,6 @@ class DummyCommunicator(object): def scatter(self, sendobj, root=0): return sendobj[0] + + def Abort(self, exit_code_or_msg): + sys.exit(exit_code_or_msg) diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index eef4acdedf..c9b991d1d1 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -7,6 +7,7 @@ densities is all done in-memory instead of through the filesystem. """ +import sys import copy from collections import OrderedDict from itertools import chain @@ -237,6 +238,10 @@ class Operator(TransportOperator): def __call__(self, vec, power): """Runs a simulation. + Simulation will abort under the following circumstances: + + 1) No energy is computed using OpenMC tallies. + Parameters ---------- vec : list of numpy.ndarray @@ -272,7 +277,16 @@ class Operator(TransportOperator): time_openmc = time.time() # Extract results - op_result = self._unpack_tallies_and_normalize(power) + try: + op_result = self._unpack_tallies_and_normalize(power) + except ZeroDivisionError: + if comm.rank == 0: + sys.stderr.flush() + print(" No energy reported from openmc tallies. Do you have " + "MT901 data?\n", file=sys.stderr, flush=True) + comm.barrier() + comm.Abort(1) + return copy.deepcopy(op_result) @@ -655,6 +669,10 @@ class Operator(TransportOperator): # J / s / source neutron energy = comm.allreduce(self._energy_helper.energy) + # Guard against divide by zero + if energy == 0: + raise ZeroDivisionError + # Scale reaction rates to obtain units of reactions/sec rates *= power / energy From 952cd2a3cf590c762198161bbec4dfc4b12d842f Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Mon, 16 Sep 2019 16:49:23 -0500 Subject: [PATCH 061/158] Doc fix for ResultsList.get_depletion_time --- openmc/deplete/results_list.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openmc/deplete/results_list.py b/openmc/deplete/results_list.py index 3b5166ed8a..1530d3955c 100644 --- a/openmc/deplete/results_list.py +++ b/openmc/deplete/results_list.py @@ -137,7 +137,7 @@ class ResultsList(list): def get_depletion_time(self): """Return an array of the average time to deplete a material - ..note:: + .. note:: Will have one fewer row than number of other methods, like :meth:`get_eigenvalues`, because no depletion @@ -145,7 +145,6 @@ class ResultsList(list): Returns ------- - times : :class:`numpy.ndarray` Vector of average time to deplete a single material across all processes and materials. From c0e32b751bd6b3d3e64939a43227adb7de079b4e Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Mon, 16 Sep 2019 23:48:52 -0500 Subject: [PATCH 062/158] Addressing comments from @paulromano. --- include/openmc/cell.h | 9 --------- src/cell.cpp | 40 ++-------------------------------------- 2 files changed, 2 insertions(+), 47 deletions(-) diff --git a/include/openmc/cell.h b/include/openmc/cell.h index 5ba714570a..7206938c40 100644 --- a/include/openmc/cell.h +++ b/include/openmc/cell.h @@ -232,15 +232,6 @@ protected: find_left_parenthesis(std::vector::iterator start, const std::vector& rpn); - //! Returns the ending position of a parenthesis block (immediately after two - //! operator tokens) in the RPN given a starting position at the beginning of - //! that block (immediately before two surface tokens) - //! \param start Starting position of the search - //! \param rpn The rpn being searched - static std::vector::iterator - find_right_parenthesis(std::vector::iterator start, - const std::vector& rpn); - }; //============================================================================== diff --git a/src/cell.cpp b/src/cell.cpp index 914391ae22..3a7a2598fc 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -642,38 +642,6 @@ CSGCell::find_left_parenthesis(std::vector::iterator start, return it; } -std::vector::iterator -CSGCell::find_right_parenthesis(std::vector::iterator start, - const std::vector& rpn) { - int parenthesis_level = 0; - auto it = start; - while (it != rpn.end()) { - // look at two tokens at a time - int32_t one = *it; - int32_t two = *(it + 1); - - // increment parenthesis level if there are two adjacent surfaces - if (one < OP_UNION && two < OP_UNION) { - parenthesis_level++; - // decrement if there are two adjacent operators - } else if (one >= OP_UNION && two >= OP_UNION) { - parenthesis_level--; - } - - // if the level gets to zero, return the position - if (parenthesis_level == 0) { - // move the iterator forward one before leaving the loop - // so that all tokens in the parenthesis block are included - it++; - break; - } - - // continue loop, one token at a time - it++; - } - return it; -} - void CSGCell::remove_complement_ops(std::vector& rpn) { auto it = std::find(rpn.begin(), rpn.end(), OP_COMPLEMENT); while (it != rpn.end()) { @@ -711,12 +679,8 @@ BoundingBox CSGCell::bounding_box_complex(std::vector rpn) { } } - if (i_stack == 0) { - return stack[i_stack]; - } else { - return BoundingBox(); - } - + Ensures(i_stack == 0); + return stack.front(); } BoundingBox CSGCell::bounding_box() const { From 6b386f8447071212561be030f7bb9b7ed237406f Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 17 Sep 2019 11:34:20 -0500 Subject: [PATCH 063/158] Use string score names for EnergyScoreHelper Operator defaults to using "heating-local" if not running in coupled photon transport. --- openmc/deplete/helpers.py | 25 ++++++------------------- openmc/deplete/operator.py | 2 +- openmc/lib/tally.py | 2 +- 3 files changed, 8 insertions(+), 21 deletions(-) diff --git a/openmc/deplete/helpers.py b/openmc/deplete/helpers.py index d994ab68c5..8e87c532ab 100644 --- a/openmc/deplete/helpers.py +++ b/openmc/deplete/helpers.py @@ -10,7 +10,6 @@ from collections import defaultdict from numpy import dot, zeros, newaxis from . import comm -import openmc.data from openmc.checkvalue import check_type, check_greater_than from openmc.lib import ( Tally, MaterialFilter, EnergyFilter, EnergyFunctionFilter) @@ -164,9 +163,9 @@ class EnergyScoreHelper(EnergyHelper): Parameters ---------- - reaction_mt : int or None - Valid score to use when obtaining system energy from openmc. - Defaults to 901 [heating assuming local photons] + score : string + Valid score to use when obtaining system energy from OpenMC. + Defaults to "heating-local" Attributes ---------- @@ -177,28 +176,16 @@ class EnergyScoreHelper(EnergyHelper): System energy [eV] computed from the tally. Will be zero for all MPI processes that are not the "master" process to avoid artificially increasing the tallied energy. - score : int - MT reaction number that is scored. + score : str + Score used to obtain system energy """ - def __init__(self, score=None): + def __init__(self, score="heating-local"): super().__init__() self.score = score self._tally = None - @property - def score(self): - return self._score - - @score.setter - def score(self, value): - if value is None: - self._score = 901 - else: - check_type("score", value, int) - self._score = value - def prepare(self, *args, **kwargs): """Create a tally for system energy production diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index c9b991d1d1..3558759b4e 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -225,7 +225,7 @@ class Operator(TransportOperator): if energy_mode == "fission-q": self._energy_helper = ChainFissionHelper() else: - score = 301 if settings.photon_transport else 901 + score = "heating" if settings.photon_transport else "heating-local" self._energy_helper = EnergyScoreHelper(score) # Select and create fission yield helper diff --git a/openmc/lib/tally.py b/openmc/lib/tally.py index e6dd3ddbff..97b7437ea3 100644 --- a/openmc/lib/tally.py +++ b/openmc/lib/tally.py @@ -319,7 +319,7 @@ class Tally(_FortranObjectWithID): @scores.setter def scores(self, scores): scores_ = (c_char_p * len(scores))() - scores_[:] = [str(x).encode() for x in scores] + scores_[:] = [x.encode() for x in scores] _dll.openmc_tally_set_scores(self._index, len(scores), scores_) @property From 7bf455d72284895259a0615e2c98c184598335a4 Mon Sep 17 00:00:00 2001 From: rockfool Date: Tue, 17 Sep 2019 12:36:43 -0400 Subject: [PATCH 064/158] add white boundary in surface.py --- openmc/surface.py | 238 +++++++++++++++++++++++----------------------- 1 file changed, 117 insertions(+), 121 deletions(-) diff --git a/openmc/surface.py b/openmc/surface.py index 4bbcb5e4c4..2ffbf0722b 100644 --- a/openmc/surface.py +++ b/openmc/surface.py @@ -277,49 +277,48 @@ class Surface(IDManagerMixin, metaclass=ABCMeta): # Create the Surface based on its type if surf_type == 'x-plane': x0 = coeffs[0] - surface = XPlane(surface_id, bc, x0, name) + surface = XPlane(x0, bc, name, surface_id) elif surf_type == 'y-plane': y0 = coeffs[0] - surface = YPlane(surface_id, bc, y0, name) + surface = YPlane(y0, bc, name, surface_id) elif surf_type == 'z-plane': z0 = coeffs[0] - surface = ZPlane(surface_id, bc, z0, name) + surface = ZPlane(z0, bc, name, surface_id) elif surf_type == 'plane': A, B, C, D = coeffs - surface = Plane(surface_id, bc, A, B, C, D, name) + surface = Plane(A, B, C, D, bc, name, surface_id) elif surf_type == 'x-cylinder': y0, z0, r = coeffs - surface = XCylinder(surface_id, bc, y0, z0, r, name) + surface = XCylinder(y0, z0, r, bc, name, surface_id) elif surf_type == 'y-cylinder': x0, z0, r = coeffs - surface = YCylinder(surface_id, bc, x0, z0, r, name) + surface = YCylinder(x0, z0, r, bc, name, surface_id) elif surf_type == 'z-cylinder': x0, y0, r = coeffs - surface = ZCylinder(surface_id, bc, x0, y0, r, name) + surface = ZCylinder(x0, y0, r, bc, name, surface_id) elif surf_type == 'sphere': x0, y0, z0, r = coeffs - surface = Sphere(surface_id, bc, x0, y0, z0, r, name) + surface = Sphere(x0, y0, z0, r, bc, name, surface_id) elif surf_type in ['x-cone', 'y-cone', 'z-cone']: x0, y0, z0, r2 = coeffs if surf_type == 'x-cone': - surface = XCone(surface_id, bc, x0, y0, z0, r2, name) + surface = XCone(x0, y0, z0, r2, bc, name, surface_id) elif surf_type == 'y-cone': - surface = YCone(surface_id, bc, x0, y0, z0, r2, name) + surface = YCone(x0, y0, z0, r2, bc, name, surface_id) elif surf_type == 'z-cone': - surface = ZCone(surface_id, bc, x0, y0, z0, r2, name) + surface = ZCone(x0, y0, z0, r2, bc, name, surface_id) elif surf_type == 'quadric': a, b, c, d, e, f, g, h, j, k = coeffs - surface = Quadric(surface_id, bc, a, b, c, d, e, f, g, - h, j, k, name) + surface = Quadric(a, b, c, d, e, f, g, h, j, k, bc, name, surface_id) return surface @@ -329,13 +328,6 @@ class Plane(Surface): Parameters ---------- - surface_id : int, optional - Unique identifier for the surface. If not specified, an identifier will - automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional - Boundary condition that defines the behavior for particles hitting the - surface. Defaults to transmissive boundary condition where particles - freely pass through the surface. a : float, optional The 'A' parameter for the plane. Defaults to 1. b : float, optional @@ -344,8 +336,15 @@ class Plane(Surface): The 'C' parameter for the plane. Defaults to 0. d : float, optional The 'D' parameter for the plane. Defaults to 0. + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional + Boundary condition that defines the behavior for particles hitting the + surface. Defaults to transmissive boundary condition where particles + freely pass through the surface. name : str, optional Name of the plane. If not specified, the name will be the empty string. + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. Attributes ---------- @@ -377,8 +376,8 @@ class Plane(Surface): _type = 'plane' _coeff_keys = ('a', 'b', 'c', 'd') - def __init__(self, surface_id=None, boundary_type='transmission', - a=1., b=0., c=0., d=0., name='', **kwargs): + def __init__(self, a=1., b=0., c=0., d=0., boundary_type='transmission', + name='', surface_id=None, **kwargs): super().__init__(surface_id, boundary_type, name=name) self._periodic_surface = None self.a = a @@ -532,18 +531,18 @@ class XPlane(Plane): Parameters ---------- - surface_id : int, optional - Unique identifier for the surface. If not specified, an identifier will - automatically be assigned. + x0 : float, optional + Location of the plane. Defaults to 0. boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. Only axis-aligned periodicity is supported, i.e., x-planes can only be paired with x-planes. - x0 : float, optional - Location of the plane. Defaults to 0. name : str, optional Name of the plane. If not specified, the name will be the empty string. + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. Attributes ---------- @@ -569,9 +568,9 @@ class XPlane(Plane): _type = 'x-plane' _coeff_keys = ('x0',) - def __init__(self, surface_id=None, boundary_type='transmission', - x0=0., name=''): - super().__init__(surface_id, boundary_type, name=name) + def __init__(self, x0=0., boundary_type='transmission', + name='', surface_id=None): + super().__init__(surface_id=surface_id, boundary_type=boundary_type, name=name) self.x0 = x0 @property @@ -657,18 +656,18 @@ class YPlane(Plane): Parameters ---------- - surface_id : int, optional - Unique identifier for the surface. If not specified, an identifier will - automatically be assigned. + y0 : float, optional + Location of the plane boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. Only axis-aligned periodicity is supported, i.e., x-planes can only be paired with x-planes. - y0 : float, optional - Location of the plane name : str, optional Name of the plane. If not specified, the name will be the empty string. + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. Attributes ---------- @@ -694,10 +693,9 @@ class YPlane(Plane): _type = 'y-plane' _coeff_keys = ('y0',) - def __init__(self, surface_id=None, boundary_type='transmission', - y0=0., name=''): - # Initialize YPlane class attributes - super().__init__(surface_id, boundary_type, name=name) + def __init__(self, y0=0., boundary_type='transmission', + name='', surface_id=None): + super().__init__(surface_id=surface_id, boundary_type=boundary_type, name=name) self.y0 = y0 @property @@ -820,10 +818,9 @@ class ZPlane(Plane): _type = 'z-plane' _coeff_keys = ('z0',) - def __init__(self, surface_id=None, boundary_type='transmission', - z0=0., name=''): - # Initialize ZPlane class attributes - super().__init__(surface_id, boundary_type, name=name) + def __init__(self, z0=0., boundary_type='transmission', + name='', surface_id=None): + super().__init__(surface_id=surface_id, boundary_type=boundary_type, name=name) self.z0 = z0 @property @@ -909,18 +906,18 @@ class Cylinder(Surface): Parameters ---------- - surface_id : int, optional - Unique identifier for the surface. If not specified, an identifier will - automatically be assigned. + r : float, optional + Radius of the cylinder. Defaults to 1. boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. - r : float, optional - Radius of the cylinder. Defaults to 1. name : str, optional Name of the cylinder. If not specified, the name will be the empty string. + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. Attributes ---------- @@ -939,8 +936,8 @@ class Cylinder(Surface): Type of the surface """ - def __init__(self, surface_id=None, boundary_type='transmission', - r=1., name=''): + def __init__(self, r=1., boundary_type='transmission', + name='', surface_id=None): super().__init__(surface_id, boundary_type, name=name) self.r = r @@ -960,22 +957,22 @@ class XCylinder(Cylinder): Parameters ---------- - surface_id : int, optional - Unique identifier for the surface. If not specified, an identifier will - automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional - Boundary condition that defines the behavior for particles hitting the - surface. Defaults to transmissive boundary condition where particles - freely pass through the surface. y0 : float, optional y-coordinate of the center of the cylinder. Defaults to 0. z0 : float, optional z-coordinate of the center of the cylinder. Defaults to 0. r : float, optional Radius of the cylinder. Defaults to 0. + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional + Boundary condition that defines the behavior for particles hitting the + surface. Defaults to transmissive boundary condition where particles + freely pass through the surface. name : str, optional Name of the cylinder. If not specified, the name will be the empty string. + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. Attributes ---------- @@ -1000,12 +997,12 @@ class XCylinder(Cylinder): _type = 'x-cylinder' _coeff_keys = ('y0', 'z0', 'r') - def __init__(self, surface_id=None, boundary_type='transmission', - y0=0., z0=0., r=1., name='', *, R=None): + def __init__(self, y0=0., z0=0., r=1., boundary_type='transmission', + name='', surface_id=None, *, R=None): if R is not None: warn(_WARNING_UPPER.format(type(self).__name__, 'r', 'R'), FutureWarning) r = R - super().__init__(surface_id, boundary_type, r, name=name) + super().__init__(r, boundary_type, name, surface_id) self.y0 = y0 self.z0 = z0 @@ -1107,22 +1104,22 @@ class YCylinder(Cylinder): Parameters ---------- - surface_id : int, optional - Unique identifier for the surface. If not specified, an identifier will - automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional - Boundary condition that defines the behavior for particles hitting the - surface. Defaults to transmissive boundary condition where particles - freely pass through the surface. x0 : float, optional x-coordinate of the center of the cylinder. Defaults to 0. z0 : float, optional z-coordinate of the center of the cylinder. Defaults to 0. r : float, optional Radius of the cylinder. Defaults to 1. + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional + Boundary condition that defines the behavior for particles hitting the + surface. Defaults to transmissive boundary condition where particles + freely pass through the surface. name : str, optional Name of the cylinder. If not specified, the name will be the empty string. + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. Attributes ---------- @@ -1147,12 +1144,12 @@ class YCylinder(Cylinder): _type = 'y-cylinder' _coeff_keys = ('x0', 'z0', 'r') - def __init__(self, surface_id=None, boundary_type='transmission', - x0=0., z0=0., r=1., name='', *, R=None): + def __init__(self, x0=0., z0=0., r=1., boundary_type='transmission', + name='', surface_id=None, *, R=None): if R is not None: warn(_WARNING_UPPER.format(type(self).__name__, 'r', 'R'), FutureWarning) r = R - super().__init__(surface_id, boundary_type, r, name=name) + super().__init__(r, boundary_type, name, surface_id) self.x0 = x0 self.z0 = z0 @@ -1294,12 +1291,12 @@ class ZCylinder(Cylinder): _type = 'z-cylinder' _coeff_keys = ('x0', 'y0', 'r') - def __init__(self, surface_id=None, boundary_type='transmission', - x0=0., y0=0., r=1., name='', *, R=None): + def __init__(self, x0=0., y0=0., r=1., boundary_type='transmission', + name='', surface_id=None, *, R=None): if R is not None: warn(_WARNING_UPPER.format(type(self).__name__, 'r', 'R'), FutureWarning) r = R - super().__init__(surface_id, boundary_type, r, name=name) + super().__init__(r, boundary_type, name, surface_id) self.x0 = x0 self.y0 = y0 @@ -1400,13 +1397,6 @@ class Sphere(Surface): Parameters ---------- - surface_id : int, optional - Unique identifier for the surface. If not specified, an identifier will - automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional - Boundary condition that defines the behavior for particles hitting the - surface. Defaults to transmissive boundary condition where particles - freely pass through the surface. x0 : float, optional x-coordinate of the center of the sphere. Defaults to 0. y0 : float, optional @@ -1415,8 +1405,15 @@ class Sphere(Surface): z-coordinate of the center of the sphere. Defaults to 0. r : float, optional Radius of the sphere. Defaults to 1. + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional + Boundary condition that defines the behavior for particles hitting the + surface. Defaults to transmissive boundary condition where particles + freely pass through the surface. name : str, optional Name of the sphere. If not specified, the name will be the empty string. + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. Attributes ---------- @@ -1445,8 +1442,8 @@ class Sphere(Surface): _type = 'sphere' _coeff_keys = ('x0', 'y0', 'z0', 'r') - def __init__(self, surface_id=None, boundary_type='transmission', - x0=0., y0=0., z0=0., r=1., name='', *, R=None): + def __init__(self, x0=0., y0=0., z0=0., r=1., boundary_type='transmission', + name='', surface_id=None, *, R=None): if R is not None: warn(_WARNING_UPPER.format(type(self).__name__, 'r', 'R'), FutureWarning) r = R @@ -1574,6 +1571,14 @@ class Cone(Surface): Parameters ---------- + x0 : float, optional + x-coordinate of the apex. Defaults to 0. + y0 : float, optional + y-coordinate of the apex. Defaults to 0. + z0 : float, optional + z-coordinate of the apex. Defaults to 0. + r2 : float, optional + Parameter related to the aperature. Defaults to 1. surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. @@ -1581,14 +1586,6 @@ class Cone(Surface): Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. - x0 : float, optional - x-coordinate of the apex. Defaults to 0. - y0 : float - y-coordinate of the apex. Defaults to 0. - z0 : float - z-coordinate of the apex. Defaults to 0. - r2 : float - Parameter related to the aperature. Defaults to 1. name : str Name of the cone. If not specified, the name will be the empty string. @@ -1618,8 +1615,8 @@ class Cone(Surface): _coeff_keys = ('x0', 'y0', 'z0', 'r2') - def __init__(self, surface_id=None, boundary_type='transmission', - x0=0., y0=0., z0=0., r2=1., name='', *, R2=None): + def __init__(self, x0=0., y0=0., z0=0., r2=1., boundary_type='transmission', + name='', surface_id=None, *, R2=None): if R2 is not None: warn(_WARNING_UPPER.format(type(self).__name__, 'r2', 'R2'), FutureWarning) r2 = R2 @@ -1695,13 +1692,6 @@ class XCone(Cone): Parameters ---------- - surface_id : int, optional - Unique identifier for the surface. If not specified, an identifier will - automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional - Boundary condition that defines the behavior for particles hitting the - surface. Defaults to transmissive boundary condition where particles - freely pass through the surface. x0 : float, optional x-coordinate of the apex. Defaults to 0. y0 : float, optional @@ -1710,8 +1700,15 @@ class XCone(Cone): z-coordinate of the apex. Defaults to 0. r2 : float, optional Parameter related to the aperature. Defaults to 1. + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional + Boundary condition that defines the behavior for particles hitting the + surface. Defaults to transmissive boundary condition where particles + freely pass through the surface. name : str, optional Name of the cone. If not specified, the name will be the empty string. + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. Attributes ---------- @@ -1766,13 +1763,6 @@ class YCone(Cone): Parameters ---------- - surface_id : int, optional - Unique identifier for the surface. If not specified, an identifier will - automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional - Boundary condition that defines the behavior for particles hitting the - surface. Defaults to transmissive boundary condition where particles - freely pass through the surface. x0 : float, optional x-coordinate of the apex. Defaults to 0. y0 : float, optional @@ -1781,8 +1771,15 @@ class YCone(Cone): z-coordinate of the apex. Defaults to 0. r2 : float, optional Parameter related to the aperature. Defaults to 1. + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional + Boundary condition that defines the behavior for particles hitting the + surface. Defaults to transmissive boundary condition where particles + freely pass through the surface. name : str, optional Name of the cone. If not specified, the name will be the empty string. + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. Attributes ---------- @@ -1837,13 +1834,6 @@ class ZCone(Cone): Parameters ---------- - surface_id : int, optional - Unique identifier for the surface. If not specified, an identifier will - automatically be assigned. - boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional - Boundary condition that defines the behavior for particles hitting the - surface. Defaults to transmissive boundary condition where particles - freely pass through the surface. x0 : float, optional x-coordinate of the apex. Defaults to 0. y0 : float, optional @@ -1852,8 +1842,15 @@ class ZCone(Cone): z-coordinate of the apex. Defaults to 0. r2 : float, optional Parameter related to the aperature. Defaults to 1. + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional + Boundary condition that defines the behavior for particles hitting the + surface. Defaults to transmissive boundary condition where particles + freely pass through the surface. name : str, optional Name of the cone. If not specified, the name will be the empty string. + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. Attributes ---------- @@ -1908,17 +1905,17 @@ class Quadric(Surface): Parameters ---------- - surface_id : int, optional - Unique identifier for the surface. If not specified, an identifier will - automatically be assigned. + a, b, c, d, e, f, g, h, j, k : float, optional + coefficients for the surface. All default to 0. boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. - a, b, c, d, e, f, g, h, j, k : float, optional - coefficients for the surface. All default to 0. name : str, optional Name of the surface. If not specified, the name will be the empty string. + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. Attributes ---------- @@ -1941,9 +1938,8 @@ class Quadric(Surface): _type = 'quadric' _coeff_keys = ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k') - def __init__(self, surface_id=None, boundary_type='transmission', - a=0., b=0., c=0., d=0., e=0., f=0., g=0., - h=0., j=0., k=0., name=''): + def __init__(self, a=0., b=0., c=0., d=0., e=0., f=0., g=0., h=0., j=0., + k=0., boundary_type='transmission', name='', surface_id=None): super().__init__(surface_id, boundary_type, name=name) self.a = a self.b = b From 06ebf7dbade84ce53fae7eb01b781eeebdb66381 Mon Sep 17 00:00:00 2001 From: rockfool Date: Tue, 17 Sep 2019 12:41:48 -0400 Subject: [PATCH 065/158] fix math equation in geometry.rst --- docs/source/methods/geometry.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/methods/geometry.rst b/docs/source/methods/geometry.rst index ca45e035d3..e89c28dc4e 100644 --- a/docs/source/methods/geometry.rst +++ b/docs/source/methods/geometry.rst @@ -924,8 +924,8 @@ as follows, \mu \in [0, 1] \phi \in [0, 2\pi] -where :math:`{\mu = cos(\theta)}` is the cosine of the polar angle between reflected direction -and the normal to the surface; and :math: `{\theta}` is the azimuthal angle. +where :math:`\mu = cos(\theta)` is the cosine of the polar angle between reflected direction +and the normal to the surface; and :math:`\theta` is the azimuthal angle. Thus, the cosine of the polar angle can extracted like this, .. math:: : label: white-reflection-cosine @@ -935,7 +935,7 @@ Thus, the cosine of the polar angle can extracted like this, and the azimuthal angle is uniform, .. math:: - : label: white-reflection-unifrm + : label: white-reflection-uniform f(\phi) = frac{d\phi}{2\pi} @@ -947,7 +947,7 @@ like this, \mu = sqrt{\eta_(1)} \phi = 2\pi \eta_(2) -where :math: `{\eta}` is the uniform random number, simply computed by random number generator. +where :math:`\eta` is the uniform random number, simply computed by random number generator. Eventually, the final reflected direction vector can be computed via the rotation of normal to the surface like this, .. math:: From cfbd4f2fd698ce92ef0bc0ca8590f18c86aa6b9e Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 17 Sep 2019 12:17:49 -0500 Subject: [PATCH 066/158] Operator aborts in unpacking, not call, if no energy produced --- openmc/deplete/operator.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index 3558759b4e..5ea1f120de 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -277,16 +277,7 @@ class Operator(TransportOperator): time_openmc = time.time() # Extract results - try: - op_result = self._unpack_tallies_and_normalize(power) - except ZeroDivisionError: - if comm.rank == 0: - sys.stderr.flush() - print(" No energy reported from openmc tallies. Do you have " - "MT901 data?\n", file=sys.stderr, flush=True) - comm.barrier() - comm.Abort(1) - + op_result = self._unpack_tallies_and_normalize(power) return copy.deepcopy(op_result) @@ -671,7 +662,12 @@ class Operator(TransportOperator): # Guard against divide by zero if energy == 0: - raise ZeroDivisionError + if comm.rank == 0: + sys.stderr.flush() + print(" No energy reported from OpenMC tallies. Do your HDF5 " + "files have heating data?\n", file=sys.stderr, flush=True) + comm.barrier() + comm.Abort(1) # Scale reaction rates to obtain units of reactions/sec rates *= power / energy From 2ab3ad7b789b0e1b773eb0a1ad56a4fc430c63a7 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 17 Sep 2019 12:18:51 -0500 Subject: [PATCH 067/158] Minor tweaks to Operator, EnergyScoreHelper Per reviewer comments --- openmc/deplete/helpers.py | 2 +- openmc/deplete/operator.py | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/openmc/deplete/helpers.py b/openmc/deplete/helpers.py index 8e87c532ab..9e3e379d58 100644 --- a/openmc/deplete/helpers.py +++ b/openmc/deplete/helpers.py @@ -205,7 +205,7 @@ class EnergyScoreHelper(EnergyHelper): the number of MPI processes """ super().reset() - if not comm.rank: + if comm.rank == 0: self._energy = self._tally.results[0, 0, 1] # ------------------------------------ diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index 5ea1f120de..743615bbe9 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -84,8 +84,7 @@ class Operator(TransportOperator): Indicator for computing system energy. ``"energy-deposition"`` will compute with a single energy deposition tally, taking fission energy release data and heating into consideration. ``"fission-q"`` will - use the fission Q values from the depletion chain, not taking - heating into consideration. + use the fission Q values from the depletion chain fission_q : dict, optional Dictionary of nuclides and their fission Q values [eV]. If not given, values will be pulled from the ``chain_file``. Only applicable @@ -261,8 +260,6 @@ class Operator(TransportOperator): # Update status self.number.set_density(vec) - time_start = time.time() - # Update material compositions and tally nuclides self._update_materials() nuclides = self._get_tally_nuclides() @@ -274,8 +271,6 @@ class Operator(TransportOperator): openmc.lib.reset() openmc.lib.run() - time_openmc = time.time() - # Extract results op_result = self._unpack_tallies_and_normalize(power) From 17ded504432043e3c4fc6443403b99a552762137 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 17 Sep 2019 12:19:30 -0500 Subject: [PATCH 068/158] Improve energy deposition methodology --- docs/source/methods/energy_deposition.rst | 95 +++++++++++++---------- 1 file changed, 55 insertions(+), 40 deletions(-) diff --git a/docs/source/methods/energy_deposition.rst b/docs/source/methods/energy_deposition.rst index 5bd16802db..af4962c553 100644 --- a/docs/source/methods/energy_deposition.rst +++ b/docs/source/methods/energy_deposition.rst @@ -5,23 +5,23 @@ Heating and Energy Deposition ============================= As particles traverse a problem, some portion of their energy is deposited at -collision sites. There are a variety of mechanisms that contribute to the -energy deposition, including down-scattering from higher to lower energies, -fission events, and reactions with positive Q-values. The information describing -how much energy is deposited for a specific reaction is referred to as +collision sites. This energy is deposited when charged particles, including +electrons and recoil nuclei, undergo electromagnetic interactions with +surrounding electons and ions. The information describing how much energy +is deposited for a specific reaction is referred to as "heating numbers" and can be computed using a program like NJOY with the ``heatr`` module. -These heating numbers are the product of reaction-specific coefficients and +These heating rate is the product of reaction-specific coefficients and a reaction cross section .. math:: - H(E) = \phi(E)\sum_i\rho_i\sum_rk_{i, r} + H(E) = \phi(E)\sum_i\rho_i\sum_rk_{i, r}(E), and has units energy per time, typically eV / s. -Here, :math:`k_{i, r}` are the KERMA [Kinetic Energy Release in Materials] -coefficients for reaction :math:`r` of isotope :math:`i`. +Here, :math:`k_{i, r}` are the KERMA (Kinetic Energy Release in Materials) +[Mack97]_ coefficients for reaction :math:`r` of isotope :math:`i`. The KERMA coefficients have units energy :math:`\times` cross-section, e.g. eV-barn, and can be used much like a reaction cross section for the purpose of tallying energy deposition. @@ -33,14 +33,16 @@ requested .. math:: - k_{i, r}(E) = \left(E + Q_{i, r} - \bar{E}_{i, r, n} - \bar{E}_{i, r, \gamma}\right)\sigma_{i, r}(E) + k_{i, r}(E) = \left(E + Q_{i, r} - \bar{E}_{i, r, n} + - \bar{E}_{i, r, \gamma}\right)\sigma_{i, r}(E), -removing the energy of secondary neutrons and photons from the sum of -incident neutron energy, :math:`E`, and the reaction :math:`Q` value. +removing the energy of neutral particles (neutrons and photons) that are +transported away from the reaction site :math:`\bar{E}`, and the reaction +:math:`Q` value. ---------------------------- -The Special Case of Fission ---------------------------- +------- +Fission +------- During a fission event, there are potentially many secondary particles, and all must be considered. The total energy released in a fission event is typically @@ -54,14 +56,16 @@ broken up into the following categories: - :math:`E_{\beta}` - energy of released :math:`\beta` particles - :math:`E_{\nu}` - energy of neutrinos -These components are defined in MT=458 data in a standard ENDF/B-VII file. -All these quantities have some energy dependence, but this dependence is not shown to -make the following demonstrations cleaner. -As neutrinos scarcely interact with matter, the recoverable energy from fission is defined as +These components are defined in MF=1,MT=458 data in a standard ENDF/B-6 formatted +file. All these quantities may depend upon incident neutron energy, +but this dependence is not shown to make the following demonstrations cleaner. +As neutrinos scarcely interact with matter, the recoverable energy from +fission is defined as .. math:: - E_r\equiv E_{fr} + E_{n,p} + E_{n, d} + E_{\gamma, p} + E_{\gamma, d} + E_{\beta} + E_r\equiv E_{fr} + E_{n,p} + E_{n, d} + E_{\gamma, p} + + E_{\gamma, d} + E_{\beta} Furthermore, the energy of the secondary neutrons and photons is given as :math:`E_{n, p}` and :math:`E_{\gamma, p}`, respectively. @@ -75,34 +79,35 @@ NJOY computes the fission KERMA coefficient using this energy-balance method to .. note:: - The energy from delayed neutrons and photons and beta particles are intentionally - left out from the NJOY calculations + The energy from delayed neutrons and photons and beta particles is intentionally + left out from the NJOY calculations. --------------------- OpenMC Implementation --------------------- -For fissile isotopes, OpenMC makes modifications to the heating reaction to include -all relevant components of fission energy release. These modifications are made to -the total heating reaction, MT=301. Breaking the total heating number into -a fission and non-fission section, one can write +For fissile isotopes, OpenMC makes modifications to the heating reaction to +include all relevant components of fission energy release. These modifications +are made to the total heating reaction, MT=301. Breaking the total heating +KERMA into a fission and non-fission section, one can write .. math:: - H_i(E) = H_{i, nf}(E) + \left[E_{fr}(E) + E_{\gamma, p}\right]\sigma_{i, f}(E) + k_i(E) = k_{i, nf}(E) + \left[E_{fr}(E) + E_{\gamma, p}\right]\sigma_{i, f}(E) -OpenMC seeks to modify the total heating data to include energy from :math:`\beta` particles -and, conditionally, delayed photons. This conditional inclusion depends on the simulation -mode: neutron transport, or coupled neutron-photon transport. The heating due to fission -is removed using MT=318 data, and then re-built using the desired components of fission -energy release from MT=458 data. +OpenMC seeks to modify the total heating data to include energy from +:math:`\beta` particles and, conditionally, delayed photons. This conditional +inclusion depends on the simulation mode: neutron transport, or coupled +neutron-photon transport. The heating due to fission is removed using MT=318 +data, and then re-built using the desired components of fission energy release +from MF=1,MT=458 data. Neutron Transport ----------------- -For this case, OpenMC instructs ``heatr`` to produce heating coefficients assuming -that energy from photons, :math:`E_{\gamma, p}` and :math:`E_{\gamma, d}`, -is deposited at the fission site. +For this case, OpenMC instructs ``heatr`` to produce heating coefficients +assuming that energy from photons, :math:`E_{\gamma, p}` and +:math:`E_{\gamma, d}`, is deposited at the fission site. Let :math:`N901` represent the total heating number returned from this ``heatr`` run with :math:`N918` reflecting fission heating computed from NJOY. :math:`M901` represent the following modification @@ -119,11 +124,12 @@ if ``901`` is included in :attr:`openmc.Tally.scores`. Coupled neutron-photon transport -------------------------------- -Here, OpenMC instructs ``heatr`` to remove the assumption of local photon energy. -However, the definitions provided in the NJOY manual indicate that, regardless of -this mode, the prompt photon energy is still included in :math:`k_{i, f}`, -and therefore must be manually removed. Let :math:`N301` represent the total -heating number returned from this ``heatr`` run and :math:`M301` be +Here, OpenMC instructs ``heatr`` to assume that energy from photons is not +deposited locally. However, the definitions provided in the NJOY manual +indicate that, regardless of this mode, the prompt photon energy is still +included in :math:`k_{i, f}`, and therefore must be manually removed. +Let :math:`N301` represent the total heating number returned from this +``heatr`` run and :math:`M301` be .. math:: @@ -131,4 +137,13 @@ heating number returned from this ``heatr`` run and :math:`M301` be + \left[E_{i, fr}(E) + E_{i, \beta}(E)\right]\sigma_{i, f}(E). This modified heating data is stored as the MT=301 reaction and will be scored -if ``301`` is included in :attr:`openmc.Tally.scores`. +if ``heating`` is included in :attr:`openmc.Tally.scores`. + +---------- +References +---------- + +.. [Mack97] Abdou, M.A., Maynard, C.W., and Wright, R.Q. MACK: computer + program to calculate neutron energy release parameters (fluence-to-kerma + factors) and multigroup neutron reaction cross sections from nuclear data + in ENDF Format. Oak Ridge National Laboratory report ORNL-TM-3994. \ No newline at end of file From 2428702fb653617658b99319a232f9b76454ee91 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 17 Sep 2019 11:11:39 -0500 Subject: [PATCH 069/158] Improved documentation for depletion module * __call__ methods for Operator documented * CRAM16 and CRAM48 are closer to the top of the page * Document deplete and timed_deplete functions * Abstract integrators documented as part of abc submodule * Abstract operator helpers documented as part of abc submodule --- docs/source/_templates/mycallable.rst | 9 ++++++++ docs/source/pythonapi/deplete.rst | 30 ++++++++++++++------------- 2 files changed, 25 insertions(+), 14 deletions(-) create mode 100644 docs/source/_templates/mycallable.rst diff --git a/docs/source/_templates/mycallable.rst b/docs/source/_templates/mycallable.rst new file mode 100644 index 0000000000..85fdd34c35 --- /dev/null +++ b/docs/source/_templates/mycallable.rst @@ -0,0 +1,9 @@ +{{ fullname }} +{{ underline }} + +.. currentmodule:: {{ module }} + +.. autoclass:: {{ objname }} + :members: + :special-members: __call__ + diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 5b26eb38aa..531723b72b 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -31,7 +31,7 @@ specific to OpenMC is available using the following class: .. autosummary:: :toctree: generated :nosignatures: - :template: myclass.rst + :template: mycallable.rst Operator @@ -82,6 +82,19 @@ data, such as number densities and reaction rates for each material. Results ResultsList +The following functions are used to solve the depletion equations, with +:func:`cram.CRAM48` being the default. + +.. autosummary:: + :toctree: generated + :nosignatures: + :template: myfunction.rst + + cram.CRAM16 + cram.CRAM48 + cram.deplete + cram.timed_deplete + The following classes are used to help the :class:`openmc.deplete.Operator` compute quantities like effective fission yields, reaction rates, and total system energy. @@ -119,16 +132,5 @@ base classes: :nosignatures: :template: myintegrator.rst - Integrator - SIIntegrator - -Each of the integrator classes also relies on a number of "helper" functions -as follows: - -.. autosummary:: - :toctree: generated - :nosignatures: - :template: myfunction.rst - - cram.CRAM16 - cram.CRAM48 + abc.Integrator + abc.SIIntegrator From 72471d249fd4f0edda5ca4e26ad1fb1210a84247 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 17 Sep 2019 14:56:31 -0500 Subject: [PATCH 070/158] Expand openmc.deplete documentation: minimal example and ABCs Slight reformatting of the depletion documentation. The "Primary API" is presented at the top, including integrators and the Operator. This section is followed by a "Minimal Example" that demonstrates how one might instantiate an Operator, and use it for depletion. The comm communicator is moved into the "Internal Classes and Functions" section, as the end-user is less likely to interact with this directly. Lastly, a section on "Abstract Base Classes" is provided and expanded, documenting the purpose of specific ABCs. --- docs/source/pythonapi/deplete.rst | 82 ++++++++++++++++++++++++++----- 1 file changed, 71 insertions(+), 11 deletions(-) diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 531723b72b..55725f3bc1 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -6,7 +6,22 @@ .. module:: openmc.deplete -Several classes are provided that implement different time-integration +Primary API +----------- + +The two primary requirements to perform depletion with :mod:`openmc.deplete` +are: + + 1) A transport operator + 2) A time-integration scheme + +The former is responsible for executing a transport code, like OpenMC, +and retaining important information required for depletion. The most common examples +are reaction rates and power normalization data. The latter is responsible for +projecting reaction rates and compositions forward in calendar time across +some step size :math:`\Delta t`, and obtaining new compositions given a power +or power density. The :class:`Operator` is provided to handle communicating with +OpenMC. Several classes are provided that implement different time-integration algorithms for depletion calculations, which are described in detail in Colin Josey's thesis, `Development and analysis of high order neutron transport-depletion coupling algorithms `_. @@ -35,6 +50,38 @@ specific to OpenMC is available using the following class: Operator +The :class:`Operator` must also have some knowledge of how nuclides transmute +and decay. This is handled by the :class:`Chain` + +Minimal Example +--------------- + +A minimal example for performing depletion would be: + +.. code:: Python + + >>> import openmc + >>> import openmc.deplete + >>> geometry = openmc.Geometry.from_xml() + >>> settings = openmc.Settings.from_xml() + + # Representation of a depletion chain + >>> chain_file = "chain_casl.xml" + >>> operator = openmc.deplete.Operator( + ... geometry, settings, chain_file) + + # Set up 5 time steps of one day each + >>> dt = [24 * 60 * 60] * 5 + >>> power = 1E6 # constant power of 1 MW + + # Deplete using mid-point predictor-corrector + >>> cecm = openmc.deplete.CECMIntegrator( + ... operator, dt, power) + >>> cecm.integrate() + +Internal Classes and Functions +------------------------------ + When running in parallel using `mpi4py `_, the MPI intercommunicator used can be changed by modifying the following module variable. If it is not explicitly modified, it defaults to @@ -46,9 +93,6 @@ variable. If it is not explicitly modified, it defaults to :type: mpi4py.MPI.Comm -Internal Classes and Functions ------------------------------- - During a depletion calculation, the depletion chain, reaction rates, and number densities are managed through a series of internal classes that are not normally visible to a user. However, should you find yourself wondering about these @@ -110,19 +154,35 @@ total system energy. helpers.DirectReactionRateHelper helpers.FissionYieldCutoffHelper -The following classes are abstract classes that can be used to extend the -:mod:`openmc.deplete` capabilities: + +Abstract Base Classes +--------------------- + +A good starting point for extending capabilities in :mod:`openmc.deplete` is +to examine the following abstract base classes. Custom classes can +inherit from :class:`abc.TransportOperator` to implement alternative +schemes for collecting reaction rates and other data from a transport code +prior to depleting materials + +.. autosummary:: + :toctree: generated + :nosignatures: + :template: mycallable.rst + + abc.TransportOperator + +The following classes are abstract classes used to pass information from +OpenMC simulations back on to the :class:`abc.TransportOperator` .. autosummary:: :toctree: generated :nosignatures: :template: myclass.rst - EnergyHelper - FissionYieldHelper - ReactionRateHelper - TalliedFissionYieldHelper - TransportOperator + abc.EnergyHelper + abc.FissionYieldHelper + abc.ReactionRateHelper + abc.TalliedFissionYieldHelper Custom integrators can be developed by subclassing from the following abstract base classes: From 747e87d31ab282de132602d1e2a38589c2cf3e22 Mon Sep 17 00:00:00 2001 From: rockfool Date: Tue, 17 Sep 2019 16:34:11 -0400 Subject: [PATCH 071/158] bug fixed in geometry.rst --- docs/source/methods/geometry.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/source/methods/geometry.rst b/docs/source/methods/geometry.rst index e89c28dc4e..4cc97e7d0a 100644 --- a/docs/source/methods/geometry.rst +++ b/docs/source/methods/geometry.rst @@ -901,6 +901,7 @@ Dxy + Eyz + Fxz + Gx + Hy + Jz + K = 0`. Thus, the gradient to the surface is ------------------------------ White Boundary Conditions ------------------------------ + The white boundary condition is usually applied in deterministic codes, where the particle will hit the surface and travel back with isotropic angular distribution. The change in particle's direction is sampled from a cosine distribution instead of uniform. @@ -947,7 +948,7 @@ like this, \mu = sqrt{\eta_(1)} \phi = 2\pi \eta_(2) -where :math:`\eta` is the uniform random number, simply computed by random number generator. +where :math:`\eta` is the uniform random number that is simply computed by the random number generator. Eventually, the final reflected direction vector can be computed via the rotation of normal to the surface like this, .. math:: From b3c5d98454baeb33462c0637f8944634d756303a Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 17 Sep 2019 15:13:19 -0500 Subject: [PATCH 072/158] Cleaner openmc.deplete namespace with __all__ Provide explicit __all__ lists for files in openmc/deplete containing classes that should be brought into the "primary" API. This prevents cluttering the openmc.deplete namespace caused by the wildcard imports ``` from .nuclide import * ... ``` The abc, cram, and helpers modules are imported simply as ``` from . import abc from . import cram from . import helpers ``` --- openmc/deplete/__init__.py | 5 ++++- openmc/deplete/abc.py | 7 +++++++ openmc/deplete/chain.py | 2 ++ openmc/deplete/cram.py | 2 -- openmc/deplete/nuclide.py | 4 ++++ openmc/deplete/operator.py | 3 +++ openmc/deplete/reaction_rates.py | 3 +++ openmc/deplete/results.py | 3 +++ openmc/deplete/results_list.py | 3 +++ 9 files changed, 29 insertions(+), 3 deletions(-) diff --git a/openmc/deplete/__init__.py b/openmc/deplete/__init__.py index 8c19615ece..f0b04c3de5 100644 --- a/openmc/deplete/__init__.py +++ b/openmc/deplete/__init__.py @@ -35,7 +35,10 @@ from .nuclide import * from .chain import * from .operator import * from .reaction_rates import * -from .abc import * +from .atom_number import * from .results import * from .results_list import * from .integrators import * +from . import abc +from . import cram +from . import helpers diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index e0ed64645e..565b9e2dcf 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -24,6 +24,13 @@ from .results import Results from .chain import Chain from .results_list import ResultsList + +__all__ = [ + "OperatorResult", "TransportOperator", "ReactionRateHelper", + "EnergyHelper", "FissionYieldHelper", "TalliedFissionYieldHelper", + "Integrator", "SIIntegrator"] + + OperatorResult = namedtuple('OperatorResult', ['k', 'rates']) OperatorResult.__doc__ = """\ Result of applying transport operator diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index 510112c7c9..a2f7db8184 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -45,6 +45,8 @@ _REACTIONS = [ ] +__all__ = ["Chain"] + def replace_missing(product, decay_data): """Replace missing product with suitable decay daughter. diff --git a/openmc/deplete/cram.py b/openmc/deplete/cram.py index 67a455cb35..31049bb724 100644 --- a/openmc/deplete/cram.py +++ b/openmc/deplete/cram.py @@ -13,8 +13,6 @@ import scipy.sparse.linalg as sla from . import comm -__all__ = ["deplete", "timed_deplete", "CRAM16", "CRAM48"] - def deplete(chain, x, rates, dt, matrix_func=None): """Deplete materials using given reaction rates for a specified time diff --git a/openmc/deplete/nuclide.py b/openmc/deplete/nuclide.py index 8896180a88..ec1668dacb 100644 --- a/openmc/deplete/nuclide.py +++ b/openmc/deplete/nuclide.py @@ -17,6 +17,10 @@ from numpy import empty from openmc.checkvalue import check_type +__all__ = [ + "DecayTuple", "ReactionTuple", "Nuclide", "FissionYield", + "FissionYieldDistribution"] + DecayTuple = namedtuple('DecayTuple', 'type target branching_ratio') DecayTuple.__doc__ = """\ diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index 86dbeb7550..9800bb2805 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -30,6 +30,9 @@ from .helpers import ( FissionYieldCutoffHelper, AveragedFissionYieldHelper) +__all__ = ["Operator", "OperatorResult"] + + def _distribute(items): """Distribute items across MPI communicator diff --git a/openmc/deplete/reaction_rates.py b/openmc/deplete/reaction_rates.py index cea2f19976..85c8d8998d 100644 --- a/openmc/deplete/reaction_rates.py +++ b/openmc/deplete/reaction_rates.py @@ -7,6 +7,9 @@ from collections import OrderedDict import numpy as np +__all__ = ["ReactionRates"] + + class ReactionRates(np.ndarray): """Reaction rates resulting from a transport operator call diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index 6593d5fc5f..b44739bc5f 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -16,6 +16,9 @@ from .reaction_rates import ReactionRates _VERSION_RESULTS = (1, 0) +__all__ = ["Results"] + + class Results(object): """Output of a depletion run diff --git a/openmc/deplete/results_list.py b/openmc/deplete/results_list.py index 3b5166ed8a..26bc8785fe 100644 --- a/openmc/deplete/results_list.py +++ b/openmc/deplete/results_list.py @@ -5,6 +5,9 @@ from .results import Results, _VERSION_RESULTS from openmc.checkvalue import check_filetype_version +__all__ = ["ResultsList"] + + class ResultsList(list): """A list of openmc.deplete.Results objects From cb92f73b18806e8e572fe74f4d75f76ee4235261 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 18 Sep 2019 09:24:39 -0500 Subject: [PATCH 073/158] Add link to openmc.org/depletion-chains in example notebook --- examples/jupyter/pincell_depletion.ipynb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/jupyter/pincell_depletion.ipynb b/examples/jupyter/pincell_depletion.ipynb index 63b644a9df..94ec479f62 100644 --- a/examples/jupyter/pincell_depletion.ipynb +++ b/examples/jupyter/pincell_depletion.ipynb @@ -339,7 +339,10 @@ "* A fission event for U-235 produces fission products like Xe135 according to a distribution\n", "* For thermal problems, Am241 will produce metastable Am242 about 8% of the time during an $(n,\\gamma)$ reaction. The other 92% of capture reactions will produce ground state Am242\n", "\n", - "These data are often distributed with other nuclear data, like incident neutron cross sections with ENDF/B-VII. The `openmc.deplete.Chain` object is responsible for representing this information. Two executables, `openmc-make-depletion-chain` and `openmc-make-depletion-chain-casl` will read in nuclear data using the `OPENMC_ENDF_DATA` environment variable and produce two depletion chain files. The first is a more detailed file containing every reaction and fission product possible, while the second uses data from CASL [Specification for the VERA Depletion Benchmark Suite](https://doi.org/10.2172/1256820) report to reduce the number of isotopes.\n", + "These data are often distributed with other nuclear data, like incident neutron cross sections with ENDF/B-VII.\n", + "OpenMC uses the [`openmc.deplete.Chain`](https://docs.openmc.org/en/latest/pythonapi/generated/openmc.deplete.Chain.html#openmc.deplete.Chain) to collect represent the various decay and transmutation pathways in a single object.\n", + "While a complete `Chain` can be created using nuclear data files, users may prefer to download pre-generated XML-representations instead.\n", + "Such files can be found at https://openmc.org/depletion-chains/ and include full and compressed chains, with capture branching ratios derived using PWR- or SFR-spectra.\n", "\n", "For this problem, we will be using a much smaller depletion chain that contains very few nuclides. In a realistic problem, over 1000 isotopes may be included in the depletion chain." ] From 3e12e363eb09fc75b8e6629959e47448c85a1c3f Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 18 Sep 2019 09:39:59 -0500 Subject: [PATCH 074/158] Make some fixes/edits in white boundary condition documentation --- docs/source/methods/geometry.rst | 87 ++++++++++++++++---------------- 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/docs/source/methods/geometry.rst b/docs/source/methods/geometry.rst index 4cc97e7d0a..7416aee5b9 100644 --- a/docs/source/methods/geometry.rst +++ b/docs/source/methods/geometry.rst @@ -617,10 +617,10 @@ condition has been applied, the particle is killed and any surface current tallies are scored to as needed. If a reflective boundary condition has been applied to the surface, surface current tallies are scored to and then the particle's direction is changed according to the procedure in :ref:`reflection`. -Note that the white boundary condition can be considered as the special case of +Note that the white boundary condition can be considered as the special case of reflective boundary condition, where the same processing method will be applied to -deal with the surface current tallies scoring, except for determining the -changes of particle's direction according to the procedures in :ref:`white`. +deal with the surface current tallies scoring, except for determining the +changes of particle's direction according to the procedures in :ref:`white`. Next, we need to determine what cell is beyond the surface in the direction of travel of the particle so that we can evaluate cross sections based on its @@ -898,15 +898,16 @@ Dxy + Eyz + Fxz + Gx + Hy + Jz + K = 0`. Thus, the gradient to the surface is .. _white: ------------------------------- +------------------------- White Boundary Conditions ------------------------------- +------------------------- -The white boundary condition is usually applied in deterministic codes, where the particle -will hit the surface and travel back with isotropic angular distribution. The change in -particle's direction is sampled from a cosine distribution instead of uniform. -Figure :num:`fig-cosine-dist` shows an example of cosine-distribution reflection on the -arbitrary surface relative to the surface normal. +The `white boundary condition `_ +is usually applied in deterministic codes, where the particle will hit the +surface and travel back with isotropic angular distribution. The change in +particle's direction is sampled from a cosine distribution instead of uniform. +Figure :num:`fig-cosine-dist` shows an example of cosine-distribution reflection +on the arbitrary surface relative to the surface normal. .. _fig-cosine-dist: @@ -914,52 +915,50 @@ arbitrary surface relative to the surface normal. :align: center :figclass: align-center - Cosine-distribution reflection on the arbitrary surface. + Cosine-distribution reflection on an arbitrary surface. + +The probability density function (pdf) for the reflected direction can be +expressed as follows, -The probability density function (pdf) for the reflected direction can be expressed -as follows, .. math:: - : label: white-reflection-pdf - - f(\mu, \phi) = frac{\mu}{\pi} d\mu d\phi = 2\mu d\mu frac{d\phi}{2\pi} - \mu \in [0, 1] - \phi \in [0, 2\pi] + :label: white-reflection-pdf + + f(\mu, \phi) d\mu d\phi = \frac{\mu}{\pi} d\mu d\phi = 2\mu d\mu \frac{d\phi}{2\pi} + +where :math:`\mu = \cos \theta` is the cosine of the polar angle between +reflected direction and the normal to the surface; and :math:`\theta` is the +azimuthal angle in :math:`[0,2\pi]`. We can separate the multivariate +probability density into two separate univariate density functions, one for +the cosine of the polar angle, -where :math:`\mu = cos(\theta)` is the cosine of the polar angle between reflected direction -and the normal to the surface; and :math:`\theta` is the azimuthal angle. -Thus, the cosine of the polar angle can extracted like this, .. math:: - : label: white-reflection-cosine - - f(\mu)d\mu = 2\mu d\mu + :label: white-reflection-cosine + f(\mu) = 2\mu -and the azimuthal angle is uniform, -.. math:: - : label: white-reflection-uniform - - f(\phi) = frac{d\phi}{2\pi} +and one for the azimuthal angle, -Then, the cosine can be sampled by analytical inversion of cumulative density distribution (cdf) -like this, .. math:: - : label: white-reflection-sqrt-prn - - \mu = sqrt{\eta_(1)} - \phi = 2\pi \eta_(2) + :label: white-reflection-uniform + + f(\phi) = \frac{1}{2\pi}. + +Each of these density functions can be sampled by analytical inversion of the +cumulative distribution distribution, resulting in the following sampling +scheme: -where :math:`\eta` is the uniform random number that is simply computed by the random number generator. -Eventually, the final reflected direction vector can be computed via the rotation of normal to -the surface like this, .. math:: - : label: white-reflection-rotation + :label: white-reflection-sqrt-prn - u^' = u \mu + frac{uw sqrt{1-\mu^2} cos(\phi) - v sqrt{1-\mu^2}sin(\phi)}{sqrt{1-w^2}} - v^' = v \mu + frac{vw sqrt{1-\mu^2} cos(phi) + u sqrt{1-\mu^2} sin(phi)}{sqrt{1-w^2}} - w^' = w \mu - sqrt{1-w^2} sqrt{1-\mu^2} cos(\phi) + \mu = \sqrt{\xi_1} \\ + \phi = 2\pi\xi_2 -The white reflection boundary can apply to any kind of surface, as long as the normal to the surface -is known as mentioned above in :ref:`reflection`. +where :math:`\xi_1` and :math:`\xi_2` are uniform random numbers on +:math:`[0,1)`. With the sampled values of :math:`\mu` and :math:`\phi`, the +final reflected direction vector can be computed via rotation of the surface +normal using the equations from :ref:`transform-coordinates`. The white boundary +condition can be applied to any kind of surface, as long as the normal to the +surface is known as in :ref:`reflection`. .. _constructive solid geometry: http://en.wikipedia.org/wiki/Constructive_solid_geometry .. _surfaces: http://en.wikipedia.org/wiki/Surface From 30efbcc6a81175f5b1462fc806db437d101fbf2a Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 18 Sep 2019 13:33:58 -0500 Subject: [PATCH 075/158] Add a missing virtual destructor on Mesh --- include/openmc/mesh.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/openmc/mesh.h b/include/openmc/mesh.h index 6d59826aaf..34782b5e30 100644 --- a/include/openmc/mesh.h +++ b/include/openmc/mesh.h @@ -33,9 +33,10 @@ extern std::unordered_map mesh_map; class Mesh { public: - // Constructors + // Constructors and destructor Mesh() = default; Mesh(pugi::xml_node node); + virtual ~Mesh() = default; // Methods From 89de2ce03517d1a01079f86f9e3342c6d191a095 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Wed, 18 Sep 2019 18:34:02 -0400 Subject: [PATCH 076/158] Allow tests to run when OPENMC_ENDF_DATA is unset --- tests/unit_tests/test_data_decay.py | 9 ++--- tests/unit_tests/test_data_neutron.py | 52 +++++++++++++++++--------- tests/unit_tests/test_data_photon.py | 8 ++-- tests/unit_tests/test_data_thermal.py | 28 +++++++------- tests/unit_tests/test_deplete_chain.py | 9 ++--- 5 files changed, 60 insertions(+), 46 deletions(-) diff --git a/tests/unit_tests/test_data_decay.py b/tests/unit_tests/test_data_decay.py index b925506dfb..0cc7939904 100644 --- a/tests/unit_tests/test_data_decay.py +++ b/tests/unit_tests/test_data_decay.py @@ -10,9 +10,6 @@ from uncertainties import ufloat import openmc.data -_ENDF_DATA = os.environ['OPENMC_ENDF_DATA'] - - def ufloat_close(a, b): assert a.nominal_value == pytest.approx(b.nominal_value) assert a.std_dev == pytest.approx(b.std_dev) @@ -21,14 +18,16 @@ def ufloat_close(a, b): @pytest.fixture(scope='module') def nb90(): """Nb90 decay data.""" - filename = os.path.join(_ENDF_DATA, 'decay', 'dec-041_Nb_090.endf') + endf_data = os.environ['OPENMC_ENDF_DATA'] + filename = os.path.join(endf_data, 'decay', 'dec-041_Nb_090.endf') return openmc.data.Decay.from_endf(filename) @pytest.fixture(scope='module') def u235_yields(): """U235 fission product yield data.""" - filename = os.path.join(_ENDF_DATA, 'nfy', 'nfy-092_U_235.endf') + endf_data = os.environ['OPENMC_ENDF_DATA'] + filename = os.path.join(endf_data, 'nfy', 'nfy-092_U_235.endf') return openmc.data.FissionProductYields.from_endf(filename) diff --git a/tests/unit_tests/test_data_neutron.py b/tests/unit_tests/test_data_neutron.py index d8639cc5a1..ac6164e5b5 100644 --- a/tests/unit_tests/test_data_neutron.py +++ b/tests/unit_tests/test_data_neutron.py @@ -9,7 +9,6 @@ import openmc.data from . import needs_njoy _TEMPERATURES = [300., 600., 900.] -_ENDF_DATA = os.environ['OPENMC_ENDF_DATA'] @pytest.fixture(scope='module') @@ -23,14 +22,16 @@ def pu239(): @pytest.fixture(scope='module') def xe135(): """Xe135 ENDF data (contains SLBW resonance range)""" - filename = os.path.join(_ENDF_DATA, 'neutrons', 'n-054_Xe_135.endf') + endf_data = os.environ['OPENMC_ENDF_DATA'] + filename = os.path.join(endf_data, 'neutrons', 'n-054_Xe_135.endf') return openmc.data.IncidentNeutron.from_endf(filename) @pytest.fixture(scope='module') def sm150(): """Sm150 ENDF data (contains MLBW resonance range)""" - filename = os.path.join(_ENDF_DATA, 'neutrons', 'n-062_Sm_150.endf') + endf_data = os.environ['OPENMC_ENDF_DATA'] + filename = os.path.join(endf_data, 'neutrons', 'n-062_Sm_150.endf') return openmc.data.IncidentNeutron.from_endf(filename) @@ -38,69 +39,79 @@ def sm150(): def gd154(): """Gd154 ENDF data (contains Reich Moore resonance range and reosnance covariance with LCOMP=1).""" - filename = os.path.join(_ENDF_DATA, 'neutrons', 'n-064_Gd_154.endf') + endf_data = os.environ['OPENMC_ENDF_DATA'] + filename = os.path.join(endf_data, 'neutrons', 'n-064_Gd_154.endf') return openmc.data.IncidentNeutron.from_endf(filename, covariance=True) @pytest.fixture(scope='module') def cl35(): """Cl35 ENDF data (contains RML resonance range)""" - filename = os.path.join(_ENDF_DATA, 'neutrons', 'n-017_Cl_035.endf') + endf_data = os.environ['OPENMC_ENDF_DATA'] + filename = os.path.join(endf_data, 'neutrons', 'n-017_Cl_035.endf') return openmc.data.IncidentNeutron.from_endf(filename) @pytest.fixture(scope='module') def am241(): """Am241 ENDF data (contains Madland-Nix fission energy distribution).""" - filename = os.path.join(_ENDF_DATA, 'neutrons', 'n-095_Am_241.endf') + endf_data = os.environ['OPENMC_ENDF_DATA'] + filename = os.path.join(endf_data, 'neutrons', 'n-095_Am_241.endf') return openmc.data.IncidentNeutron.from_endf(filename) @pytest.fixture(scope='module') def u233(): """U233 ENDF data (contains Watt fission energy distribution).""" - filename = os.path.join(_ENDF_DATA, 'neutrons', 'n-092_U_233.endf') + endf_data = os.environ['OPENMC_ENDF_DATA'] + filename = os.path.join(endf_data, 'neutrons', 'n-092_U_233.endf') return openmc.data.IncidentNeutron.from_endf(filename) @pytest.fixture(scope='module') def u236(): """U236 ENDF data (contains Watt fission energy distribution).""" - filename = os.path.join(_ENDF_DATA, 'neutrons', 'n-092_U_236.endf') + endf_data = os.environ['OPENMC_ENDF_DATA'] + filename = os.path.join(endf_data, 'neutrons', 'n-092_U_236.endf') return openmc.data.IncidentNeutron.from_endf(filename) @pytest.fixture(scope='module') def na22(): """Na22 ENDF data (contains evaporation spectrum).""" - filename = os.path.join(_ENDF_DATA, 'neutrons', 'n-011_Na_022.endf') + endf_data = os.environ['OPENMC_ENDF_DATA'] + filename = os.path.join(endf_data, 'neutrons', 'n-011_Na_022.endf') return openmc.data.IncidentNeutron.from_endf(filename) @pytest.fixture(scope='module') def na23(): """Na23 ENDF data (contains MLBW resonance covariance with LCOMP=0).""" - filename = os.path.join(_ENDF_DATA, 'neutrons', 'n-011_Na_023.endf') + endf_data = os.environ['OPENMC_ENDF_DATA'] + filename = os.path.join(endf_data, 'neutrons', 'n-011_Na_023.endf') return openmc.data.IncidentNeutron.from_endf(filename, covariance=True) @pytest.fixture(scope='module') def be9(): """Be9 ENDF data (contains laboratory angle-energy distribution).""" - filename = os.path.join(_ENDF_DATA, 'neutrons', 'n-004_Be_009.endf') + endf_data = os.environ['OPENMC_ENDF_DATA'] + filename = os.path.join(endf_data, 'neutrons', 'n-004_Be_009.endf') return openmc.data.IncidentNeutron.from_endf(filename) @pytest.fixture(scope='module') def h2(): - endf_file = os.path.join(_ENDF_DATA, 'neutrons', 'n-001_H_002.endf') + endf_data = os.environ['OPENMC_ENDF_DATA'] + endf_file = os.path.join(endf_data, 'neutrons', 'n-001_H_002.endf') return openmc.data.IncidentNeutron.from_njoy( endf_file, temperatures=_TEMPERATURES) @pytest.fixture(scope='module') def am244(): - endf_file = os.path.join(_ENDF_DATA, 'neutrons', 'n-095_Am_244.endf') + endf_data = os.environ['OPENMC_ENDF_DATA'] + endf_file = os.path.join(endf_data, 'neutrons', 'n-095_Am_244.endf') return openmc.data.IncidentNeutron.from_njoy(endf_file) @@ -108,21 +119,24 @@ def am244(): def ti50(): """Ti50 ENDF data (contains Multi-level Breit-Wigner resonance range and resonance covariance with LCOMP=1).""" - filename = os.path.join(_ENDF_DATA, 'neutrons', 'n-022_Ti_050.endf') + endf_data = os.environ['OPENMC_ENDF_DATA'] + filename = os.path.join(endf_data, 'neutrons', 'n-022_Ti_050.endf') return openmc.data.IncidentNeutron.from_endf(filename, covariance=True) @pytest.fixture(scope='module') def cf252(): """Cf252 ENDF data (contains RM resonance covariance with LCOMP=0).""" - filename = os.path.join(_ENDF_DATA, 'neutrons', 'n-098_Cf_252.endf') + endf_data = os.environ['OPENMC_ENDF_DATA'] + filename = os.path.join(endf_data, 'neutrons', 'n-098_Cf_252.endf') return openmc.data.IncidentNeutron.from_endf(filename, covariance=True) @pytest.fixture(scope='module') def th232(): """Th232 ENDF data (contains RM resonance covariance with LCOMP=2).""" - filename = os.path.join(_ENDF_DATA, 'neutrons', 'n-090_Th_232.endf') + endf_data = os.environ['OPENMC_ENDF_DATA'] + filename = os.path.join(endf_data, 'neutrons', 'n-090_Th_232.endf') return openmc.data.IncidentNeutron.from_endf(filename, covariance=True) @@ -458,7 +472,8 @@ def test_laboratory(be9): @needs_njoy def test_correlated(tmpdir): - endf_file = os.path.join(_ENDF_DATA, 'neutrons', 'n-014_Si_030.endf') + endf_data = os.environ['OPENMC_ENDF_DATA'] + endf_file = os.path.join(endf_data, 'neutrons', 'n-014_Si_030.endf') si30 = openmc.data.IncidentNeutron.from_njoy(endf_file, heatr=False) # Convert to HDF5 and read back @@ -484,7 +499,8 @@ def test_nbody(tmpdir, h2): @needs_njoy def test_ace_convert(run_in_tmpdir): - filename = os.path.join(_ENDF_DATA, 'neutrons', 'n-001_H_001.endf') + endf_data = os.environ['OPENMC_ENDF_DATA'] + filename = os.path.join(endf_data, 'neutrons', 'n-001_H_001.endf') ace_ascii = 'ace_ascii' ace_binary = 'ace_binary' openmc.data.njoy.make_ace(filename, acer=ace_ascii) diff --git a/tests/unit_tests/test_data_photon.py b/tests/unit_tests/test_data_photon.py index 9bbbcc8ecb..c767d19e6c 100644 --- a/tests/unit_tests/test_data_photon.py +++ b/tests/unit_tests/test_data_photon.py @@ -9,19 +9,17 @@ import pytest import openmc.data -_ENDF_DATA = os.environ['OPENMC_ENDF_DATA'] - - @pytest.fixture(scope='module') def elements_endf(): """Dictionary of element ENDF data indexed by atomic symbol.""" + endf_data = os.environ['OPENMC_ENDF_DATA'] elements = {'H': 1, 'O': 8, 'Al': 13, 'Cu': 29, 'Ag': 47, 'U': 92, 'Pu': 94} data = {} for symbol, Z in elements.items(): p_file = 'photoat-{:03}_{}_000.endf'.format(Z, symbol) - p_path = os.path.join(_ENDF_DATA, 'photoat', p_file) + p_path = os.path.join(endf_data, 'photoat', p_file) a_file = 'atom-{:03}_{}_000.endf'.format(Z, symbol) - a_path = os.path.join(_ENDF_DATA, 'atomic_relax', a_file) + a_path = os.path.join(endf_data, 'atomic_relax', a_file) data[symbol] = openmc.data.IncidentPhoton.from_endf(p_path, a_path) return data diff --git a/tests/unit_tests/test_data_thermal.py b/tests/unit_tests/test_data_thermal.py index 2d8d7de2c4..dc4d24628b 100644 --- a/tests/unit_tests/test_data_thermal.py +++ b/tests/unit_tests/test_data_thermal.py @@ -10,9 +10,6 @@ import openmc.data from . import needs_njoy -_ENDF_DATA = os.environ['OPENMC_ENDF_DATA'] - - @pytest.fixture(scope='module') def h2o(): """H in H2O thermal scattering data.""" @@ -32,8 +29,9 @@ def graphite(): @pytest.fixture(scope='module') def h2o_njoy(): """H in H2O generated using NJOY.""" - path_h1 = os.path.join(_ENDF_DATA, 'neutrons', 'n-001_H_001.endf') - path_h2o = os.path.join(_ENDF_DATA, 'thermal_scatt', 'tsl-HinH2O.endf') + endf_data = os.environ['OPENMC_ENDF_DATA'] + path_h1 = os.path.join(endf_data, 'neutrons', 'n-001_H_001.endf') + path_h2o = os.path.join(endf_data, 'thermal_scatt', 'tsl-HinH2O.endf') return openmc.data.ThermalScattering.from_njoy( path_h1, path_h2o, temperatures=[293.6, 500.0]) @@ -41,15 +39,17 @@ def h2o_njoy(): @pytest.fixture(scope='module') def hzrh(): """H in ZrH thermal scattering data.""" - filename = os.path.join(_ENDF_DATA, 'thermal_scatt', 'tsl-HinZrH.endf') + endf_data = os.environ['OPENMC_ENDF_DATA'] + filename = os.path.join(endf_data, 'thermal_scatt', 'tsl-HinZrH.endf') return openmc.data.ThermalScattering.from_endf(filename) @pytest.fixture(scope='module') def hzrh_njoy(): """H in ZrH generated using NJOY.""" - path_h1 = os.path.join(_ENDF_DATA, 'neutrons', 'n-001_H_001.endf') - path_hzrh = os.path.join(_ENDF_DATA, 'thermal_scatt', 'tsl-HinZrH.endf') + endf_data = os.environ['OPENMC_ENDF_DATA'] + path_h1 = os.path.join(endf_data, 'neutrons', 'n-001_H_001.endf') + path_hzrh = os.path.join(endf_data, 'thermal_scatt', 'tsl-HinZrH.endf') with_endf_data = openmc.data.ThermalScattering.from_njoy( path_h1, path_hzrh, temperatures=[296.0], iwt=0 ) @@ -62,7 +62,8 @@ def hzrh_njoy(): @pytest.fixture(scope='module') def sio2(): """SiO2 thermal scattering data.""" - filename = os.path.join(_ENDF_DATA, 'thermal_scatt', 'tsl-SiO2.endf') + endf_data = os.environ['OPENMC_ENDF_DATA'] + filename = os.path.join(endf_data, 'thermal_scatt', 'tsl-SiO2.endf') return openmc.data.ThermalScattering.from_endf(filename) @@ -100,11 +101,11 @@ def test_graphite_xs(graphite): elastic = graphite.elastic.xs['296K'] assert elastic([1e-3, 1.0]) == pytest.approx([0.0, 0.62586153]) - @needs_njoy def test_graphite_njoy(): - path_c0 = os.path.join(_ENDF_DATA, 'neutrons', 'n-006_C_000.endf') - path_gr = os.path.join(_ENDF_DATA, 'thermal_scatt', 'tsl-graphite.endf') + endf_data = os.environ['OPENMC_ENDF_DATA'] + path_c0 = os.path.join(endf_data, 'neutrons', 'n-006_C_000.endf') + path_gr = os.path.join(endf_data, 'thermal_scatt', 'tsl-graphite.endf') graphite = openmc.data.ThermalScattering.from_njoy( path_c0, path_gr, temperatures=[296.0]) assert graphite.nuclides == ['C0', 'C12', 'C13'] @@ -141,7 +142,8 @@ def test_continuous_dist(h2o_njoy): def test_h2o_endf(): - filename = os.path.join(_ENDF_DATA, 'thermal_scatt', 'tsl-HinH2O.endf') + endf_data = os.environ['OPENMC_ENDF_DATA'] + filename = os.path.join(endf_data, 'thermal_scatt', 'tsl-HinH2O.endf') h2o = openmc.data.ThermalScattering.from_endf(filename) assert not h2o.elastic assert h2o.atomic_weight_ratio == pytest.approx(0.99917) diff --git a/tests/unit_tests/test_deplete_chain.py b/tests/unit_tests/test_deplete_chain.py index d5a3857a52..08c60d3ed1 100644 --- a/tests/unit_tests/test_deplete_chain.py +++ b/tests/unit_tests/test_deplete_chain.py @@ -12,8 +12,6 @@ import pytest from tests import cdtemp -_ENDF_DATA = Path(os.environ['OPENMC_ENDF_DATA']) - _TEST_CHAIN = """\ @@ -67,9 +65,10 @@ def test_len(): def test_from_endf(): """Test depletion chain building from ENDF files""" - decay_data = (_ENDF_DATA / 'decay').glob('*.endf') - fpy_data = (_ENDF_DATA / 'nfy').glob('*.endf') - neutron_data = (_ENDF_DATA / 'neutrons').glob('*.endf') + endf_data = Path(os.environ['OPENMC_ENDF_DATA']) + decay_data = (endf_data / 'decay').glob('*.endf') + fpy_data = (endf_data / 'nfy').glob('*.endf') + neutron_data = (endf_data / 'neutrons').glob('*.endf') chain = Chain.from_endf(decay_data, fpy_data, neutron_data) assert len(chain) == len(chain.nuclides) == len(chain.nuclide_dict) == 3820 From 7a2b52dd8423091fe33ac4dd46deae8a7e320d59 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 19 Sep 2019 08:05:30 -0500 Subject: [PATCH 077/158] Apply suggestions from code review Co-Authored-By: Paul Romano --- docs/source/pythonapi/deplete.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 55725f3bc1..5a02e0794d 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -51,14 +51,14 @@ specific to OpenMC is available using the following class: Operator The :class:`Operator` must also have some knowledge of how nuclides transmute -and decay. This is handled by the :class:`Chain` +and decay. This is handled by the :class:`Chain`. Minimal Example --------------- A minimal example for performing depletion would be: -.. code:: Python +.. code:: >>> import openmc >>> import openmc.deplete @@ -72,7 +72,7 @@ A minimal example for performing depletion would be: # Set up 5 time steps of one day each >>> dt = [24 * 60 * 60] * 5 - >>> power = 1E6 # constant power of 1 MW + >>> power = 1e6 # constant power of 1 MW # Deplete using mid-point predictor-corrector >>> cecm = openmc.deplete.CECMIntegrator( From e17687d457945136c6c632d409b54f8c9556a2bd Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 19 Sep 2019 09:18:26 -0500 Subject: [PATCH 078/158] Add openmc::Tally.writeable_ attribute Boolean flag that indicates if a tally should be written to output files. This has no effect on tally scoring, but instead will be used to skip writing Tally data for those not marked as writeable. Related to #1327 as the depletion interface will use this flag to reduce time spent writing reaction rate tallies and also reducing the size of the final statepoint files --- include/openmc/tallies/tally.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/openmc/tallies/tally.h b/include/openmc/tallies/tally.h index 2a166738d8..0b0baeb8c4 100644 --- a/include/openmc/tallies/tally.h +++ b/include/openmc/tallies/tally.h @@ -98,6 +98,9 @@ public: //! (e.g. specific cell, specific energy group, etc.) xt::xtensor results_; + //! True if this tally should be written to statepoint files + bool writeable_ {true}; + //---------------------------------------------------------------------------- // Miscellaneous public members. From 0fc57b207b712cd7e9a01a5a188fe3f30571c11a Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 19 Sep 2019 10:06:58 -0500 Subject: [PATCH 079/158] Teach output write_tallies about writeable tallies The tally header will still be written, but no summary, scores, or filters will be written. Instead, the output will indicate the tally is internal. --- src/output.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/output.cpp b/src/output.cpp index 40cd1dbfe9..439fc1558f 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -638,6 +638,16 @@ write_tallies() for (auto i_tally = 0; i_tally < model::tallies.size(); ++i_tally) { const auto& tally {*model::tallies[i_tally]}; + // Write header block. + std::string tally_header("TALLY " + std::to_string(tally.id_)); + if (!tally.name_.empty()) tally_header += ": " + tally.name_; + tallies_out << header(tally_header) << "\n\n"; + + if (!tally.writeable_) { + tallies_out << " Internal\n\n"; + continue; + } + // Calculate t-value for confidence intervals double t_value = 1; if (settings::confidence_intervals) { @@ -645,11 +655,6 @@ write_tallies() t_value = t_percentile(1 - alpha*0.5, tally.n_realizations_ - 1); } - // Write header block. - std::string tally_header("TALLY " + std::to_string(tally.id_)); - if (!tally.name_.empty()) tally_header += ": " + tally.name_; - tallies_out << header(tally_header) << "\n\n"; - // Write derivative information. if (tally.deriv_ != C_NONE) { const auto& deriv {model::tally_derivs[tally.deriv_]}; From 12201f7361ba8f455cdd080d9561b27b71b14042 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 19 Sep 2019 16:37:26 -0500 Subject: [PATCH 080/158] Add discussion in user's guide on depletion caveats Potential over-depletion using the fission-q energy deposition mode and some remedies. Repeated burnable materials are depleted as a single material unless diff_burnable_mats=True is given to the Operator --- docs/source/usersguide/depletion.rst | 84 ++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/docs/source/usersguide/depletion.rst b/docs/source/usersguide/depletion.rst index cf8f4c2093..25ada4f5d4 100644 --- a/docs/source/usersguide/depletion.rst +++ b/docs/source/usersguide/depletion.rst @@ -51,3 +51,87 @@ time:: Note that the coupling between the transport solver and the transmutation solver happens in-memory rather than by reading/writing files on disk. + +Caveats +======= + +Energy Deposition +----------------- + +The default energy deposition mode, ``"fission-q"``, instructs the +:class:`openmc.deplete.Operator` to normalize reaction rates using the product +of fission reaction rates and fission Q values taken from the depletion chain. +This approach does not consider indirect contributions to energy deposition, +including neutron heating or kinetic energy of fission fragments. In doing this, +the energy deposited during a transport calculation will be lower than expected. +This causes the reaction rates to be over-adjusted to hit the user-specific power, +or power density, leading to an over-depletion of burnable materials. + +There are some remedies. First, the fission Q values can be directly set in a +variety of ways. This requires knowing what the total fission energy release should +be, including indirect components. Some examples are provided below:: + + # use a dictionary of fission_q values + fission_q = {"U235": 202} # energy in MeV + + # create a modified chain and write it to a new file + chain = openmc.deplete.Chain.from_xml("chain.xml", fission_q) + chain.export_to_xml("chain_mod_q.xml") + op = openmc.deplete.Operator(geometry, setting, "chain_mod_q.xml") + + # pass the modified fission Q directly to the operator + op = openmc.deplete.Operator(geometry, setting, "chain.xml", + fission_q=fission_q) + + +A more complete way to model the energy deposition is to use the modified heating +reactions described in :ref:`methods_heating`. These values can be used to normalize +reaction rates instead of using the fission reaction rates with:: + + op = openmc.deplete.Operator(geometry, settings, "chain.xml", + energy_mode="energy-deposition") + +These modified heating libraries can be generated by running the latest version +of :meth:`openmc.data.IncidentNeutron.from_njoy`, and will eventually be bundled into +the distributed libraries. + +Local Spectra and Repeated Materials +------------------------------------ + +It is not uncommon to explicitly create a single burnable material across many locations. +From a pure transport perspective, there is nothing wrong with creating a single +3.5 wt.% enriched fuel ``fuel_3``, and placing that fuel in every fuel pin in an assembly +or even full core problem. This certainly expedites the model making process, but can pose +issues with depletion. +Under this setup, :mod:`openmc.deplete` will deplete a single ``fuel_3`` material using +a single set of reaction rates, and produce a single new composition for the next time +step. This can be problematic if the same ``fuel_3`` is used in very different regions +of the problem. + +As an example, consider a full-scale power reactor core with vacuum boundary +conditions, and with fuel pins solely composed of the same ``fuel_3`` material. +The fuel pins towards the center of the problem will surely experience a more intense +neutron flux and greater reaction rates than those towards the edge of the domain. +This indicates that the fuel in the center should be at a more depleted state than +periphery pins, at least for the fist depletion step. +However, without any other instructions, OpenMC will deplete ``fuel_3`` as a single +material, and all of the fuel pins will have an identical composition at the next +transport step. + +This can be countered by instructing the operator to treat repeated instances +of the same material as a unique material definition with:: + + op = openmc.deplete.Operator(geometry, settings, chain_file, + diff_burnable_mats=True) + +For our example problem, this would deplete fuel on the outer region of the problem +with different reaction rates than those in the center. Materials will be depleted +corresponding to their local neutron spectra, and have unique compositions at each +transport step. + + +.. note:: + + This will increase the total memory usage and run time due to increased + tallies and material definitions. + From 07d3207d88ca5c0c8752ae81f0fb8794df298188 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 19 Sep 2019 08:50:42 -0500 Subject: [PATCH 081/158] Provide class-based CRAM solvers CRAM16 and CRAM48 are now aliases for __call__ methods on two new classes: openmc.deplete.cram.Cram16Solver and Cram48Solver. These are two concrete subclasses of openmc.deplete.abc.IPFCramSolver and openmc.deplete.abc.DepSystemSolver abstract classes. The primary benefit of the class based approach is that the 16th and 48th order have nearly identical implementations. The only differences are the alpha, theta, and alpha_0 coefficients used. This allows both the Cram16Solver and Cram48Solver to have unique sets of coefficient vectors, while relying on the IPFSolver base class implementation. The implementation of IPF CRAM has been cleaned up. The NxN identity matrix is not re-created each iteration. Given the alpha and theta attributes on the IPFCramSolver instances, one can iterate through the orders using a zip command. By forcing concrete classes to declared alpha and theta vectors, the need to re-declare the vectors at each entrance into CRAM48 is also removed. Using these classes, a 10% speedup is observed depleting up to 500 materials on a single MPI process. The function-based approach took 62s, while the new classes required 52s. --- openmc/deplete/abc.py | 117 +++++++++++++++++- openmc/deplete/cram.py | 271 +++++++++++++++++++++++------------------ 2 files changed, 267 insertions(+), 121 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 565b9e2dcf..b42aa3e745 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -14,7 +14,9 @@ from copy import deepcopy from warnings import warn from numbers import Real, Integral -from numpy import nonzero, empty, asarray +from numpy import nonzero, empty, asarray, float64, real +import scipy.sparse as sp +import scipy.sparse.linalg as sla from uncertainties import ufloat from openmc.data import DataLibrary, JOULE_PER_EV @@ -855,3 +857,116 @@ class SIIntegrator(Integrator): Results.save(self.operator, [conc], [res_list[-1]], [t, t], p, self._i_res + len(self), proc_time) self.operator.write_bos_data(self._i_res + len(self)) + + +class DepSystemSolver(ABC): + r"""Abstract class for solving depletion equations + + Responsible for solving + + .. math:: + + \frac{\partial \vec{N}}{\partial t} = \bar{A}\vec{N}(t), + + for :math:`0< t\leq t +\Delta t`, given :math:`\vec{N}(0) = \vec{N}_0` + + """ + + @abstractmethod + def __call__(self, A, n0, dt): + """Solve the linear system of equations for depletion + + Parameters + ---------- + A : scipy.sparse.csr_matrix + Sparse transmutation matrix ``A[j, i]`` desribing rates at + which isotope ``i`` transmutes to isotope ``j`` + n0 : numpy.ndarray + Initial compositions, typically given in number of atoms in some + material or an atom density + dt : float + Time [s] of the specific interval to be solved + + Returns + ------- + numpy.ndarray + Final compositions after ``dt``. Should be of identical shape + to ``n0``. + + """ + + +class IPFCramSolver(DepSystemSolver): + r"""Abstract class that implements the IPF form of CRAM + + Provides a :meth:`__call__` that utilizes an incomplete + partial factorization (IPF) for the Chebyshev Rational Approximation + Method (CRAM) [Pusa16]_ + + Concrete subclasses must provide two complex vectors :attr:`alpha` + and :attr:`theta` that make up the coefficients of the decompostion. + Vectors are expected to be of equal length ``N``. + Subclases are also expected to provide a coefficient :attr:`alpha0` + used in the final scaling step. + + Attributes + ---------- + alpha : numpy.ndarray + Complex residues of poles :attr:`theta` in the incomplete partial + factorization. Denoted as :math:`\tilde{\alpha}` + theta : numpy.ndarray + Complex poles :math:`\theta` of the rational approximation + alpha0 : float + Limit of the approximation at infinity + + References + ---------- + + .. [Pusa16] M. Pusa, "Higher-Order Chebyshev Rational Approximation + Method and Application to Burnup Equations," Nuclear Science And + Engineering, 182:3,297-318 + `DOI: 10.13182/NSE15-26 `_ + + """ + + @property + @abstractmethod + def alpha(self): + pass + + @property + @abstractmethod + def theta(self): + pass + + @property + @abstractmethod + def alpha0(self): + pass + + def __call__(self, A, n0, dt): + """Solve depletion equations using IPF CRAM + + Parameters + ---------- + A : scipy.sparse.csr_matrix + Sparse transmutation matrix ``A[j, i]`` desribing rates at + which isotope ``i`` transmutes to isotope ``j`` + n0 : numpy.ndarray + Initial compositions, typically given in number of atoms in some + material or an atom density + dt : float + Time [s] of the specific interval to be solved + + Returns + ------- + numpy.ndarray + Final compositions after ``dt`` + + """ + A = sp.csr_matrix(A * dt, dtype=float64) + y = asarray(n0, dtype=float64) + ident = sp.eye(A.shape[0]) + for alpha, theta in zip(self.alpha, self.theta): + y += 2*real(alpha*sla.spsolve(A - theta*ident, y)) + return y * self.alpha0 diff --git a/openmc/deplete/cram.py b/openmc/deplete/cram.py index 31049bb724..4fdba87bfb 100644 --- a/openmc/deplete/cram.py +++ b/openmc/deplete/cram.py @@ -8,10 +8,12 @@ from multiprocessing import Pool import time import numpy as np -import scipy.sparse as sp -import scipy.sparse.linalg as sla -from . import comm +from .abc import IPFCramSolver + +__all__ = [ + "deplete", "timed_deplete", "CRAM16", "CRAM48", + "Cram16Solver", "Cram48Solver"] def deplete(chain, x, rates, dt, matrix_func=None): @@ -81,147 +83,176 @@ def timed_deplete(*args, **kwargs): return time.time() - start, results -def CRAM16(A, n0, dt): - """Chebyshev Rational Approximation Method, order 16 +class Cram16Solver(IPFCramSolver): + r"""Solver implementing the 16th order IPF CRAM - Algorithm is the 16th order Chebyshev Rational Approximation Method, - implemented in the more stable `incomplete partial fraction (IPF) - `_ form. + Coefficients to :attr:`alpha`, :attr:`theta`, and :attr:`alpha0` + are from Table A.IV in [Pusa16]_. - Parameters + Attributes ---------- - A : scipy.linalg.csr_matrix - Matrix to take exponent of. - n0 : numpy.array - Vector to operate a matrix exponent on. - dt : float - Time to integrate to. - - Returns - ------- - numpy.array - Results of the matrix exponent. + alpha : numpy.ndarray + Complex residues of poles :attr:`theta` in the incomplete partial + factorization. Denoted as :math:`\tilde{\alpha}` + in Algorithm 1 of [Pusa16]_ + theta : numpy.ndarray + Complex poles :math:`\theta` of the rational approximation + alpha0 : float + Limit of the approximation at infinity """ + alpha = np.array([ + +5.464930576870210e+3 - 3.797983575308356e+4j, + +9.045112476907548e+1 - 1.115537522430261e+3j, + +2.344818070467641e+2 - 4.228020157070496e+2j, + +9.453304067358312e+1 - 2.951294291446048e+2j, + +7.283792954673409e+2 - 1.205646080220011e+5j, + +3.648229059594851e+1 - 1.155509621409682e+2j, + +2.547321630156819e+1 - 2.639500283021502e+1j, + +2.394538338734709e+1 - 5.650522971778156e+0j], + dtype=np.complex128) - alpha = np.array([+2.124853710495224e-16, - +5.464930576870210e+3 - 3.797983575308356e+4j, - +9.045112476907548e+1 - 1.115537522430261e+3j, - +2.344818070467641e+2 - 4.228020157070496e+2j, - +9.453304067358312e+1 - 2.951294291446048e+2j, - +7.283792954673409e+2 - 1.205646080220011e+5j, - +3.648229059594851e+1 - 1.155509621409682e+2j, - +2.547321630156819e+1 - 2.639500283021502e+1j, - +2.394538338734709e+1 - 5.650522971778156e+0j], - dtype=np.complex128) - theta = np.array([+0.0, - +3.509103608414918 + 8.436198985884374j, - +5.948152268951177 + 3.587457362018322j, - -5.264971343442647 + 16.22022147316793j, - +1.419375897185666 + 10.92536348449672j, - +6.416177699099435 + 1.194122393370139j, - +4.993174737717997 + 5.996881713603942j, - -1.413928462488886 + 13.49772569889275j, - -10.84391707869699 + 19.27744616718165j], - dtype=np.complex128) - - n = A.shape[0] + theta = np.array([ + +3.509103608414918 + 8.436198985884374j, + +5.948152268951177 + 3.587457362018322j, + -5.264971343442647 + 16.22022147316793j, + +1.419375897185666 + 10.92536348449672j, + +6.416177699099435 + 1.194122393370139j, + +4.993174737717997 + 5.996881713603942j, + -1.413928462488886 + 13.49772569889275j, + -10.84391707869699 + 19.27744616718165j], + dtype=np.complex128) alpha0 = 2.124853710495224e-16 - k = 8 + def __call__(self, A, n0, dt): + """Solve using 16th order IPF CRAM [Pusa16]_ - y = np.array(n0, dtype=np.float64) - for l in range(1, k+1): - y = 2.0*np.real(alpha[l]*sla.spsolve(A*dt - theta[l]*sp.eye(n), y)) + y + Parameters + ---------- + A : scipy.sparse.csr_matrix + Sparse transmutation matrix ``A[j, i]`` desribing rates at + which isotope ``i`` transmutes to isotope ``j`` + n0 : numpy.ndarray + Initial compositions, typically given in number of atoms in some + material or an atom density + dt : float + Time [s] of the specific interval to be solved - y *= alpha0 - return y + Returns + ------- + numpy.ndarray + Final compositions after ``dt`` + + """ + return super().__call__(A, n0, dt) -def CRAM48(A, n0, dt): - """Chebyshev Rational Approximation Method, order 48 +class Cram48Solver(IPFCramSolver): + r"""Solver implementing the 48th order IPF CRAM - Algorithm is the 48th order Chebyshev Rational Approximation Method, - implemented in the more stable `incomplete partial fraction (IPF) - `_ form. + Coefficients to :attr:`alpha`, :attr:`theta`, and :attr:`alpha0` + are from Table A.XII in [Pusa16]_. - Parameters + Attributes ---------- - A : scipy.linalg.csr_matrix - Matrix to take exponent of. - n0 : numpy.array - Vector to operate a matrix exponent on. - dt : float - Time to integrate to. - - Returns - ------- - numpy.array - Results of the matrix exponent. + alpha : numpy.ndarray + Complex residues of poles :attr:`theta` in the incomplete partial + factorization. Denoted as :math:`\tilde{\alpha}` + in Algorithm 1 of [Pusa16]_ + theta : numpy.ndarray + Complex poles :math:`\theta` of the rational approximation + alpha0 : float + Limit of the approximation at infinity """ - theta_r = np.array([-4.465731934165702e+1, -5.284616241568964e+0, - -8.867715667624458e+0, +3.493013124279215e+0, - +1.564102508858634e+1, +1.742097597385893e+1, - -2.834466755180654e+1, +1.661569367939544e+1, - +8.011836167974721e+0, -2.056267541998229e+0, - +1.449208170441839e+1, +1.853807176907916e+1, - +9.932562704505182e+0, -2.244223871767187e+1, - +8.590014121680897e-1, -1.286192925744479e+1, - +1.164596909542055e+1, +1.806076684783089e+1, - +5.870672154659249e+0, -3.542938819659747e+1, - +1.901323489060250e+1, +1.885508331552577e+1, - -1.734689708174982e+1, +1.316284237125190e+1]) - theta_i = np.array([+6.233225190695437e+1, +4.057499381311059e+1, - +4.325515754166724e+1, +3.281615453173585e+1, - +1.558061616372237e+1, +1.076629305714420e+1, - +5.492841024648724e+1, +1.316994930024688e+1, - +2.780232111309410e+1, +3.794824788914354e+1, - +1.799988210051809e+1, +5.974332563100539e+0, - +2.532823409972962e+1, +5.179633600312162e+1, - +3.536456194294350e+1, +4.600304902833652e+1, - +2.287153304140217e+1, +8.368200580099821e+0, - +3.029700159040121e+1, +5.834381701800013e+1, - +1.194282058271408e+0, +3.583428564427879e+0, - +4.883941101108207e+1, +2.042951874827759e+1]) + theta_r = np.array([ + -4.465731934165702e+1, -5.284616241568964e+0, + -8.867715667624458e+0, +3.493013124279215e+0, + +1.564102508858634e+1, +1.742097597385893e+1, + -2.834466755180654e+1, +1.661569367939544e+1, + +8.011836167974721e+0, -2.056267541998229e+0, + +1.449208170441839e+1, +1.853807176907916e+1, + +9.932562704505182e+0, -2.244223871767187e+1, + +8.590014121680897e-1, -1.286192925744479e+1, + +1.164596909542055e+1, +1.806076684783089e+1, + +5.870672154659249e+0, -3.542938819659747e+1, + +1.901323489060250e+1, +1.885508331552577e+1, + -1.734689708174982e+1, +1.316284237125190e+1]) + + theta_i = np.array([ + +6.233225190695437e+1, +4.057499381311059e+1, + +4.325515754166724e+1, +3.281615453173585e+1, + +1.558061616372237e+1, +1.076629305714420e+1, + +5.492841024648724e+1, +1.316994930024688e+1, + +2.780232111309410e+1, +3.794824788914354e+1, + +1.799988210051809e+1, +5.974332563100539e+0, + +2.532823409972962e+1, +5.179633600312162e+1, + +3.536456194294350e+1, +4.600304902833652e+1, + +2.287153304140217e+1, +8.368200580099821e+0, + +3.029700159040121e+1, +5.834381701800013e+1, + +1.194282058271408e+0, +3.583428564427879e+0, + +4.883941101108207e+1, +2.042951874827759e+1]) + theta = np.array(theta_r + theta_i * 1j, dtype=np.complex128) - alpha_r = np.array([+6.387380733878774e+2, +1.909896179065730e+2, - +4.236195226571914e+2, +4.645770595258726e+2, - +7.765163276752433e+2, +1.907115136768522e+3, - +2.909892685603256e+3, +1.944772206620450e+2, - +1.382799786972332e+5, +5.628442079602433e+3, - +2.151681283794220e+2, +1.324720240514420e+3, - +1.617548476343347e+4, +1.112729040439685e+2, - +1.074624783191125e+2, +8.835727765158191e+1, - +9.354078136054179e+1, +9.418142823531573e+1, - +1.040012390717851e+2, +6.861882624343235e+1, - +8.766654491283722e+1, +1.056007619389650e+2, - +7.738987569039419e+1, +1.041366366475571e+2]) - alpha_i = np.array([-6.743912502859256e+2, -3.973203432721332e+2, - -2.041233768918671e+3, -1.652917287299683e+3, - -1.783617639907328e+4, -5.887068595142284e+4, - -9.953255345514560e+3, -1.427131226068449e+3, - -3.256885197214938e+6, -2.924284515884309e+4, - -1.121774011188224e+3, -6.370088443140973e+4, - -1.008798413156542e+6, -8.837109731680418e+1, - -1.457246116408180e+2, -6.388286188419360e+1, - -2.195424319460237e+2, -6.719055740098035e+2, - -1.693747595553868e+2, -1.177598523430493e+1, - -4.596464999363902e+3, -1.738294585524067e+3, - -4.311715386228984e+1, -2.777743732451969e+2]) + alpha_r = np.array([ + +6.387380733878774e+2, +1.909896179065730e+2, + +4.236195226571914e+2, +4.645770595258726e+2, + +7.765163276752433e+2, +1.907115136768522e+3, + +2.909892685603256e+3, +1.944772206620450e+2, + +1.382799786972332e+5, +5.628442079602433e+3, + +2.151681283794220e+2, +1.324720240514420e+3, + +1.617548476343347e+4, +1.112729040439685e+2, + +1.074624783191125e+2, +8.835727765158191e+1, + +9.354078136054179e+1, +9.418142823531573e+1, + +1.040012390717851e+2, +6.861882624343235e+1, + +8.766654491283722e+1, +1.056007619389650e+2, + +7.738987569039419e+1, +1.041366366475571e+2]) + + alpha_i = np.array([ + -6.743912502859256e+2, -3.973203432721332e+2, + -2.041233768918671e+3, -1.652917287299683e+3, + -1.783617639907328e+4, -5.887068595142284e+4, + -9.953255345514560e+3, -1.427131226068449e+3, + -3.256885197214938e+6, -2.924284515884309e+4, + -1.121774011188224e+3, -6.370088443140973e+4, + -1.008798413156542e+6, -8.837109731680418e+1, + -1.457246116408180e+2, -6.388286188419360e+1, + -2.195424319460237e+2, -6.719055740098035e+2, + -1.693747595553868e+2, -1.177598523430493e+1, + -4.596464999363902e+3, -1.738294585524067e+3, + -4.311715386228984e+1, -2.777743732451969e+2]) + alpha = np.array(alpha_r + alpha_i * 1j, dtype=np.complex128) - n = A.shape[0] + + del theta_i, theta_r, alpha_r, alpha_i alpha0 = 2.258038182743983e-47 - k = 24 + def __call__(self, A, n0, dt): + """Solve using 48th order IPF CRAM [Pusa16]_ - y = np.array(n0, dtype=np.float64) - for l in range(k): - y = 2.0*np.real(alpha[l]*sla.spsolve(A*dt - theta[l]*sp.eye(n), y)) + y + Parameters + ---------- + A : scipy.sparse.csr_matrix + Sparse transmutation matrix ``A[j, i]`` desribing rates at + which isotope ``i`` transmutes to isotope ``j`` + n0 : numpy.ndarray + Initial compositions, typically given in number of atoms in some + material or an atom density + dt : float + Time [s] of the specific interval to be solved - y *= alpha0 - return y + Returns + ------- + numpy.ndarray + Final compositions after ``dt`` + + """ + return super().__call__(A, n0, dt) + + +CRAM16 = Cram16Solver().__call__ +CRAM48 = Cram48Solver().__call__ From df387e87dc069836a80cf62ed4839369fe66ca4c Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 19 Sep 2019 16:51:38 -0500 Subject: [PATCH 082/158] Document concrete and abstract depletion solvers --- docs/source/pythonapi/deplete.rst | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index e18afd9208..03f5e6b61a 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -139,6 +139,18 @@ The following functions are used to solve the depletion equations, with cram.deplete cram.timed_deplete + +:func:`cram.CRAM16` and :func:`cram.CRAM48` are aliases to the ``__call__`` +methods for the following classes + +.. autosummary:: + :toctree: generated + :nosignatures: + :template: mycallable.rst + + cram.Cram16Solver + cram.Cram48Solver + The following classes are used to help the :class:`openmc.deplete.Operator` compute quantities like effective fission yields, reaction rates, and total system energy. @@ -185,8 +197,8 @@ OpenMC simulations back on to the :class:`abc.TransportOperator` abc.ReactionRateHelper abc.TalliedFissionYieldHelper -Custom integrators can be developed by subclassing from the following abstract -base classes: +Custom integrators or depletion solvers can be developed by subclassing from +the following abstract base classes: .. autosummary:: :toctree: generated @@ -195,3 +207,5 @@ base classes: abc.Integrator abc.SIIntegrator + abc.DepSystemSolver + abc.IPFCramSolver From 2ead24e1723314da6721e04eb680d1851b88618f Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 19 Sep 2019 10:22:07 -0500 Subject: [PATCH 083/158] Teach openmc_statepoint_write about internal tallies If a tally is marked as not writeable, a tally group will still be added to the statepoint file. However, no filter, score, nuclide, or results data will be written to this tally group. A new attribute is introduced to this tally group "internal" that is an integer flag: 0 if the tally is not internal (i.e. is writeable) and 1 if the tally is internal (i.e. is not writeable). The internal attribute has been added to the statepoint io format in the documentation. Other changes: use ``` const auto& tally : model::tallies tally->X ``` to iterate over tallies during the writing process --- docs/source/io_formats/statepoint.rst | 4 +++ src/state_point.cpp | 40 ++++++++++++++++----------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/docs/source/io_formats/statepoint.rst b/docs/source/io_formats/statepoint.rst index 9c5b8f5260..5a373580df 100644 --- a/docs/source/io_formats/statepoint.rst +++ b/docs/source/io_formats/statepoint.rst @@ -109,6 +109,10 @@ The current version of the statepoint file format is 17.0. **/tallies/tally /** +:Attributes: - **internal** (*int*) -- Flag indicating the presence of tally + data (0) or absence of tally data (1). All user defined + tallies will have a value of 0 unless otherwise instructed. + :Datasets: - **n_realizations** (*int*) -- Number of realizations. - **n_filters** (*int*) -- Number of filters used. - **filters** (*int[]*) -- User-defined unique IDs of the filters on diff --git a/src/state_point.cpp b/src/state_point.cpp index 7239f3284f..8c1780c820 100644 --- a/src/state_point.cpp +++ b/src/state_point.cpp @@ -165,36 +165,43 @@ openmc_statepoint_write(const char* filename, bool* write_source) write_attribute(tallies_group, "ids", tally_ids); // Write all tally information except results - for (const auto& tally_ptr : model::tallies) { - const auto& tally {*tally_ptr}; + for (const auto& tally : model::tallies) { hid_t tally_group = create_group(tallies_group, - "tally " + std::to_string(tally.id_)); + "tally " + std::to_string(tally->id_)); - write_dataset(tally_group, "name", tally.name_); + write_dataset(tally_group, "name", tally->name_); - if (tally.estimator_ == ESTIMATOR_ANALOG) { + if (tally->writeable_) { + write_attribute(tally_group, "internal", 0); + } else { + write_attribute(tally_group, "internal", 1); + close_group(tally_group); + continue; + } + + if (tally->estimator_ == ESTIMATOR_ANALOG) { write_dataset(tally_group, "estimator", "analog"); - } else if (tally.estimator_ == ESTIMATOR_TRACKLENGTH) { + } else if (tally->estimator_ == ESTIMATOR_TRACKLENGTH) { write_dataset(tally_group, "estimator", "tracklength"); - } else if (tally.estimator_ == ESTIMATOR_COLLISION) { + } else if (tally->estimator_ == ESTIMATOR_COLLISION) { write_dataset(tally_group, "estimator", "collision"); } - write_dataset(tally_group, "n_realizations", tally.n_realizations_); + write_dataset(tally_group, "n_realizations", tally->n_realizations_); // Write the ID of each filter attached to this tally - write_dataset(tally_group, "n_filters", tally.filters().size()); - if (!tally.filters().empty()) { + write_dataset(tally_group, "n_filters", tally->filters().size()); + if (!tally->filters().empty()) { std::vector filter_ids; - filter_ids.reserve(tally.filters().size()); - for (auto i_filt : tally.filters()) + filter_ids.reserve(tally->filters().size()); + for (auto i_filt : tally->filters()) filter_ids.push_back(model::tally_filters[i_filt]->id()); write_dataset(tally_group, "filters", filter_ids); } // Write the nuclides this tally scores std::vector nuclides; - for (auto i_nuclide : tally.nuclides_) { + for (auto i_nuclide : tally->nuclides_) { if (i_nuclide == -1) { nuclides.push_back("total"); } else { @@ -207,12 +214,12 @@ openmc_statepoint_write(const char* filename, bool* write_source) } write_dataset(tally_group, "nuclides", nuclides); - if (tally.deriv_ != C_NONE) write_dataset(tally_group, "derivative", - model::tally_derivs[tally.deriv_].id); + if (tally->deriv_ != C_NONE) write_dataset(tally_group, "derivative", + model::tally_derivs[tally->deriv_].id); // Write the tally score bins std::vector scores; - for (auto sc : tally.scores_) scores.push_back(reaction_name(sc)); + for (auto sc : tally->scores_) scores.push_back(reaction_name(sc)); write_dataset(tally_group, "n_score_bins", scores.size()); write_dataset(tally_group, "score_bins", scores); @@ -232,6 +239,7 @@ openmc_statepoint_write(const char* filename, bool* write_source) // Write all tally results for (const auto& tally : model::tallies) { + if (!tally->writeable_) continue; // Write sum and sum_sq for each bin std::string name = "tally " + std::to_string(tally->id_); hid_t tally_group = open_group(tallies_group, name.c_str()); From 1300ca2678ad3f0bfc37822c68d2800317a6f9e7 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 20 Sep 2019 08:48:27 -0500 Subject: [PATCH 084/158] Teach load_state_point about internal tallies When reading in tally data from a statepoint file, a check is performed on the "internal" attribute of the tally group. If the attribute doesn't exist (previous statepoint files), then it is assumed that the tally is writeable / not internal. Otherwise, the internal attribute is read from the dataset. If the tally is determined to be internal, the value of tally.writeable_ is set to false and no tally data is read --- src/state_point.cpp | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/state_point.cpp b/src/state_point.cpp index 8c1780c820..a550748a54 100644 --- a/src/state_point.cpp +++ b/src/state_point.cpp @@ -433,11 +433,23 @@ void load_state_point() // Read sum, sum_sq, and N for each bin std::string name = "tally " + std::to_string(tally->id_); hid_t tally_group = open_group(tallies_group, name.c_str()); - auto& results = tally->results_; - read_tally_results(tally_group, results.shape()[0], - results.shape()[1], results.data()); - read_dataset(tally_group, "n_realizations", tally->n_realizations_); - close_group(tally_group); + + int internal; + if (attribute_exists(tally_group, "internal")) { + read_attribute(tally_group, "internal", internal); + } else { + internal = 0; + } + if (internal) { + tally->writeable_ = false; + } else { + + auto& results = tally->results_; + read_tally_results(tally_group, results.shape()[0], + results.shape()[1], results.data()); + read_dataset(tally_group, "n_realizations", tally->n_realizations_); + close_group(tally_group); + } } close_group(tallies_group); @@ -705,6 +717,7 @@ void write_tally_results_nr(hid_t file_id) for (const auto& t : model::tallies) { // Skip any tallies that are not active if (!t->active_) continue; + if (!t->writeable_) continue; if (mpi::master && !object_exists(file_id, "tallies_present")) { write_attribute(file_id, "tallies_present", 1); From 66593fd7e1d072e1ad07e4fb5d6d3724cbad0ac6 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 20 Sep 2019 09:14:09 -0500 Subject: [PATCH 085/158] Expose tally.writeable_ through openmc.lib Add two functions to capi.h: openmc_tally_set_writeable and openmc_tally_get_writeable which are exposed to the Python API through the openmc.lib.Tally.writeable property. These functions are very similar to the set/get active counterparts as both act on a boolean Tally attribute A new unit test test_tally_writeable has been added that toggles the writeable state of a tally. It is important to note that the writeable state must be reset, otherwise tallies in subsequent tests will be skewed. This is because the test_tally_writeable function requires the capi_simulation_init fixture and the tallies are shared by those functions that use the capi_run fixture --- include/openmc/capi.h | 2 ++ openmc/lib/tally.py | 16 ++++++++++++++++ src/tallies/tally.cpp | 24 ++++++++++++++++++++++++ tests/unit_tests/test_lib.py | 11 ++++++++++- 4 files changed, 52 insertions(+), 1 deletion(-) diff --git a/include/openmc/capi.h b/include/openmc/capi.h index 3d2e8f57bf..327594c3e4 100644 --- a/include/openmc/capi.h +++ b/include/openmc/capi.h @@ -110,6 +110,7 @@ extern "C" { int openmc_tally_get_nuclides(int32_t index, int** nuclides, int* n); int openmc_tally_get_scores(int32_t index, int** scores, int* n); int openmc_tally_get_type(int32_t index, int32_t* type); + int openmc_tally_get_writeable(int32_t index, bool* writeable); int openmc_tally_reset(int32_t index); int openmc_tally_results(int32_t index, double** ptr, size_t shape_[3]); int openmc_tally_set_active(int32_t index, bool active); @@ -119,6 +120,7 @@ extern "C" { int openmc_tally_set_nuclides(int32_t index, int n, const char** nuclides); int openmc_tally_set_scores(int32_t index, int n, const char** scores); int openmc_tally_set_type(int32_t index, const char* type); + int openmc_tally_set_writeable(int32_t index, bool writeable); int openmc_zernike_filter_get_order(int32_t index, int* order); int openmc_zernike_filter_get_params(int32_t index, double* x, double* y, double* r); int openmc_zernike_filter_set_order(int32_t index, int order); diff --git a/openmc/lib/tally.py b/openmc/lib/tally.py index 97b7437ea3..906084c4b7 100644 --- a/openmc/lib/tally.py +++ b/openmc/lib/tally.py @@ -53,6 +53,9 @@ _dll.openmc_tally_get_scores.errcheck = _error_handler _dll.openmc_tally_get_type.argtypes = [c_int32, POINTER(c_int32)] _dll.openmc_tally_get_type.restype = c_int _dll.openmc_tally_get_type.errcheck = _error_handler +_dll.openmc_tally_get_writeable.argtypes = [c_int32, POINTER(c_bool)] +_dll.openmc_tally_get_writeable.restype = c_int +_dll.openmc_tally_get_writeable.errcheck = _error_handler _dll.openmc_tally_reset.argtypes = [c_int32] _dll.openmc_tally_reset.restype = c_int _dll.openmc_tally_reset.errcheck = _error_handler @@ -81,6 +84,9 @@ _dll.openmc_tally_set_scores.errcheck = _error_handler _dll.openmc_tally_set_type.argtypes = [c_int32, c_char_p] _dll.openmc_tally_set_type.restype = c_int _dll.openmc_tally_set_type.errcheck = _error_handler +_dll.openmc_tally_set_writeable.argtypes = [c_int32, c_bool] +_dll.openmc_tally_set_writeable.restype = c_int +_dll.openmc_tally_set_writeable.errcheck = _error_handler _dll.tallies_size.restype = c_size_t @@ -344,6 +350,16 @@ class Tally(_FortranObjectWithID): return std_dev + @property + def writeable(self): + writeable = c_bool() + _dll.openmc_tally_get_writeable(self._index, writeable) + return writeable.value + + @writeable.setter + def writeable(self, writeable): + _dll.openmc_tally_set_writeable(self._index, writeable) + def reset(self): """Reset results and num_realizations of tally""" _dll.openmc_tally_reset(self._index) diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 563beee766..96253285e2 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -1220,6 +1220,30 @@ openmc_tally_set_active(int32_t index, bool active) return 0; } +extern "C" int +openmc_tally_get_writeable(int32_t index, bool* writeable) +{ + if (index < 0 || index >= model::tallies.size()) { + set_errmsg("Index in tallies array is out of bounds."); + return OPENMC_E_OUT_OF_BOUNDS; + } + *writeable = model::tallies[index]->writeable_; + + return 0; +} + +extern "C" int +openmc_tally_set_writeable(int32_t index, bool writeable) +{ + if (index < 0 || index >= model::tallies.size()) { + set_errmsg("Index in tallies array is out of bounds."); + return OPENMC_E_OUT_OF_BOUNDS; + } + model::tallies[index]->writeable_ = writeable; + + return 0; +} + extern "C" int openmc_tally_get_scores(int32_t index, int** scores, int* n) { diff --git a/tests/unit_tests/test_lib.py b/tests/unit_tests/test_lib.py index 2f39f0b0d3..3736ddc1e0 100644 --- a/tests/unit_tests/test_lib.py +++ b/tests/unit_tests/test_lib.py @@ -256,9 +256,18 @@ def test_tally_activate(capi_simulation_init): assert t.active +def test_tally_writeable(capi_simulation_init): + t = openmc.lib.tallies[1] + assert t.writeable + t.writeable = False + assert not t.writeable + # Revert tally to writeable state for capi_run fixtures + t.writeable = True + + def test_tally_results(capi_run): t = openmc.lib.tallies[1] - assert t.num_realizations == 10 # t was made active in test_tally + assert t.num_realizations == 10 # t was made active in test_tally_active assert np.all(t.mean >= 0) nonzero = (t.mean > 0.0) assert np.all(t.std_dev[nonzero] >= 0) From a83f4d2bb2def7466bdd0d8b244e4b4c31266af4 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 20 Sep 2019 09:15:25 -0500 Subject: [PATCH 086/158] Rename capi_* fixtures to lib_* in test_lib.py --- tests/unit_tests/test_lib.py | 72 ++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/tests/unit_tests/test_lib.py b/tests/unit_tests/test_lib.py index 3736ddc1e0..7f992cb096 100644 --- a/tests/unit_tests/test_lib.py +++ b/tests/unit_tests/test_lib.py @@ -50,24 +50,24 @@ def pincell_model(): @pytest.fixture(scope='module') -def capi_init(pincell_model, mpi_intracomm): +def lib_init(pincell_model, mpi_intracomm): openmc.lib.init(intracomm=mpi_intracomm) yield openmc.lib.finalize() @pytest.fixture(scope='module') -def capi_simulation_init(capi_init): +def lib_simulation_init(lib_init): openmc.lib.simulation_init() yield @pytest.fixture(scope='module') -def capi_run(capi_simulation_init): +def lib_run(lib_simulation_init): openmc.lib.run() -def test_cell_mapping(capi_init): +def test_cell_mapping(lib_init): cells = openmc.lib.cells assert isinstance(cells, Mapping) assert len(cells) == 3 @@ -76,7 +76,7 @@ def test_cell_mapping(capi_init): assert cell_id == cell.id -def test_cell(capi_init): +def test_cell(lib_init): cell = openmc.lib.cells[1] assert isinstance(cell.fill, openmc.lib.Material) cell.fill = openmc.lib.materials[1] @@ -85,7 +85,7 @@ def test_cell(capi_init): cell.name = "Not fuel" assert cell.name == "Not fuel" -def test_cell_temperature(capi_init): +def test_cell_temperature(lib_init): cell = openmc.lib.cells[1] cell.set_temperature(100.0, 0) assert cell.get_temperature(0) == 100.0 @@ -93,7 +93,7 @@ def test_cell_temperature(capi_init): assert cell.get_temperature() == 200.0 -def test_new_cell(capi_init): +def test_new_cell(lib_init): with pytest.raises(exc.AllocationError): openmc.lib.Cell(1) new_cell = openmc.lib.Cell() @@ -101,7 +101,7 @@ def test_new_cell(capi_init): assert len(openmc.lib.cells) == 5 -def test_material_mapping(capi_init): +def test_material_mapping(lib_init): mats = openmc.lib.materials assert isinstance(mats, Mapping) assert len(mats) == 3 @@ -110,7 +110,7 @@ def test_material_mapping(capi_init): assert mat_id == mat.id -def test_material(capi_init): +def test_material(lib_init): m = openmc.lib.materials[3] assert m.nuclides == ['H1', 'O16', 'B10', 'B11'] @@ -136,14 +136,14 @@ def test_material(capi_init): m.name = "Not hot borated water" assert m.name == "Not hot borated water" -def test_material_add_nuclide(capi_init): +def test_material_add_nuclide(lib_init): m = openmc.lib.materials[3] m.add_nuclide('Xe135', 1e-12) assert m.nuclides[-1] == 'Xe135' assert m.densities[-1] == 1e-12 -def test_new_material(capi_init): +def test_new_material(lib_init): with pytest.raises(exc.AllocationError): openmc.lib.Material(1) new_mat = openmc.lib.Material() @@ -151,7 +151,7 @@ def test_new_material(capi_init): assert len(openmc.lib.materials) == 5 -def test_nuclide_mapping(capi_init): +def test_nuclide_mapping(lib_init): nucs = openmc.lib.nuclides assert isinstance(nucs, Mapping) assert len(nucs) == 13 @@ -160,7 +160,7 @@ def test_nuclide_mapping(capi_init): assert name == nuc.name -def test_settings(capi_init): +def test_settings(lib_init): settings = openmc.lib.settings assert settings.batches == 10 settings.batches = 10 @@ -175,7 +175,7 @@ def test_settings(capi_init): settings.run_mode = 'eigenvalue' -def test_tally_mapping(capi_init): +def test_tally_mapping(lib_init): tallies = openmc.lib.tallies assert isinstance(tallies, Mapping) assert len(tallies) == 3 @@ -184,7 +184,7 @@ def test_tally_mapping(capi_init): assert tally_id == tally.id -def test_energy_function_filter(capi_init): +def test_energy_function_filter(lib_init): """Test special __new__ and __init__ for EnergyFunctionFilter""" efunc = openmc.lib.EnergyFunctionFilter([0.0, 1.0], [0.0, 2.0]) assert len(efunc.energy) == 2 @@ -193,7 +193,7 @@ def test_energy_function_filter(capi_init): assert (efunc.y == [0.0, 2.0]).all() -def test_tally(capi_init): +def test_tally(lib_init): t = openmc.lib.tallies[1] assert t.type == 'volume' assert len(t.filters) == 2 @@ -239,7 +239,7 @@ def test_tally(capi_init): assert len(t3_f.y) == 3 -def test_new_tally(capi_init): +def test_new_tally(lib_init): with pytest.raises(exc.AllocationError): openmc.lib.Material(1) new_tally = openmc.lib.Tally() @@ -249,23 +249,23 @@ def test_new_tally(capi_init): assert len(openmc.lib.tallies) == 5 -def test_tally_activate(capi_simulation_init): +def test_tally_activate(lib_simulation_init): t = openmc.lib.tallies[1] assert not t.active t.active = True assert t.active -def test_tally_writeable(capi_simulation_init): +def test_tally_writeable(lib_simulation_init): t = openmc.lib.tallies[1] assert t.writeable t.writeable = False assert not t.writeable - # Revert tally to writeable state for capi_run fixtures + # Revert tally to writeable state for lib_run fixtures t.writeable = True -def test_tally_results(capi_run): +def test_tally_results(lib_run): t = openmc.lib.tallies[1] assert t.num_realizations == 10 # t was made active in test_tally_active assert np.all(t.mean >= 0) @@ -278,26 +278,26 @@ def test_tally_results(capi_run): assert t2.mean.size == (n + 1) * (n + 2) // 2 * 3 # Number of Zernike coeffs * 3 cells -def test_global_tallies(capi_run): +def test_global_tallies(lib_run): assert openmc.lib.num_realizations() == 5 gt = openmc.lib.global_tallies() for mean, std_dev in gt: assert mean >= 0 -def test_statepoint(capi_run): +def test_statepoint(lib_run): openmc.lib.statepoint_write('test_sp.h5') assert os.path.exists('test_sp.h5') -def test_source_bank(capi_run): +def test_source_bank(lib_run): source = openmc.lib.source_bank() assert np.all(source['E'] > 0.0) assert np.all(source['wgt'] == 1.0) assert np.allclose(np.linalg.norm(source['u'], axis=1), 1.0) -def test_by_batch(capi_run): +def test_by_batch(lib_run): openmc.lib.hard_reset() # Running next batch before simulation is initialized should raise an @@ -322,7 +322,7 @@ def test_by_batch(capi_run): openmc.lib.simulation_finalize() -def test_reset(capi_run): +def test_reset(lib_run): # Init and run 10 batches. openmc.lib.hard_reset() openmc.lib.simulation_init() @@ -353,7 +353,7 @@ def test_reset(capi_run): openmc.lib.simulation_finalize() -def test_reproduce_keff(capi_init): +def test_reproduce_keff(lib_init): # Get k-effective after run openmc.lib.hard_reset() openmc.lib.run() @@ -366,7 +366,7 @@ def test_reproduce_keff(capi_init): assert keff0 == pytest.approx(keff1) -def test_find_cell(capi_init): +def test_find_cell(lib_init): cell, instance = openmc.lib.find_cell((0., 0., 0.)) assert cell is openmc.lib.cells[1] cell, instance = openmc.lib.find_cell((0.4, 0., 0.)) @@ -375,14 +375,14 @@ def test_find_cell(capi_init): openmc.lib.find_cell((100., 100., 100.)) -def test_find_material(capi_init): +def test_find_material(lib_init): mat = openmc.lib.find_material((0., 0., 0.)) assert mat is openmc.lib.materials[1] mat = openmc.lib.find_material((0.4, 0., 0.)) assert mat is openmc.lib.materials[2] -def test_mesh(capi_init): +def test_mesh(lib_init): mesh = openmc.lib.RegularMesh() mesh.dimension = (2, 3, 4) assert mesh.dimension == (2, 3, 4) @@ -417,7 +417,7 @@ def test_mesh(capi_init): assert msf.mesh == mesh -def test_restart(capi_init, mpi_intracomm): +def test_restart(lib_init, mpi_intracomm): # Finalize and re-init to make internal state consistent with XML. openmc.lib.hard_reset() openmc.lib.finalize() @@ -449,7 +449,7 @@ def test_restart(capi_init, mpi_intracomm): assert keff0 == pytest.approx(keff1) -def test_load_nuclide(capi_init): +def test_load_nuclide(lib_init): # load multiple nuclides openmc.lib.load_nuclide('H3') assert 'H3' in openmc.lib.nuclides @@ -460,7 +460,7 @@ def test_load_nuclide(capi_init): openmc.lib.load_nuclide('Pu3') -def test_id_map(capi_init): +def test_id_map(lib_init): expected_ids = np.array([[(3, 3), (2, 2), (3, 3)], [(2, 2), (1, 1), (2, 2)], [(3, 3), (2, 2), (3, 3)]], dtype='int32') @@ -478,7 +478,7 @@ def test_id_map(capi_init): ids = openmc.lib.plot.id_map(s) assert np.array_equal(expected_ids, ids) -def test_property_map(capi_init): +def test_property_map(lib_init): expected_properties = np.array( [[(293.6, 0.740582), (293.6, 6.55), (293.6, 0.740582)], [ (293.6, 6.55), (293.6, 10.29769), (293.6, 6.55)], @@ -498,7 +498,7 @@ def test_property_map(capi_init): assert np.allclose(expected_properties, properties, atol=1e-04) -def test_position(capi_init): +def test_position(lib_init): pos = openmc.lib.plot._Position(1.0, 2.0, 3.0) @@ -511,7 +511,7 @@ def test_position(capi_init): assert tuple(pos) == (1.3, 2.3, 3.3) -def test_global_bounding_box(capi_init): +def test_global_bounding_box(lib_init): expected_llc = (-0.63, -0.63, -np.inf) expected_urc = (0.63, 0.63, np.inf) From 4b688e5efae9bd99c586d2f8afd795d28c6e0fd2 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 20 Sep 2019 10:31:56 -0500 Subject: [PATCH 087/158] Teach openmc.StatePoint about internal tallies When reading tally data from the statepoint file, tallies that are marked as internal are not created nor added to the StatePoint.tallies property --- openmc/statepoint.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/openmc/statepoint.py b/openmc/statepoint.py index c9a8f9f9a8..5802a74cae 100644 --- a/openmc/statepoint.py +++ b/openmc/statepoint.py @@ -375,13 +375,18 @@ class StatePoint(object): for tally_id in tally_ids: group = tallies_group['tally {}'.format(tally_id)] - # Read the number of realizations - n_realizations = group['n_realizations'][()] + # Check if tally is internal and therefore has no data + if group.attrs.get("internal"): + continue # Create Tally object and assign basic properties tally = openmc.Tally(tally_id) tally._sp_filename = self._f.filename tally.name = group['name'][()].decode() if 'name' in group else '' + + # Read the number of realizations + n_realizations = group['n_realizations'][()] + tally.estimator = group['estimator'][()].decode() tally.num_realizations = n_realizations From 72212f60a2ea64cbd37059ed7c45b5ca2c7f3cee Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 20 Sep 2019 10:49:32 -0500 Subject: [PATCH 088/158] Use internal tallies to compute quantities for depletion Tallies created by internal depletion helpers are marked as internal using the openmc.lib.Tally.writeable property. This is done to resolve issue #1327 where statepoint files could grow to be quite large after tallying reaction rates across many burnable materials for all nuclides of interest. A check is added in the depletion regression test to ensure that no additional tallies are loaded to the StatePoint. --- openmc/deplete/abc.py | 1 + openmc/deplete/helpers.py | 3 +++ tests/regression_tests/deplete/test.py | 6 ++++++ 3 files changed, 10 insertions(+) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index e0ed64645e..3a7eee34df 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -539,6 +539,7 @@ class TalliedFissionYieldHelper(FissionYieldHelper): # Tally group-wise fission reaction rates self._fission_rate_tally = Tally() + self._fission_rate_tally.writeable = False self._fission_rate_tally.scores = ['fission'] self._fission_rate_tally.filters = [MaterialFilter(materials)] diff --git a/openmc/deplete/helpers.py b/openmc/deplete/helpers.py index 9e3e379d58..b1e7e17ef3 100644 --- a/openmc/deplete/helpers.py +++ b/openmc/deplete/helpers.py @@ -59,6 +59,7 @@ class DirectReactionRateHelper(ReactionRateHelper): ``"(n, gamma)"``, needed for the reaction rate tally. """ self._rate_tally = Tally() + self._rate_tally.writeable = False self._rate_tally.scores = scores self._rate_tally.filters = [MaterialFilter(materials)] @@ -194,6 +195,7 @@ class EnergyScoreHelper(EnergyHelper): """ self._tally = Tally() + self._tally.writeable = False self._tally.scores = [self.score] def reset(self): @@ -570,6 +572,7 @@ class AveragedFissionYieldHelper(TalliedFissionYieldHelper): func_filter = EnergyFunctionFilter() func_filter.set_data((0, self._upper_energy), (0, self._upper_energy)) weighted_tally = Tally() + weighted_tally.writeable = False weighted_tally.scores = ['fission'] weighted_tally.filters = filters + [func_filter] self._weighted_tally = weighted_tally diff --git a/tests/regression_tests/deplete/test.py b/tests/regression_tests/deplete/test.py index c7a9c162e0..01ce21c010 100644 --- a/tests/regression_tests/deplete/test.py +++ b/tests/regression_tests/deplete/test.py @@ -108,11 +108,17 @@ def test_full(run_in_tmpdir): t_ref, k_ref = res_ref.get_eigenvalue() k_state = np.empty_like(k_ref) + n_tallies = np.empty(N + 1, dtype=int) + # Get statepoint files for all BOS points and EOL for n in range(N + 1): statepoint = openmc.StatePoint("openmc_simulation_n{}.h5".format(n)) k_n = statepoint.k_combined k_state[n] = [k_n.nominal_value, k_n.std_dev] + n_tallies[n] = len(statepoint.tallies) # Look for exact match pulling from statepoint and depletion_results assert np.all(k_state == k_test) assert np.allclose(k_test, k_ref) + + # Check that no additional tallies are loaded from the files + assert np.all(n_tallies == 0) From 1d08279c0216f4387d88841af2333f7cfe33daf0 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 20 Sep 2019 13:29:46 -0500 Subject: [PATCH 089/158] Respond to reviewer comments on depletion caveats --- docs/source/usersguide/depletion.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/usersguide/depletion.rst b/docs/source/usersguide/depletion.rst index 25ada4f5d4..9e26930986 100644 --- a/docs/source/usersguide/depletion.rst +++ b/docs/source/usersguide/depletion.rst @@ -62,7 +62,7 @@ The default energy deposition mode, ``"fission-q"``, instructs the :class:`openmc.deplete.Operator` to normalize reaction rates using the product of fission reaction rates and fission Q values taken from the depletion chain. This approach does not consider indirect contributions to energy deposition, -including neutron heating or kinetic energy of fission fragments. In doing this, +such as neutron heating and energy from secondary photons. In doing this, the energy deposited during a transport calculation will be lower than expected. This causes the reaction rates to be over-adjusted to hit the user-specific power, or power density, leading to an over-depletion of burnable materials. @@ -79,7 +79,7 @@ be, including indirect components. Some examples are provided below:: chain.export_to_xml("chain_mod_q.xml") op = openmc.deplete.Operator(geometry, setting, "chain_mod_q.xml") - # pass the modified fission Q directly to the operator + # alternatively, pass the modified fission Q directly to the operator op = openmc.deplete.Operator(geometry, setting, "chain.xml", fission_q=fission_q) @@ -132,6 +132,6 @@ transport step. .. note:: - This will increase the total memory usage and run time due to increased - tallies and material definitions. + This will increase the total memory usage and run time due to an increased + number of tallies and material definitions. From 0055ecbb3a4ef859f5e3f834548868168ae89810 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Fri, 20 Sep 2019 14:22:28 -0500 Subject: [PATCH 090/158] Read materials from summary.h5 if using DagMC geometry. --- openmc/summary.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/openmc/summary.py b/openmc/summary.py index 2ab29c00a4..6b8471977e 100644 --- a/openmc/summary.py +++ b/openmc/summary.py @@ -97,16 +97,17 @@ class Summary(object): self._macroscopics = name.decode() def _read_geometry(self): - if "dagmc" in self._f['geometry'].attrs.keys(): - return - # Read in and initialize the Materials and Geometry + # Read in and initialize the Materials self._read_materials() - self._read_surfaces() - cell_fills = self._read_cells() - self._read_universes() - self._read_lattices() - self._finalize_geometry(cell_fills) + + # Read native geometry only + if "dagmc" not in self._f['geometry'].attrs.keys(): + self._read_surfaces() + cell_fills = self._read_cells() + self._read_universes() + self._read_lattices() + self._finalize_geometry(cell_fills) def _read_materials(self): for group in self._f['materials'].values(): From c1d746164fa47849d7f4e89bfcd203a825e8556c Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Mon, 23 Sep 2019 12:00:24 -0500 Subject: [PATCH 091/158] Writing empty groups for nuclides/atoms in volume calc if no entries are found. --- openmc/volume.py | 3 +-- src/volume_calc.cpp | 26 ++++++++++++-------------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/openmc/volume.py b/openmc/volume.py index 7ac4fdd0c6..91ff829cbd 100644 --- a/openmc/volume.py +++ b/openmc/volume.py @@ -212,13 +212,12 @@ class VolumeCalculation(object): ids.append(domain_id) group = f[obj_name] volume = ufloat(*group['volume'][()]) + volumes[domain_id] = volume nucnames = group['nuclides'][()] atoms_ = group['atoms'][()] - atom_dict = OrderedDict() for name_i, atoms_i in zip(nucnames, atoms_): atom_dict[name_i.decode()] = ufloat(*atoms_i) - volumes[domain_id] = volume atoms[domain_id] = atom_dict # Instantiate some throw-away domains that are used by the constructor diff --git a/src/volume_calc.cpp b/src/volume_calc.cpp index 93b729bd66..6c1c48932f 100644 --- a/src/volume_calc.cpp +++ b/src/volume_calc.cpp @@ -311,22 +311,20 @@ void VolumeCalculation::to_hdf5(const std::string& filename, // Create array of nuclide names from the vector auto n_nuc = result.nuclides.size(); - if (!result.nuclides.empty()) { - std::vector nucnames; - for (int i_nuc : result.nuclides) { - nucnames.push_back(data::nuclides[i_nuc]->name_); - } - - // Create array of total # of atoms with uncertainty for each nuclide - xt::xtensor atom_data({n_nuc, 2}); - xt::view(atom_data, xt::all(), 0) = xt::adapt(result.atoms); - xt::view(atom_data, xt::all(), 1) = xt::adapt(result.uncertainty); - - // Write results - write_dataset(group_id, "nuclides", nucnames); - write_dataset(group_id, "atoms", atom_data); + std::vector nucnames; + for (int i_nuc : result.nuclides) { + nucnames.push_back(data::nuclides[i_nuc]->name_); } + // Create array of total # of atoms with uncertainty for each nuclide + xt::xtensor atom_data({n_nuc, 2}); + xt::view(atom_data, xt::all(), 0) = xt::adapt(result.atoms); + xt::view(atom_data, xt::all(), 1) = xt::adapt(result.uncertainty); + + // Write results + write_dataset(group_id, "nuclides", nucnames); + write_dataset(group_id, "atoms", atom_data); + close_group(group_id); } From 45aeb35aa99e9ea80c58ebe8f356a50b8e729cac Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 24 Sep 2019 15:15:30 -0400 Subject: [PATCH 092/158] Rotate azimuthal distributions by pi/2 --- src/distribution_multi.cpp | 4 ++-- tests/regression_tests/source/results_true.dat | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/distribution_multi.cpp b/src/distribution_multi.cpp index d4a728c71f..0c1ecddecb 100644 --- a/src/distribution_multi.cpp +++ b/src/distribution_multi.cpp @@ -59,8 +59,8 @@ Direction PolarAzimuthal::sample() const double mu = mu_->sample(); if (mu == 1.0) return u_ref_; - // Sample azimuthal angle - double phi = phi_->sample(); + // Sample the azimuthal angle with an offset to match azimuth conventions + double phi = phi_->sample() + 0.5*PI; return rotate_angle(u_ref_, mu, &phi); } diff --git a/tests/regression_tests/source/results_true.dat b/tests/regression_tests/source/results_true.dat index 90cc3ec1f6..6fe76c8cd5 100644 --- a/tests/regression_tests/source/results_true.dat +++ b/tests/regression_tests/source/results_true.dat @@ -1,2 +1,2 @@ k-combined: -3.026614E-01 3.952008E-03 +2.800827E-01 7.360163E-03 From 60f17ea4663886af845a15f393e6e026f4b373f7 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 25 Sep 2019 12:36:47 -0500 Subject: [PATCH 093/158] Make sure from_ace classmethods get correct type of ACE table --- openmc/data/neutron.py | 3 +++ openmc/data/photon.py | 4 +++- openmc/data/thermal.py | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/openmc/data/neutron.py b/openmc/data/neutron.py index 022cf8283d..8a6c889817 100644 --- a/openmc/data/neutron.py +++ b/openmc/data/neutron.py @@ -588,6 +588,9 @@ class IncidentNeutron(EqualityMixin): # If mass number hasn't been specified, make an educated guess zaid, xs = ace.name.split('.') + if not xs.endswith('c'): + raise TypeError( + "{} is not a continuous-energy neutron ACE table.".format(ace)) name, element, Z, mass_number, metastable = \ get_metadata(int(zaid), metastable_scheme) diff --git a/openmc/data/photon.py b/openmc/data/photon.py index 0d0e527533..3766b697e0 100644 --- a/openmc/data/photon.py +++ b/openmc/data/photon.py @@ -549,7 +549,9 @@ class IncidentPhoton(EqualityMixin): ace = get_table(ace_or_filename) # Get atomic number based on name of ACE table - zaid = ace.name.split('.')[0] + zaid, xs = ace.name.split('.') + if not xs.endswith('p'): + raise TypeError("{} is not a photoatomic transport ACE table.".format(ace)) Z = get_metadata(int(zaid))[2] # Read each reaction diff --git a/openmc/data/thermal.py b/openmc/data/thermal.py index 06e50c6618..4fbe963631 100644 --- a/openmc/data/thermal.py +++ b/openmc/data/thermal.py @@ -600,6 +600,8 @@ class ThermalScattering(EqualityMixin): # Get new name that is GND-consistent ace_name, xs = ace.name.split('.') + if not xs.endswith('t'): + raise TypeError("{} is not a thermal scattering ACE table.".format(ace)) name = get_thermal_name(ace_name) # Assign temperature to the running list From d112eddbc39470b9df8d2d5b8797d69e7d1002c1 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 26 Sep 2019 20:26:13 -0400 Subject: [PATCH 094/158] openmc.deplete.cram.IPFCramSolver accepts coefficients at init Moved from deplete.abc into openmc.deplete.cram. CRAM16 and CRAM48 are aliases to __call__ methods for two instances of IPFCramSolver: Cram16Solver and Cram48Solver, created using 16th and 48th order coefficients --- docs/source/pythonapi/deplete.rst | 12 -- openmc/deplete/abc.py | 80 +--------- openmc/deplete/cram.py | 247 +++++++++++++++--------------- 3 files changed, 125 insertions(+), 214 deletions(-) diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 03f5e6b61a..82143e14a0 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -139,18 +139,6 @@ The following functions are used to solve the depletion equations, with cram.deplete cram.timed_deplete - -:func:`cram.CRAM16` and :func:`cram.CRAM48` are aliases to the ``__call__`` -methods for the following classes - -.. autosummary:: - :toctree: generated - :nosignatures: - :template: mycallable.rst - - cram.Cram16Solver - cram.Cram48Solver - The following classes are used to help the :class:`openmc.deplete.Operator` compute quantities like effective fission yields, reaction rates, and total system energy. diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index b42aa3e745..2005528707 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -14,9 +14,7 @@ from copy import deepcopy from warnings import warn from numbers import Real, Integral -from numpy import nonzero, empty, asarray, float64, real -import scipy.sparse as sp -import scipy.sparse.linalg as sla +from numpy import nonzero, empty, asarray from uncertainties import ufloat from openmc.data import DataLibrary, JOULE_PER_EV @@ -894,79 +892,3 @@ class DepSystemSolver(ABC): to ``n0``. """ - - -class IPFCramSolver(DepSystemSolver): - r"""Abstract class that implements the IPF form of CRAM - - Provides a :meth:`__call__` that utilizes an incomplete - partial factorization (IPF) for the Chebyshev Rational Approximation - Method (CRAM) [Pusa16]_ - - Concrete subclasses must provide two complex vectors :attr:`alpha` - and :attr:`theta` that make up the coefficients of the decompostion. - Vectors are expected to be of equal length ``N``. - Subclases are also expected to provide a coefficient :attr:`alpha0` - used in the final scaling step. - - Attributes - ---------- - alpha : numpy.ndarray - Complex residues of poles :attr:`theta` in the incomplete partial - factorization. Denoted as :math:`\tilde{\alpha}` - theta : numpy.ndarray - Complex poles :math:`\theta` of the rational approximation - alpha0 : float - Limit of the approximation at infinity - - References - ---------- - - .. [Pusa16] M. Pusa, "Higher-Order Chebyshev Rational Approximation - Method and Application to Burnup Equations," Nuclear Science And - Engineering, 182:3,297-318 - `DOI: 10.13182/NSE15-26 `_ - - """ - - @property - @abstractmethod - def alpha(self): - pass - - @property - @abstractmethod - def theta(self): - pass - - @property - @abstractmethod - def alpha0(self): - pass - - def __call__(self, A, n0, dt): - """Solve depletion equations using IPF CRAM - - Parameters - ---------- - A : scipy.sparse.csr_matrix - Sparse transmutation matrix ``A[j, i]`` desribing rates at - which isotope ``i`` transmutes to isotope ``j`` - n0 : numpy.ndarray - Initial compositions, typically given in number of atoms in some - material or an atom density - dt : float - Time [s] of the specific interval to be solved - - Returns - ------- - numpy.ndarray - Final compositions after ``dt`` - - """ - A = sp.csr_matrix(A * dt, dtype=float64) - y = asarray(n0, dtype=float64) - ident = sp.eye(A.shape[0]) - for alpha, theta in zip(self.alpha, self.theta): - y += 2*real(alpha*sla.spsolve(A - theta*ident, y)) - return y * self.alpha0 diff --git a/openmc/deplete/cram.py b/openmc/deplete/cram.py index 4fdba87bfb..f4a847907b 100644 --- a/openmc/deplete/cram.py +++ b/openmc/deplete/cram.py @@ -3,13 +3,17 @@ Implements two different forms of CRAM for use in openmc.deplete. """ +import numbers from itertools import repeat from multiprocessing import Pool import time import numpy as np +import scipy.sparse as sp +import scipy.sparse.linalg as sla -from .abc import IPFCramSolver +from openmc.checkvalue import check_type, check_length +from .abc import DepSystemSolver __all__ = [ "deplete", "timed_deplete", "CRAM16", "CRAM48", @@ -83,50 +87,51 @@ def timed_deplete(*args, **kwargs): return time.time() - start, results -class Cram16Solver(IPFCramSolver): - r"""Solver implementing the 16th order IPF CRAM +class IPFCramSolver(DepSystemSolver): + r"""Class for solving depletion systems with IPF Cram - Coefficients to :attr:`alpha`, :attr:`theta`, and :attr:`alpha0` - are from Table A.IV in [Pusa16]_. + Provides a :meth:`__call__` that utilizes an incomplete + partial factorization (IPF) for the Chebyshev Rational Approximation + Method (CRAM) + + M. Pusa, "Higher-Order Chebyshev Rational Approximation + Method and Application to Burnup Equations," Nuclear Science And + Engineering, 182:3,297-318 + `DOI: 10.13182/NSE15-26 `_ + + Parameters + ---------- + alpha : numpy.ndarray + Complex residues of poles used in the factorization. Must be a + vector with even number of items. + theta : numpy.ndarray + Complex poles. Must have an equal size as ``alpha``. + alpha0 : float + Limit of the approximation at infinity Attributes ---------- alpha : numpy.ndarray Complex residues of poles :attr:`theta` in the incomplete partial factorization. Denoted as :math:`\tilde{\alpha}` - in Algorithm 1 of [Pusa16]_ theta : numpy.ndarray Complex poles :math:`\theta` of the rational approximation alpha0 : float Limit of the approximation at infinity """ - alpha = np.array([ - +5.464930576870210e+3 - 3.797983575308356e+4j, - +9.045112476907548e+1 - 1.115537522430261e+3j, - +2.344818070467641e+2 - 4.228020157070496e+2j, - +9.453304067358312e+1 - 2.951294291446048e+2j, - +7.283792954673409e+2 - 1.205646080220011e+5j, - +3.648229059594851e+1 - 1.155509621409682e+2j, - +2.547321630156819e+1 - 2.639500283021502e+1j, - +2.394538338734709e+1 - 5.650522971778156e+0j], - dtype=np.complex128) - theta = np.array([ - +3.509103608414918 + 8.436198985884374j, - +5.948152268951177 + 3.587457362018322j, - -5.264971343442647 + 16.22022147316793j, - +1.419375897185666 + 10.92536348449672j, - +6.416177699099435 + 1.194122393370139j, - +4.993174737717997 + 5.996881713603942j, - -1.413928462488886 + 13.49772569889275j, - -10.84391707869699 + 19.27744616718165j], - dtype=np.complex128) - - alpha0 = 2.124853710495224e-16 + def __init__(self, alpha, theta, alpha0): + check_type("alpha", alpha, np.ndarray, numbers.Complex) + check_type("theta", theta, np.ndarray, numbers.Complex) + check_length("theta", theta, alpha.size) + check_type("alpha0", alpha0, numbers.Real) + self.alpha = alpha + self.theta = theta + self.alpha0 = alpha0 def __call__(self, A, n0, dt): - """Solve using 16th order IPF CRAM [Pusa16]_ + """Solve depletion equations using IPF CRAM Parameters ---------- @@ -145,114 +150,110 @@ class Cram16Solver(IPFCramSolver): Final compositions after ``dt`` """ - return super().__call__(A, n0, dt) + A = sp.csr_matrix(A * dt, dtype=np.float64) + y = np.asarray(n0, dtype=np.float64) + ident = sp.eye(A.shape[0]) + for alpha, theta in zip(self.alpha, self.theta): + y += 2*np.real(alpha*sla.spsolve(A - theta*ident, y)) + return y * self.alpha0 -class Cram48Solver(IPFCramSolver): - r"""Solver implementing the 48th order IPF CRAM +# Coefficients for IPF Cram 16 +c16_alpha = np.array([ + +5.464930576870210e+3 - 3.797983575308356e+4j, + +9.045112476907548e+1 - 1.115537522430261e+3j, + +2.344818070467641e+2 - 4.228020157070496e+2j, + +9.453304067358312e+1 - 2.951294291446048e+2j, + +7.283792954673409e+2 - 1.205646080220011e+5j, + +3.648229059594851e+1 - 1.155509621409682e+2j, + +2.547321630156819e+1 - 2.639500283021502e+1j, + +2.394538338734709e+1 - 5.650522971778156e+0j], + dtype=np.complex128) - Coefficients to :attr:`alpha`, :attr:`theta`, and :attr:`alpha0` - are from Table A.XII in [Pusa16]_. +c16_theta = np.array([ + +3.509103608414918 + 8.436198985884374j, + +5.948152268951177 + 3.587457362018322j, + -5.264971343442647 + 16.22022147316793j, + +1.419375897185666 + 10.92536348449672j, + +6.416177699099435 + 1.194122393370139j, + +4.993174737717997 + 5.996881713603942j, + -1.413928462488886 + 13.49772569889275j, + -10.84391707869699 + 19.27744616718165j], + dtype=np.complex128) - Attributes - ---------- - alpha : numpy.ndarray - Complex residues of poles :attr:`theta` in the incomplete partial - factorization. Denoted as :math:`\tilde{\alpha}` - in Algorithm 1 of [Pusa16]_ - theta : numpy.ndarray - Complex poles :math:`\theta` of the rational approximation - alpha0 : float - Limit of the approximation at infinity +c16_alpha0 = 2.124853710495224e-16 +Cram16Solver = IPFCramSolver(c16_alpha, c16_theta, c16_alpha0) +CRAM16 = Cram16Solver.__call__ - """ +del c16_alpha, c16_alpha0, c16_theta - theta_r = np.array([ - -4.465731934165702e+1, -5.284616241568964e+0, - -8.867715667624458e+0, +3.493013124279215e+0, - +1.564102508858634e+1, +1.742097597385893e+1, - -2.834466755180654e+1, +1.661569367939544e+1, - +8.011836167974721e+0, -2.056267541998229e+0, - +1.449208170441839e+1, +1.853807176907916e+1, - +9.932562704505182e+0, -2.244223871767187e+1, - +8.590014121680897e-1, -1.286192925744479e+1, - +1.164596909542055e+1, +1.806076684783089e+1, - +5.870672154659249e+0, -3.542938819659747e+1, - +1.901323489060250e+1, +1.885508331552577e+1, - -1.734689708174982e+1, +1.316284237125190e+1]) +# Coefficients for 48th order IPF Cram - theta_i = np.array([ - +6.233225190695437e+1, +4.057499381311059e+1, - +4.325515754166724e+1, +3.281615453173585e+1, - +1.558061616372237e+1, +1.076629305714420e+1, - +5.492841024648724e+1, +1.316994930024688e+1, - +2.780232111309410e+1, +3.794824788914354e+1, - +1.799988210051809e+1, +5.974332563100539e+0, - +2.532823409972962e+1, +5.179633600312162e+1, - +3.536456194294350e+1, +4.600304902833652e+1, - +2.287153304140217e+1, +8.368200580099821e+0, - +3.029700159040121e+1, +5.834381701800013e+1, - +1.194282058271408e+0, +3.583428564427879e+0, - +4.883941101108207e+1, +2.042951874827759e+1]) +theta_r = np.array([ + -4.465731934165702e+1, -5.284616241568964e+0, + -8.867715667624458e+0, +3.493013124279215e+0, + +1.564102508858634e+1, +1.742097597385893e+1, + -2.834466755180654e+1, +1.661569367939544e+1, + +8.011836167974721e+0, -2.056267541998229e+0, + +1.449208170441839e+1, +1.853807176907916e+1, + +9.932562704505182e+0, -2.244223871767187e+1, + +8.590014121680897e-1, -1.286192925744479e+1, + +1.164596909542055e+1, +1.806076684783089e+1, + +5.870672154659249e+0, -3.542938819659747e+1, + +1.901323489060250e+1, +1.885508331552577e+1, + -1.734689708174982e+1, +1.316284237125190e+1]) - theta = np.array(theta_r + theta_i * 1j, dtype=np.complex128) +theta_i = np.array([ + +6.233225190695437e+1, +4.057499381311059e+1, + +4.325515754166724e+1, +3.281615453173585e+1, + +1.558061616372237e+1, +1.076629305714420e+1, + +5.492841024648724e+1, +1.316994930024688e+1, + +2.780232111309410e+1, +3.794824788914354e+1, + +1.799988210051809e+1, +5.974332563100539e+0, + +2.532823409972962e+1, +5.179633600312162e+1, + +3.536456194294350e+1, +4.600304902833652e+1, + +2.287153304140217e+1, +8.368200580099821e+0, + +3.029700159040121e+1, +5.834381701800013e+1, + +1.194282058271408e+0, +3.583428564427879e+0, + +4.883941101108207e+1, +2.042951874827759e+1]) - alpha_r = np.array([ - +6.387380733878774e+2, +1.909896179065730e+2, - +4.236195226571914e+2, +4.645770595258726e+2, - +7.765163276752433e+2, +1.907115136768522e+3, - +2.909892685603256e+3, +1.944772206620450e+2, - +1.382799786972332e+5, +5.628442079602433e+3, - +2.151681283794220e+2, +1.324720240514420e+3, - +1.617548476343347e+4, +1.112729040439685e+2, - +1.074624783191125e+2, +8.835727765158191e+1, - +9.354078136054179e+1, +9.418142823531573e+1, - +1.040012390717851e+2, +6.861882624343235e+1, - +8.766654491283722e+1, +1.056007619389650e+2, - +7.738987569039419e+1, +1.041366366475571e+2]) +c48_theta = np.array(theta_r + theta_i * 1j, dtype=np.complex128) - alpha_i = np.array([ - -6.743912502859256e+2, -3.973203432721332e+2, - -2.041233768918671e+3, -1.652917287299683e+3, - -1.783617639907328e+4, -5.887068595142284e+4, - -9.953255345514560e+3, -1.427131226068449e+3, - -3.256885197214938e+6, -2.924284515884309e+4, - -1.121774011188224e+3, -6.370088443140973e+4, - -1.008798413156542e+6, -8.837109731680418e+1, - -1.457246116408180e+2, -6.388286188419360e+1, - -2.195424319460237e+2, -6.719055740098035e+2, - -1.693747595553868e+2, -1.177598523430493e+1, - -4.596464999363902e+3, -1.738294585524067e+3, - -4.311715386228984e+1, -2.777743732451969e+2]) +alpha_r = np.array([ + +6.387380733878774e+2, +1.909896179065730e+2, + +4.236195226571914e+2, +4.645770595258726e+2, + +7.765163276752433e+2, +1.907115136768522e+3, + +2.909892685603256e+3, +1.944772206620450e+2, + +1.382799786972332e+5, +5.628442079602433e+3, + +2.151681283794220e+2, +1.324720240514420e+3, + +1.617548476343347e+4, +1.112729040439685e+2, + +1.074624783191125e+2, +8.835727765158191e+1, + +9.354078136054179e+1, +9.418142823531573e+1, + +1.040012390717851e+2, +6.861882624343235e+1, + +8.766654491283722e+1, +1.056007619389650e+2, + +7.738987569039419e+1, +1.041366366475571e+2]) - alpha = np.array(alpha_r + alpha_i * 1j, dtype=np.complex128) +alpha_i = np.array([ + -6.743912502859256e+2, -3.973203432721332e+2, + -2.041233768918671e+3, -1.652917287299683e+3, + -1.783617639907328e+4, -5.887068595142284e+4, + -9.953255345514560e+3, -1.427131226068449e+3, + -3.256885197214938e+6, -2.924284515884309e+4, + -1.121774011188224e+3, -6.370088443140973e+4, + -1.008798413156542e+6, -8.837109731680418e+1, + -1.457246116408180e+2, -6.388286188419360e+1, + -2.195424319460237e+2, -6.719055740098035e+2, + -1.693747595553868e+2, -1.177598523430493e+1, + -4.596464999363902e+3, -1.738294585524067e+3, + -4.311715386228984e+1, -2.777743732451969e+2]) - del theta_i, theta_r, alpha_r, alpha_i +c48_alpha = np.array(alpha_r + alpha_i * 1j, dtype=np.complex128) - alpha0 = 2.258038182743983e-47 +c48_alpha0 = 2.258038182743983e-47 - def __call__(self, A, n0, dt): - """Solve using 48th order IPF CRAM [Pusa16]_ +Cram48Solver = IPFCramSolver(c48_alpha, c48_theta, c48_alpha0) - Parameters - ---------- - A : scipy.sparse.csr_matrix - Sparse transmutation matrix ``A[j, i]`` desribing rates at - which isotope ``i`` transmutes to isotope ``j`` - n0 : numpy.ndarray - Initial compositions, typically given in number of atoms in some - material or an atom density - dt : float - Time [s] of the specific interval to be solved +del c48_alpha, c48_alpha0, c48_theta, alpha_r, alpha_i, theta_r, theta_i - Returns - ------- - numpy.ndarray - Final compositions after ``dt`` +CRAM48 = Cram48Solver.__call__ - """ - return super().__call__(A, n0, dt) - - -CRAM16 = Cram16Solver().__call__ -CRAM48 = Cram48Solver().__call__ From 7a1a424d0fa5a033139513dd0a71db0701897090 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 26 Sep 2019 20:34:11 -0400 Subject: [PATCH 095/158] Tally.writeable -> Tally.writable Oops Co-Authored-By: Paul Romano --- include/openmc/capi.h | 4 ++-- include/openmc/tallies/tally.h | 2 +- openmc/deplete/abc.py | 2 +- openmc/deplete/helpers.py | 6 +++--- openmc/lib/tally.py | 26 +++++++++++++------------- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/include/openmc/capi.h b/include/openmc/capi.h index 327594c3e4..82f01d60bf 100644 --- a/include/openmc/capi.h +++ b/include/openmc/capi.h @@ -110,7 +110,7 @@ extern "C" { int openmc_tally_get_nuclides(int32_t index, int** nuclides, int* n); int openmc_tally_get_scores(int32_t index, int** scores, int* n); int openmc_tally_get_type(int32_t index, int32_t* type); - int openmc_tally_get_writeable(int32_t index, bool* writeable); + int openmc_tally_get_writable(int32_t index, bool* writable); int openmc_tally_reset(int32_t index); int openmc_tally_results(int32_t index, double** ptr, size_t shape_[3]); int openmc_tally_set_active(int32_t index, bool active); @@ -120,7 +120,7 @@ extern "C" { int openmc_tally_set_nuclides(int32_t index, int n, const char** nuclides); int openmc_tally_set_scores(int32_t index, int n, const char** scores); int openmc_tally_set_type(int32_t index, const char* type); - int openmc_tally_set_writeable(int32_t index, bool writeable); + int openmc_tally_set_writable(int32_t index, bool writable); int openmc_zernike_filter_get_order(int32_t index, int* order); int openmc_zernike_filter_get_params(int32_t index, double* x, double* y, double* r); int openmc_zernike_filter_set_order(int32_t index, int order); diff --git a/include/openmc/tallies/tally.h b/include/openmc/tallies/tally.h index 0b0baeb8c4..554d302ace 100644 --- a/include/openmc/tallies/tally.h +++ b/include/openmc/tallies/tally.h @@ -99,7 +99,7 @@ public: xt::xtensor results_; //! True if this tally should be written to statepoint files - bool writeable_ {true}; + bool writable_ {true}; //---------------------------------------------------------------------------- // Miscellaneous public members. diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 3a7eee34df..44f93c7ee2 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -539,7 +539,7 @@ class TalliedFissionYieldHelper(FissionYieldHelper): # Tally group-wise fission reaction rates self._fission_rate_tally = Tally() - self._fission_rate_tally.writeable = False + self._fission_rate_tally.writable = False self._fission_rate_tally.scores = ['fission'] self._fission_rate_tally.filters = [MaterialFilter(materials)] diff --git a/openmc/deplete/helpers.py b/openmc/deplete/helpers.py index b1e7e17ef3..d79a031116 100644 --- a/openmc/deplete/helpers.py +++ b/openmc/deplete/helpers.py @@ -59,7 +59,7 @@ class DirectReactionRateHelper(ReactionRateHelper): ``"(n, gamma)"``, needed for the reaction rate tally. """ self._rate_tally = Tally() - self._rate_tally.writeable = False + self._rate_tally.writable = False self._rate_tally.scores = scores self._rate_tally.filters = [MaterialFilter(materials)] @@ -195,7 +195,7 @@ class EnergyScoreHelper(EnergyHelper): """ self._tally = Tally() - self._tally.writeable = False + self._tally.writable = False self._tally.scores = [self.score] def reset(self): @@ -572,7 +572,7 @@ class AveragedFissionYieldHelper(TalliedFissionYieldHelper): func_filter = EnergyFunctionFilter() func_filter.set_data((0, self._upper_energy), (0, self._upper_energy)) weighted_tally = Tally() - weighted_tally.writeable = False + weighted_tally.writable = False weighted_tally.scores = ['fission'] weighted_tally.filters = filters + [func_filter] self._weighted_tally = weighted_tally diff --git a/openmc/lib/tally.py b/openmc/lib/tally.py index 906084c4b7..2e8d43ae85 100644 --- a/openmc/lib/tally.py +++ b/openmc/lib/tally.py @@ -53,9 +53,9 @@ _dll.openmc_tally_get_scores.errcheck = _error_handler _dll.openmc_tally_get_type.argtypes = [c_int32, POINTER(c_int32)] _dll.openmc_tally_get_type.restype = c_int _dll.openmc_tally_get_type.errcheck = _error_handler -_dll.openmc_tally_get_writeable.argtypes = [c_int32, POINTER(c_bool)] -_dll.openmc_tally_get_writeable.restype = c_int -_dll.openmc_tally_get_writeable.errcheck = _error_handler +_dll.openmc_tally_get_writable.argtypes = [c_int32, POINTER(c_bool)] +_dll.openmc_tally_get_writable.restype = c_int +_dll.openmc_tally_get_writable.errcheck = _error_handler _dll.openmc_tally_reset.argtypes = [c_int32] _dll.openmc_tally_reset.restype = c_int _dll.openmc_tally_reset.errcheck = _error_handler @@ -84,9 +84,9 @@ _dll.openmc_tally_set_scores.errcheck = _error_handler _dll.openmc_tally_set_type.argtypes = [c_int32, c_char_p] _dll.openmc_tally_set_type.restype = c_int _dll.openmc_tally_set_type.errcheck = _error_handler -_dll.openmc_tally_set_writeable.argtypes = [c_int32, c_bool] -_dll.openmc_tally_set_writeable.restype = c_int -_dll.openmc_tally_set_writeable.errcheck = _error_handler +_dll.openmc_tally_set_writable.argtypes = [c_int32, c_bool] +_dll.openmc_tally_set_writable.restype = c_int +_dll.openmc_tally_set_writable.errcheck = _error_handler _dll.tallies_size.restype = c_size_t @@ -351,14 +351,14 @@ class Tally(_FortranObjectWithID): return std_dev @property - def writeable(self): - writeable = c_bool() - _dll.openmc_tally_get_writeable(self._index, writeable) - return writeable.value + def writable(self): + writable = c_bool() + _dll.openmc_tally_get_writable(self._index, writable) + return writable.value - @writeable.setter - def writeable(self, writeable): - _dll.openmc_tally_set_writeable(self._index, writeable) + @writable.setter + def writable(self, writable): + _dll.openmc_tally_set_writable(self._index, writable) def reset(self): """Reset results and num_realizations of tally""" From 801f18b3573a4eadb18618751d44dfafa577d9a4 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 26 Sep 2019 20:56:31 -0400 Subject: [PATCH 096/158] Provide Tally writable_ setter and getters New member functions openmc::Tally.set_writable and get_writable act on the writable attribute. These are used in the external API with openmc_tally_set_writable and openmc_tally_get_writable functions. Cleaned up some other writeable -> writable typos as well --- include/openmc/tallies/tally.h | 4 ++++ src/output.cpp | 2 +- src/state_point.cpp | 12 +++++------- src/tallies/tally.cpp | 8 ++++---- tests/unit_tests/test_lib.py | 12 ++++++------ 5 files changed, 20 insertions(+), 18 deletions(-) diff --git a/include/openmc/tallies/tally.h b/include/openmc/tallies/tally.h index 554d302ace..f411e65603 100644 --- a/include/openmc/tallies/tally.h +++ b/include/openmc/tallies/tally.h @@ -37,6 +37,8 @@ public: void set_active(bool active) { active_ = active; } + void set_writable(bool writable) { writable_ = writable; } + void set_scores(pugi::xml_node node); void set_scores(const std::vector& scores); @@ -55,6 +57,8 @@ public: int32_t n_filter_bins() const {return n_filter_bins_;} + bool get_writable() const { return writable_;} + //---------------------------------------------------------------------------- // Other methods. diff --git a/src/output.cpp b/src/output.cpp index 439fc1558f..d3262cc4d5 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -643,7 +643,7 @@ write_tallies() if (!tally.name_.empty()) tally_header += ": " + tally.name_; tallies_out << header(tally_header) << "\n\n"; - if (!tally.writeable_) { + if (!tally.writable_) { tallies_out << " Internal\n\n"; continue; } diff --git a/src/state_point.cpp b/src/state_point.cpp index a550748a54..cd2c475d86 100644 --- a/src/state_point.cpp +++ b/src/state_point.cpp @@ -171,7 +171,7 @@ openmc_statepoint_write(const char* filename, bool* write_source) write_dataset(tally_group, "name", tally->name_); - if (tally->writeable_) { + if (tally->writable_) { write_attribute(tally_group, "internal", 0); } else { write_attribute(tally_group, "internal", 1); @@ -239,7 +239,7 @@ openmc_statepoint_write(const char* filename, bool* write_source) // Write all tally results for (const auto& tally : model::tallies) { - if (!tally->writeable_) continue; + if (!tally->writable_) continue; // Write sum and sum_sq for each bin std::string name = "tally " + std::to_string(tally->id_); hid_t tally_group = open_group(tallies_group, name.c_str()); @@ -434,14 +434,12 @@ void load_state_point() std::string name = "tally " + std::to_string(tally->id_); hid_t tally_group = open_group(tallies_group, name.c_str()); - int internal; + int internal=0; if (attribute_exists(tally_group, "internal")) { read_attribute(tally_group, "internal", internal); - } else { - internal = 0; } if (internal) { - tally->writeable_ = false; + tally->writable_ = false; } else { auto& results = tally->results_; @@ -717,7 +715,7 @@ void write_tally_results_nr(hid_t file_id) for (const auto& t : model::tallies) { // Skip any tallies that are not active if (!t->active_) continue; - if (!t->writeable_) continue; + if (!t->writable_) continue; if (mpi::master && !object_exists(file_id, "tallies_present")) { write_attribute(file_id, "tallies_present", 1); diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 96253285e2..3e9de83b19 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -1221,25 +1221,25 @@ openmc_tally_set_active(int32_t index, bool active) } extern "C" int -openmc_tally_get_writeable(int32_t index, bool* writeable) +openmc_tally_get_writable(int32_t index, bool* writable) { if (index < 0 || index >= model::tallies.size()) { set_errmsg("Index in tallies array is out of bounds."); return OPENMC_E_OUT_OF_BOUNDS; } - *writeable = model::tallies[index]->writeable_; + *writable = model::tallies[index]->get_writable(); return 0; } extern "C" int -openmc_tally_set_writeable(int32_t index, bool writeable) +openmc_tally_set_writable(int32_t index, bool writable) { if (index < 0 || index >= model::tallies.size()) { set_errmsg("Index in tallies array is out of bounds."); return OPENMC_E_OUT_OF_BOUNDS; } - model::tallies[index]->writeable_ = writeable; + model::tallies[index]->set_writable(writable); return 0; } diff --git a/tests/unit_tests/test_lib.py b/tests/unit_tests/test_lib.py index 7f992cb096..ad198f255c 100644 --- a/tests/unit_tests/test_lib.py +++ b/tests/unit_tests/test_lib.py @@ -256,13 +256,13 @@ def test_tally_activate(lib_simulation_init): assert t.active -def test_tally_writeable(lib_simulation_init): +def test_tally_writable(lib_simulation_init): t = openmc.lib.tallies[1] - assert t.writeable - t.writeable = False - assert not t.writeable - # Revert tally to writeable state for lib_run fixtures - t.writeable = True + assert t.writable + t.writable = False + assert not t.writable + # Revert tally to writable state for lib_run fixtures + t.writable = True def test_tally_results(lib_run): From 1b8bc2d01d3038fbf6d0eb1dd17ad331b587af82 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Fri, 27 Sep 2019 10:43:36 -0500 Subject: [PATCH 097/158] Correcting default values in set_id methods. Initializing material ids to match the initialization logic. --- include/openmc/material.h | 4 ++-- include/openmc/tallies/filter.h | 2 +- src/material.cpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/openmc/material.h b/include/openmc/material.h index 652f3db8e1..8771b372a4 100644 --- a/include/openmc/material.h +++ b/include/openmc/material.h @@ -120,7 +120,7 @@ public: //! Assign a unique ID to the material //! \param[in] Unique ID to assign. A value of -1 indicates that an ID //! should be automatically assigned. - void set_id(int32_t id); + void set_id(int32_t id = -1); //! Get whether material is fissionable //! \return Whether material is fissionable @@ -132,7 +132,7 @@ public: //---------------------------------------------------------------------------- // Data - int32_t id_; //!< Unique ID + int32_t id_ {-1}; //!< Unique ID std::string name_; //!< Name of material std::vector nuclide_; //!< Indices in nuclides vector std::vector element_; //!< Indices in elements vector diff --git a/include/openmc/tallies/filter.h b/include/openmc/tallies/filter.h index ba6ca08830..b25110e07a 100644 --- a/include/openmc/tallies/filter.h +++ b/include/openmc/tallies/filter.h @@ -104,7 +104,7 @@ public: //! Assign a unique ID to the filter //! \param[in] Unique ID to assign. A value of -1 indicates that an ID should //! be automatically assigned - void set_id(int32_t id); + void set_id(int32_t id = -1); //! Get number of bins //! \return Number of bins diff --git a/src/material.cpp b/src/material.cpp index 685fd1da2e..9a1e206ac8 100644 --- a/src/material.cpp +++ b/src/material.cpp @@ -873,8 +873,8 @@ void Material::set_id(int32_t id) // If no ID specified, auto-assign next ID in sequence if (id == -1) { id = 0; - for (const auto& f : model::materials) { - id = std::max(id, f->id_); + for (const auto& m : model::materials) { + id = std::max(id, m->id_); } ++id; } From e7b28ab8d99c43aeb3419c52eeada0bf83cdd4a6 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Mon, 30 Sep 2019 14:31:26 -0500 Subject: [PATCH 098/158] Removing default values for set_id args as requested by @paulromano. --- include/openmc/material.h | 2 +- include/openmc/tallies/filter.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/openmc/material.h b/include/openmc/material.h index 8771b372a4..70c6b38522 100644 --- a/include/openmc/material.h +++ b/include/openmc/material.h @@ -120,7 +120,7 @@ public: //! Assign a unique ID to the material //! \param[in] Unique ID to assign. A value of -1 indicates that an ID //! should be automatically assigned. - void set_id(int32_t id = -1); + void set_id(int32_t id); //! Get whether material is fissionable //! \return Whether material is fissionable diff --git a/include/openmc/tallies/filter.h b/include/openmc/tallies/filter.h index b25110e07a..ba6ca08830 100644 --- a/include/openmc/tallies/filter.h +++ b/include/openmc/tallies/filter.h @@ -104,7 +104,7 @@ public: //! Assign a unique ID to the filter //! \param[in] Unique ID to assign. A value of -1 indicates that an ID should //! be automatically assigned - void set_id(int32_t id = -1); + void set_id(int32_t id); //! Get number of bins //! \return Number of bins From 484bf70c86a3f46280fd0bed5a95d8180148d808 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Mon, 30 Sep 2019 18:55:42 -0400 Subject: [PATCH 099/158] Tally.get_writable() -> Tally.writable() Co-Authored-By: Paul Romano --- include/openmc/tallies/tally.h | 2 +- src/tallies/tally.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/openmc/tallies/tally.h b/include/openmc/tallies/tally.h index f411e65603..dc7cd8ce27 100644 --- a/include/openmc/tallies/tally.h +++ b/include/openmc/tallies/tally.h @@ -57,7 +57,7 @@ public: int32_t n_filter_bins() const {return n_filter_bins_;} - bool get_writable() const { return writable_;} + bool writable() const { return writable_;} //---------------------------------------------------------------------------- // Other methods. diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 3e9de83b19..f843874118 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -1227,7 +1227,7 @@ openmc_tally_get_writable(int32_t index, bool* writable) set_errmsg("Index in tallies array is out of bounds."); return OPENMC_E_OUT_OF_BOUNDS; } - *writable = model::tallies[index]->get_writable(); + *writable = model::tallies[index]->writable(); return 0; } From 9a78836d2238fe11fa637447f236ff45b6fc0e04 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 1 Oct 2019 10:31:29 -0500 Subject: [PATCH 100/158] Make sure Shannon entropy mesh without dimension can be assigned --- src/mesh.cpp | 3 ++- src/settings.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/mesh.cpp b/src/mesh.cpp index ac02b679e8..acd80c7ba1 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -155,7 +155,8 @@ RegularMesh::RegularMesh(pugi::xml_node node) fatal_error("Must specify either and on a mesh."); } - if (shape_.dimension() > 0) { + // TODO: Change to zero when xtensor is updated + if (shape_.size() > 1) { if (shape_.size() != lower_left_.size()) { fatal_error("Number of entries on must be the same " "as the number of entries on ."); diff --git a/src/settings.cpp b/src/settings.cpp index 1c9d1161a0..796a2862ad 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -512,7 +512,8 @@ void read_settings_xml() if (!m) fatal_error("Only regular meshes can be used as an entropy mesh"); simulation::entropy_mesh = m; - if (m->shape_.dimension() == 0) { + // TODO: Change to zero when xtensor is updated + if (m->shape_.size() == 1) { // If the user did not specify how many mesh cells are to be used in // each direction, we automatically determine an appropriate number of // cells From 4b19dc9ffce638276a546ef17d51442a96377d6b Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 1 Oct 2019 11:41:18 -0400 Subject: [PATCH 101/158] Add TODOs to rotate_angle for a change in azimuth --- src/distribution_multi.cpp | 10 ++++++++-- src/math_functions.cpp | 6 ++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/distribution_multi.cpp b/src/distribution_multi.cpp index 0c1ecddecb..c388254b6e 100644 --- a/src/distribution_multi.cpp +++ b/src/distribution_multi.cpp @@ -59,8 +59,14 @@ Direction PolarAzimuthal::sample() const double mu = mu_->sample(); if (mu == 1.0) return u_ref_; - // Sample the azimuthal angle with an offset to match azimuth conventions - double phi = phi_->sample() + 0.5*PI; + // Sample azimuthal angle + double phi = phi_->sample(); + + // If the reference direction is along the z-axis, rotate the aziumthal angle + // to match spherical coordinate conventions. + // TODO: apply this change directly to rotate_angle + if (u_ref_.x == 0 && u_ref_.y == 0) phi += 0.5*PI; + return rotate_angle(u_ref_, mu, &phi); } diff --git a/src/math_functions.cpp b/src/math_functions.cpp index ec5d64eac7..7525a7ddda 100644 --- a/src/math_functions.cpp +++ b/src/math_functions.cpp @@ -665,6 +665,12 @@ Direction rotate_angle(Direction u, double mu, const double* phi) return {mu*u.x + a*(u.x*u.y*cosphi + u.z*sinphi) / b, mu*u.y - a*b*cosphi, mu*u.z + a*(u.y*u.z*cosphi - u.x*sinphi) / b}; + // TODO: use the following code to make PolarAzimuthal distributions match + // spherical coordinate conventions. Remove the related fixup code in + // PolarAzimuthal::sample. + //return {mu*u.x + a*(-u.x*u.y*sinphi + u.z*cosphi) / b, + // mu*u.y + a*b*sinphi, + // mu*u.z - a*(u.y*u.z*sinphi + u.x*cosphi) / b}; } } From e510064b9cbf58bbf4e630d9a61f151a67043d62 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 2 Oct 2019 21:09:18 -0500 Subject: [PATCH 102/158] Make sure heating is calculated correctly with threshold fission When trying to process Pa233 from ENDF/B-VIII.0, which has a threshold fission reaction, I discovered that calculating heating numbers crashed because we were trying to do arithmetic on arrays of different sizes. Re-evaluting the fission cross section on the proper energy grid does the trick here. --- openmc/data/neutron.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openmc/data/neutron.py b/openmc/data/neutron.py index 8a6c889817..ea14be7844 100644 --- a/openmc/data/neutron.py +++ b/openmc/data/neutron.py @@ -874,7 +874,7 @@ class IncidentNeutron(EqualityMixin): fission = data.reactions[18].xs[temp] kerma_fission = get_file3_xs(ev, 318, E) kerma.y = kerma.y - kerma_fission + ( - f.fragments(E) + f.betas(E)) * fission.y + f.fragments(E) + f.betas(E)) * fission(E) # For local KERMA, we first need to get the values from the # HEATR run with photon energy deposited locally and put @@ -887,7 +887,7 @@ class IncidentNeutron(EqualityMixin): kerma_fission_local = get_file3_xs(ev_local, 318, E) kerma_local = kerma_local - kerma_fission_local + ( f.fragments(E) + f.prompt_photons(E) - + f.delayed_photons(E) + f.betas(E))*fission.y + + f.delayed_photons(E) + f.betas(E))*fission(E) heating_local.xs[temp] = Tabulated1D(E, kerma_local) From 92d410ed776df1f3758c17a8bc6663fd42ae0958 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 2 Oct 2019 21:29:42 -0500 Subject: [PATCH 103/158] Ensure all thermal scattering evals from ENDF/B-VIII.0 and JEFF 3.3 work --- openmc/data/njoy.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/openmc/data/njoy.py b/openmc/data/njoy.py index 4983098f73..83fe991c76 100644 --- a/openmc/data/njoy.py +++ b/openmc/data/njoy.py @@ -25,13 +25,16 @@ _THERMAL_DATA = { 13: ThermalTuple('orthod', [1002], 1), 26: ThermalTuple('be', [4009], 1), 27: ThermalTuple('bebeo', [4009], 1), - 31: ThermalTuple('graph', [6000, 6012, 6013], 1), + 30: ThermalTuple('graph', [6000, 6012, 6013], 1), + 31: ThermalTuple('grph10', [6000, 6012, 6013], 1), + 32: ThermalTuple('grph30', [6000, 6012, 6013], 1), 33: ThermalTuple('lch4', [1001], 1), 34: ThermalTuple('sch4', [1001], 1), 37: ThermalTuple('hch2', [1001], 1), + 38: ThermalTuple('mesi00', [1001], 1), 39: ThermalTuple('lucite', [1001], 1), 40: ThermalTuple('benz', [1001, 6000, 6012], 2), - 41: ThermalTuple('od2o', [8016, 8017, 8018], 1), + 42: ThermalTuple('tol00', [1001], 1), 43: ThermalTuple('sisic', [14028, 14029, 14030], 1), 44: ThermalTuple('csic', [6000, 6012, 6013], 1), 46: ThermalTuple('obeo', [8016, 8017, 8018], 1), @@ -39,12 +42,16 @@ _THERMAL_DATA = { 48: ThermalTuple('uuo2', [92238], 1), 49: ThermalTuple('sio2-b', [8016, 8017, 8018, 14028, 14029, 14030], 3), 50: ThermalTuple('oice', [8016, 8017, 8018], 1), + 51: ThermalTuple('od2o', [8016, 8017, 8018], 1), 52: ThermalTuple('mg24', [12024], 1), 53: ThermalTuple('al27', [13027], 1), 55: ThermalTuple('yyh2', [39089], 1), 56: ThermalTuple('fe56', [26056], 1), 58: ThermalTuple('zrzrh', [40000, 40090, 40091, 40092, 40094, 40096], 1), 59: ThermalTuple('cacah2', [20040, 20042, 20043, 20044, 20046, 20048], 1), + 60: ThermalTuple('asap00', [13027], 1), + 71: ThermalTuple('n-un', [7014, 7015], 1), + 72: ThermalTuple('u-un', [92238], 1), 75: ThermalTuple('ouo2', [8016, 8017, 8018], 1), } @@ -444,7 +451,21 @@ def make_ace_thermal(filename, filename_thermal, temperatures=None, mat_thermal = ev_thermal.material zsymam_thermal = ev_thermal.target['zsymam'] + # Determine name, isotopes based on MAT number data = _THERMAL_DATA[mat_thermal] + if ev_thermal.info['library'][0] == 'JEFF': + # JEFF uses MAT=48 for O in Sapphire even though ENDF already uses + # that MAT number for U in UO2. It also assigns MAT=49 (which is + # supposed to be Ca in CaH2) to silicon. + if ev_thermal.material == 48: + data = ThermalTuple('osap00', [8016, 8017, 8018], 1) + elif ev_thermal.gnd_name == 'Si28': + data = ThermalTuple('si00', [14028], 1) + elif ev_thermal.info['library'] != ('ENDF/B', 8, 0): + # Before ENDF/B-VIII.0, crystalline graphite was MAT=31 + if ev_thermal.material == 31: + data = ThermalTuple('graph', [6000, 6012, 6013], 1) + zaids = ' '.join(str(zaid) for zaid in data.zaids[:3]) # Determine name of library From 829f12b1b52daf0218b9b929710301a0b9b89dee Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 3 Oct 2019 11:49:56 -0500 Subject: [PATCH 104/158] Move thermal scattering MAT number bug fixes into a dedicated function --- openmc/data/njoy.py | 62 ++++++++++++++++++++++++++++++------------ openmc/data/thermal.py | 3 ++ 2 files changed, 47 insertions(+), 18 deletions(-) diff --git a/openmc/data/njoy.py b/openmc/data/njoy.py index 83fe991c76..0c853102f3 100644 --- a/openmc/data/njoy.py +++ b/openmc/data/njoy.py @@ -10,7 +10,7 @@ from . import endf # For a given MAT number, give a name for the ACE table and a list of ZAID -# identifiers +# identifiers. This is based on Appendix C in the ENDF manual. ThermalTuple = namedtuple('ThermalTuple', ['name', 'zaids', 'nmix']) _THERMAL_DATA = { 1: ThermalTuple('hh2o', [1001], 1), @@ -23,13 +23,16 @@ _THERMAL_DATA = { 11: ThermalTuple('dd2o', [1002], 1), 12: ThermalTuple('parad', [1002], 1), 13: ThermalTuple('orthod', [1002], 1), + 14: ThermalTuple('dice', [1002], 1), 26: ThermalTuple('be', [4009], 1), 27: ThermalTuple('bebeo', [4009], 1), + 28: ThermalTuple('bebe2c', [4009], 1), 30: ThermalTuple('graph', [6000, 6012, 6013], 1), 31: ThermalTuple('grph10', [6000, 6012, 6013], 1), 32: ThermalTuple('grph30', [6000, 6012, 6013], 1), 33: ThermalTuple('lch4', [1001], 1), 34: ThermalTuple('sch4', [1001], 1), + 35: ThermalTuple('sch4p2', [1001], 1), 37: ThermalTuple('hch2', [1001], 1), 38: ThermalTuple('mesi00', [1001], 1), 39: ThermalTuple('lucite', [1001], 1), @@ -37,9 +40,10 @@ _THERMAL_DATA = { 42: ThermalTuple('tol00', [1001], 1), 43: ThermalTuple('sisic', [14028, 14029, 14030], 1), 44: ThermalTuple('csic', [6000, 6012, 6013], 1), + 45: ThermalTuple('ouo2', [8016, 8017, 8018], 1), 46: ThermalTuple('obeo', [8016, 8017, 8018], 1), 47: ThermalTuple('sio2-a', [8016, 8017, 8018, 14028, 14029, 14030], 3), - 48: ThermalTuple('uuo2', [92238], 1), + 48: ThermalTuple('osap00', [92238], 1), 49: ThermalTuple('sio2-b', [8016, 8017, 8018, 14028, 14029, 14030], 3), 50: ThermalTuple('oice', [8016, 8017, 8018], 1), 51: ThermalTuple('od2o', [8016, 8017, 8018], 1), @@ -48,13 +52,48 @@ _THERMAL_DATA = { 55: ThermalTuple('yyh2', [39089], 1), 56: ThermalTuple('fe56', [26056], 1), 58: ThermalTuple('zrzrh', [40000, 40090, 40091, 40092, 40094, 40096], 1), - 59: ThermalTuple('cacah2', [20040, 20042, 20043, 20044, 20046, 20048], 1), + 59: ThermalTuple('si00', [14028], 1), 60: ThermalTuple('asap00', [13027], 1), 71: ThermalTuple('n-un', [7014, 7015], 1), 72: ThermalTuple('u-un', [92238], 1), - 75: ThermalTuple('ouo2', [8016, 8017, 8018], 1), + 75: ThermalTuple('uuo2', [8016, 8017, 8018], 1), } + +def _get_thermal_data(ev, mat): + """Return appropriate ThermalTuple, accounting for bugs.""" + + # JEFF assigns MAT=59 to Ca in CaH2 (which is supposed to be silicon). + if ev.info['library'][0] == 'JEFF': + if ev.material == 59: + if 'CaH2' in ''.join(ev.info['description']): + zaids = [20040, 20042, 20043, 20044, 20046, 20048] + return ThermalTuple('cacah2', zaids, 1) + + # Before ENDF/B-VIII.0, crystalline graphite was MAT=31 + if ev.info['library'] != ('ENDF/B', 8, 0): + if ev.material == 31: + return _THERMAL_DATA[30] + + # ENDF/B incorrectly assigns MAT numbers for UO2 + # + # Material | ENDF Manual | VII.0 | VII.1 | VIII.0 + # ---------|-------------|-------|-------|------- + # O in UO2 | 45 | 75 | 75 | 75 + # U in UO2 | 75 | 76 | 48 | 48 + if ev.info['library'][0] == 'ENDF/B': + if ev.material == 75: + return _THERMAL_DATA[45] + version = ev.info['library'][1:] + if version in ((7, 1), (8, 0)) and ev.material == 48: + return _THERMAL_DATA[75] + if version == (7, 0) and ev.material == 76: + return _THERMAL_DATA[75] + + # If not a problematic material, use the dictionary as is + return _THERMAL_DATA[mat] + + _TEMPLATE_RECONR = """ reconr / %%%%%%%%%%%%%%%%%%% Reconstruct XS for neutrons %%%%%%%%%%%%%%%%%%%%%%% {nendf} {npendf} @@ -452,20 +491,7 @@ def make_ace_thermal(filename, filename_thermal, temperatures=None, zsymam_thermal = ev_thermal.target['zsymam'] # Determine name, isotopes based on MAT number - data = _THERMAL_DATA[mat_thermal] - if ev_thermal.info['library'][0] == 'JEFF': - # JEFF uses MAT=48 for O in Sapphire even though ENDF already uses - # that MAT number for U in UO2. It also assigns MAT=49 (which is - # supposed to be Ca in CaH2) to silicon. - if ev_thermal.material == 48: - data = ThermalTuple('osap00', [8016, 8017, 8018], 1) - elif ev_thermal.gnd_name == 'Si28': - data = ThermalTuple('si00', [14028], 1) - elif ev_thermal.info['library'] != ('ENDF/B', 8, 0): - # Before ENDF/B-VIII.0, crystalline graphite was MAT=31 - if ev_thermal.material == 31: - data = ThermalTuple('graph', [6000, 6012, 6013], 1) - + data = _get_thermal_data(ev_thermal, mat_thermal) zaids = ' '.join(str(zaid) for zaid in data.zaids[:3]) # Determine name of library diff --git a/openmc/data/thermal.py b/openmc/data/thermal.py index 4fbe963631..067c0a7034 100644 --- a/openmc/data/thermal.py +++ b/openmc/data/thermal.py @@ -33,10 +33,12 @@ _THERMAL_NAMES = { 'c_Be': ('be', 'be-metal', 'be-met', 'be00'), 'c_BeO': ('beo',), 'c_Be_in_BeO': ('bebeo', 'be-beo', 'be-o', 'be/o', 'bbeo00'), + 'c_Be_in_Be2C': ('bebe2c',), 'c_C6H6': ('benz', 'c6h6'), 'c_C_in_SiC': ('csic', 'c-sic'), 'c_Ca_in_CaH2': ('cah', 'cah00'), 'c_D_in_D2O': ('dd2o', 'd-d2o', 'hwtr', 'hw', 'dhw00'), + 'c_D_in_D2O_ice': ('dice',), 'c_Fe56': ('fe', 'fe56', 'fe-56'), 'c_Graphite': ('graph', 'grph', 'gr', 'gr00'), 'c_Graphite_10p': ('grph10',), @@ -45,6 +47,7 @@ _THERMAL_NAMES = { 'c_H_in_CH2': ('hch2', 'poly', 'pol', 'h-poly', 'pol00'), 'c_H_in_CH4_liquid': ('lch4', 'lmeth'), 'c_H_in_CH4_solid': ('sch4', 'smeth'), + 'c_H_in_CH4_solid_phase_II': ('sch4p2',), 'c_H_in_H2O': ('hh2o', 'h-h2o', 'lwtr', 'lw', 'lw00'), 'c_H_in_H2O_solid': ('hice', 'h-ice', 'ice00'), 'c_H_in_C5O2H8': ('lucite', 'c5o2h8', 'h-luci'), From c18f8457123ffea9f482966844a3912c0f2287a7 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 3 Oct 2019 13:17:15 -0500 Subject: [PATCH 105/158] Fix broken links in documentation --- docs/source/devguide/docbuild.rst | 13 +++++---- docs/source/devguide/docker.rst | 3 +- docs/source/devguide/styleguide.rst | 6 ++-- docs/source/devguide/workflow.rst | 2 +- docs/source/index.rst | 12 ++++---- docs/source/io_formats/settings.rst | 2 +- docs/source/methods/cmfd.rst | 2 +- docs/source/methods/cross_sections.rst | 4 +-- docs/source/methods/geometry.rst | 4 +-- docs/source/methods/introduction.rst | 8 +++--- docs/source/methods/neutron_physics.rst | 18 ++++++------ docs/source/methods/parallelization.rst | 24 ++++++++-------- docs/source/methods/random_numbers.rst | 4 +-- docs/source/methods/tallies.rst | 26 ++++++++--------- docs/source/publications.rst | 4 +-- docs/source/pythonapi/deplete.rst | 10 +++---- docs/source/pythonapi/index.rst | 2 +- docs/source/releasenotes/0.11.0.rst | 5 +++- docs/source/releasenotes/0.4.1.rst | 12 ++++---- docs/source/releasenotes/0.4.2.rst | 20 ++++++------- docs/source/releasenotes/0.4.3.rst | 18 ++++++------ docs/source/releasenotes/0.4.4.rst | 10 +++---- docs/source/releasenotes/0.5.0.rst | 16 +++++------ docs/source/releasenotes/0.5.1.rst | 10 +++---- docs/source/releasenotes/0.5.2.rst | 24 ++++++++-------- docs/source/releasenotes/0.5.3.rst | 10 +++---- docs/source/releasenotes/0.5.4.rst | 14 ++++----- docs/source/releasenotes/0.6.0.rst | 14 ++++----- docs/source/releasenotes/0.6.1.rst | 20 ++++++------- docs/source/releasenotes/0.6.2.rst | 8 +++--- docs/source/releasenotes/0.7.0.rst | 10 +++---- docs/source/releasenotes/0.7.1.rst | 18 ++++++------ docs/source/releasenotes/0.8.0.rst | 22 +++++++------- docs/source/releasenotes/0.9.0.rst | 38 ++++++++++++------------- docs/source/usersguide/beginners.rst | 18 ++++++------ docs/source/usersguide/geometry.rst | 4 +-- docs/source/usersguide/install.rst | 9 +++--- docs/source/usersguide/plots.rst | 2 +- openmc/mgxs/__init__.py | 2 +- openmc/plots.py | 2 +- 40 files changed, 228 insertions(+), 222 deletions(-) diff --git a/docs/source/devguide/docbuild.rst b/docs/source/devguide/docbuild.rst index 5cbbb65bb8..38ef628df5 100644 --- a/docs/source/devguide/docbuild.rst +++ b/docs/source/devguide/docbuild.rst @@ -5,27 +5,28 @@ Building Sphinx Documentation ============================= In order to build the documentation in the ``docs`` directory, you will need to -have the `Sphinx `_ third-party Python +have the `Sphinx `_ third-party Python package. The easiest way to install Sphinx is via pip: .. code-block:: sh - sudo pip install sphinx + pip install sphinx -Additionally, you will also need a Sphinx extension for numbering figures. The -`Numfig `_ package can be installed +Additionally, you will need several Sphinx extensions that can be installed directly with pip: .. code-block:: sh - sudo pip install sphinx-numfig + pip install sphinx-numfig + pip install sphinxcontrib-katex + pip install sphinxcontrib-svg2pdfconverter ----------------------------------- Building Documentation as a Webpage ----------------------------------- To build the documentation as a webpage (what appears at -http://openmc.readthedocs.io), simply go to the ``docs`` directory and run: +https://docs.openmc.org), simply go to the ``docs`` directory and run: .. code-block:: sh diff --git a/docs/source/devguide/docker.rst b/docs/source/devguide/docker.rst index 12c095c455..0b21911686 100644 --- a/docs/source/devguide/docker.rst +++ b/docs/source/devguide/docker.rst @@ -19,7 +19,7 @@ build a Docker image with OpenMC installed. The image includes OpenMC with MPICH and parallel HDF5 in the ``/opt/openmc`` directory, and `Miniconda3 `_ with all of the Python pre-requisites (NumPy, SciPy, Pandas, etc.) installed. The -`NJOY2016 `_ codebase is installed in +`NJOY2016 `_ codebase is installed in ``/opt/NJOY2016`` to support full functionality and testing of the ``openmc.data`` Python module. The publicly available nuclear data libraries necessary to run OpenMC's test suite -- including NNDC and WMP cross sections @@ -54,4 +54,3 @@ Docker container where you have access to use OpenMC. .. _Docker container: https://www.docker.com/resources/what-container .. _options: https://docs.docker.com/engine/reference/commandline/run/ .. _mounting volumes: https://docs.docker.com/storage/volumes/ - diff --git a/docs/source/devguide/styleguide.rst b/docs/source/devguide/styleguide.rst index cbf665cc8e..562b75e897 100644 --- a/docs/source/devguide/styleguide.rst +++ b/docs/source/devguide/styleguide.rst @@ -207,8 +207,8 @@ Documentation ------------- Classes, structs, and functions are to be annotated for the `Doxygen -`_ documentation generation tool. Use the -``\`` form of Doxygen commands, e.g., ``\brief`` instead of ``@brief``. +`_ documentation generation tool. Use the ``\`` form of +Doxygen commands, e.g., ``\brief`` instead of ``@brief``. ------ Python @@ -231,7 +231,7 @@ represent a filesystem path should work with both strings and Path_ objects. .. _C++ Core Guidelines: http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines .. _PEP8: https://www.python.org/dev/peps/pep-0008/ .. _numpydoc: https://numpydoc.readthedocs.io/en/latest/format.html -.. _numpy: http://www.numpy.org/ +.. _numpy: https://numpy.org/ .. _scipy: https://www.scipy.org/ .. _matplotlib: https://matplotlib.org/ .. _pandas: https://pandas.pydata.org/ diff --git a/docs/source/devguide/workflow.rst b/docs/source/devguide/workflow.rst index 31dc088428..037b8b68af 100644 --- a/docs/source/devguide/workflow.rst +++ b/docs/source/devguide/workflow.rst @@ -124,7 +124,7 @@ can interfere with virtual environments. .. _GitHub: https://github.com/ .. _git flow: http://nvie.com/git-model .. _valgrind: http://valgrind.org/ -.. _style guide: http://openmc.readthedocs.io/en/latest/devguide/styleguide.html +.. _style guide: https://docs.openmc.org/en/latest/devguide/styleguide.html .. _pull request: https://help.github.com/articles/using-pull-requests .. _openmc-dev/openmc: https://github.com/openmc-dev/openmc .. _paid plan: https://github.com/plans diff --git a/docs/source/index.rst b/docs/source/index.rst index 2ce5072d01..198a9429ee 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -2,12 +2,14 @@ The OpenMC Monte Carlo Code =========================== -OpenMC is a Monte Carlo particle transport simulation code focused on neutron -criticality calculations. It is capable of simulating 3D models based on -constructive solid geometry with second-order surfaces. OpenMC supports either -continuous-energy or multi-group transport. The continuous-energy particle +OpenMC is a community-developed Monte Carlo neutron and photon transport +simulation code. It is capable of performing fixed source, k-eigenvalue, and +subcritical multiplication calculations on models built using either a +constructive solid geometry or CAD representation. OpenMC supports both +continuous-energy and multigroup transport. The continuous-energy particle interaction data is based on a native HDF5 format that can be generated from ACE -files used by the MCNP and Serpent Monte Carlo codes. +files produced by NJOY. Parallelism is enabled via a hybrid MPI and OpenMP +programming model. OpenMC was originally developed by members of the `Computational Reactor Physics Group `_ at the `Massachusetts Institute of Technology diff --git a/docs/source/io_formats/settings.rst b/docs/source/io_formats/settings.rst index 9c68a9730b..fb55f9e513 100644 --- a/docs/source/io_formats/settings.rst +++ b/docs/source/io_formats/settings.rst @@ -333,7 +333,7 @@ or sub-elements: velocity sampling) or "dbrc" (Doppler broadening rejection correction). Descriptions of each of these methods are documented here_. - .. _here: http://dx.doi.org/10.1016/j.anucene.2017.12.044 + .. _here: https://doi.org/10.1016/j.anucene.2017.12.044 *Default*: "rvs" diff --git a/docs/source/methods/cmfd.rst b/docs/source/methods/cmfd.rst index b13004e100..344a4cc1a6 100644 --- a/docs/source/methods/cmfd.rst +++ b/docs/source/methods/cmfd.rst @@ -538,7 +538,7 @@ Examples of CMFD simulations using OpenMC can be found in [HermanThesis]_. .. rubric:: References .. [BEAVRS] Nick Horelik, Bryan Herman. *Benchmark for Evaluation And Verification of Reactor - Simulations*. Massachusetts Institute of Technology, http://crpg.mit.edu/pub/beavrs + Simulations*. Massachusetts Institute of Technology, https://crpg.mit.edu/research/beavrs , 2013. .. [Gill] Daniel F. Gill. *Newton-Krylov methods for the solution of the k-eigenvalue problem in diff --git a/docs/source/methods/cross_sections.rst b/docs/source/methods/cross_sections.rst index 4940275d3a..dbf078654f 100644 --- a/docs/source/methods/cross_sections.rst +++ b/docs/source/methods/cross_sections.rst @@ -269,11 +269,11 @@ or even isotropic scattering. .. _logarithmic mapping technique: https://laws.lanl.gov/vhosts/mcnp.lanl.gov/pdf_files/la-ur-14-24530.pdf .. _Hwang: http://www.ans.org/pubs/journals/nse/a_16381 -.. _Josey: http://dx.doi.org/10.1016/j.jcp.2015.08.013 +.. _Josey: https://doi.org/10.1016/j.jcp.2015.08.013 .. _WMP Library: https://github.com/mit-crpg/WMP_Library .. _MCNP: http://mcnp.lanl.gov .. _Serpent: http://montecarlo.vtt.fi .. _NJOY: http://t2.lanl.gov/codes.shtml .. _ENDF/B data: http://www.nndc.bnl.gov/endf -.. _Leppanen: http://dx.doi.org/10.1016/j.anucene.2009.03.019 +.. _Leppanen: https://doi.org/10.1016/j.anucene.2009.03.019 .. _algorithms: http://ab-initio.mit.edu/wiki/index.php/Faddeeva_Package diff --git a/docs/source/methods/geometry.rst b/docs/source/methods/geometry.rst index 7416aee5b9..38d9eb172c 100644 --- a/docs/source/methods/geometry.rst +++ b/docs/source/methods/geometry.rst @@ -960,8 +960,8 @@ normal using the equations from :ref:`transform-coordinates`. The white boundary condition can be applied to any kind of surface, as long as the normal to the surface is known as in :ref:`reflection`. -.. _constructive solid geometry: http://en.wikipedia.org/wiki/Constructive_solid_geometry -.. _surfaces: http://en.wikipedia.org/wiki/Surface +.. _constructive solid geometry: https://en.wikipedia.org/wiki/Constructive_solid_geometry +.. _surfaces: https://en.wikipedia.org/wiki/Surface .. _MCNP: http://mcnp.lanl.gov .. _Serpent: http://montecarlo.vtt.fi .. _Monte Carlo Performance benchmark: https://github.com/mit-crpg/benchmarks/tree/master/mc-performance/openmc diff --git a/docs/source/methods/introduction.rst b/docs/source/methods/introduction.rst index dab1c544ad..0b9544d632 100644 --- a/docs/source/methods/introduction.rst +++ b/docs/source/methods/introduction.rst @@ -141,7 +141,7 @@ be performed before the run is finished. This include the following: - All allocatable arrays are deallocated. -.. _probability distributions: http://en.wikipedia.org/wiki/Probability_distribution -.. _Monte Carlo: http://en.wikipedia.org/wiki/Monte_Carlo_method -.. _central limit theorem: http://en.wikipedia.org/wiki/Central_limit_theorem -.. _pseudorandom number: http://en.wikipedia.org/wiki/Pseudorandom_number_generator +.. _probability distributions: https://en.wikipedia.org/wiki/Probability_distribution +.. _Monte Carlo: https://en.wikipedia.org/wiki/Monte_Carlo_method +.. _central limit theorem: https://en.wikipedia.org/wiki/Central_limit_theorem +.. _pseudorandom number: https://en.wikipedia.org/wiki/Pseudorandom_number_generator diff --git a/docs/source/methods/neutron_physics.rst b/docs/source/methods/neutron_physics.rst index 60d3c1468a..71e9b6bcdd 100644 --- a/docs/source/methods/neutron_physics.rst +++ b/docs/source/methods/neutron_physics.rst @@ -1301,8 +1301,8 @@ correction (DBRC) and was first introduced by `Becker et al.`_. OpenMC has an implementation of DBRC as well as an accelerated sampling method that are described fully in `Walsh et al.`_ -.. _Becker et al.: http://dx.doi.org/10.1016/j.anucene.2008.12.001 -.. _Walsh et al.: http://dx.doi.org/10.1016/j.anucene.2014.01.017 +.. _Becker et al.: https://doi.org/10.1016/j.anucene.2008.12.001 +.. _Walsh et al.: https://doi.org/10.1016/j.anucene.2014.01.017 .. _sab_tables: @@ -1645,23 +1645,23 @@ another. .. |sab| replace:: S(:math:`\alpha,\beta,T`) -.. _SIGMA1 method: http://dx.doi.org/10.13182/NSE76-1 +.. _SIGMA1 method: https://doi.org/10.13182/NSE76-1 .. _scaled interpolation: http://www.ans.org/pubs/journals/nse/a_26575 -.. _probability table method: http://dx.doi.org/10.13182/NSE72-3 +.. _probability table method: https://doi.org/10.13182/NSE72-3 -.. _Watt fission spectrum: http://dx.doi.org/10.1103/PhysRev.87.1037 +.. _Watt fission spectrum: https://doi.org/10.1103/PhysRev.87.1037 .. _Foderaro: http://hdl.handle.net/1721.1/1716 -.. _OECD: http://www.oecd-nea.org/dbprog/MMRW-BOOKS.html +.. _OECD: http://www.oecd-nea.org/tools/abstract/detail/NEA-1792 -.. _NJOY: https://njoy.github.io/NJOY2016/ +.. _NJOY: https://www.njoy21.io/NJOY2016/ .. _PREPRO: http://www-nds.iaea.org/ndspub/endf/prepro/ -.. _ENDF-6 Format: http://www-nds.iaea.org/ndspub/documents/endf/endf102/endf102.pdf +.. _ENDF-6 Format: https://www.oecd-nea.org/dbdata/data/manual-endf/endf102.pdf .. _Monte Carlo Sampler: https://laws.lanl.gov/vhosts/mcnp.lanl.gov/pdf_files/la-9721.pdf @@ -1669,7 +1669,7 @@ another. .. _MC21: http://www.osti.gov/bridge/servlets/purl/903083-HT5p1o/903083.pdf -.. _Romano: http://dx.doi.org/10.1016/j.cpc.2014.11.001 +.. _Romano: https://doi.org/10.1016/j.cpc.2014.11.001 .. _Sutton and Brown: http://www.osti.gov/bridge/product.biblio.jsp?osti_id=307911 diff --git a/docs/source/methods/parallelization.rst b/docs/source/methods/parallelization.rst index 63c4a9d212..5bf090a2b5 100644 --- a/docs/source/methods/parallelization.rst +++ b/docs/source/methods/parallelization.rst @@ -607,33 +607,33 @@ is actually independent of the number of nodes: Radiation Penetration Calculations on a Parallel Computer," *Trans. Am. Nucl. Soc.*, **17**, 260 (1973). -.. _first paper: http://www.jstor.org/stable/2280232 +.. _first paper: https://doi.org/10.2307/2280232 .. _work of Forrest Brown: http://hdl.handle.net/2027.42/24996 -.. _Brissenden and Garlick: http://dx.doi.org/10.1016/0306-4549(86)90095-2 +.. _Brissenden and Garlick: https://doi.org/10.1016/0306-4549(86)90095-2 .. _MPICH2: http://www.mcs.anl.gov/mpi/mpich -.. _binomial tree: http://www.cs.auckland.ac.nz/~jmor159/PLDS210/trees.html +.. _binomial tree: https://www.mcs.anl.gov/~thakur/papers/ijhpca-coll.pdf -.. _Geary: http://www.jstor.org/stable/10.2307/2342070 +.. _Geary: https://doi.org/10.2307/2342070 .. _Barnett: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.51.7772 -.. _single-instruction multiple-data: http://en.wikipedia.org/wiki/SIMD +.. _single-instruction multiple-data: https://en.wikipedia.org/wiki/SIMD -.. _vector computers: http://en.wikipedia.org/wiki/Vector_processor +.. _vector computers: https://en.wikipedia.org/wiki/Vector_processor -.. _single program multiple data: http://en.wikipedia.org/wiki/SPMD +.. _single program multiple data: https://en.wikipedia.org/wiki/SPMD -.. _message-passing interface: http://en.wikipedia.org/wiki/Message_Passing_Interface +.. _message-passing interface: https://en.wikipedia.org/wiki/Message_Passing_Interface .. _PVM: http://www.csm.ornl.gov/pvm/pvm_home.html .. _MPI: http://www.mcs.anl.gov/research/projects/mpi/ -.. _embarrassingly parallel: http://en.wikipedia.org/wiki/Embarrassingly_parallel +.. _embarrassingly parallel: https://en.wikipedia.org/wiki/Embarrassingly_parallel .. _sends: http://www.mcs.anl.gov/research/projects/mpi/www/www3/MPI_Send.html @@ -643,8 +643,8 @@ is actually independent of the number of nodes: .. _allgather: http://www.mcs.anl.gov/research/projects/mpi/www/www3/MPI_Allgather.html -.. _Cauchy distribution: http://en.wikipedia.org/wiki/Cauchy_distribution +.. _Cauchy distribution: https://en.wikipedia.org/wiki/Cauchy_distribution -.. _latency: http://en.wikipedia.org/wiki/Latency_(engineering)#Packet-switched_networks +.. _latency: https://en.wikipedia.org/wiki/Latency_(engineering)#Packet-switched_networks -.. _bandwidth: http://en.wikipedia.org/wiki/Bandwidth_(computing) +.. _bandwidth: https://en.wikipedia.org/wiki/Bandwidth_(computing) diff --git a/docs/source/methods/random_numbers.rst b/docs/source/methods/random_numbers.rst index ce118556e4..0cefc9156b 100644 --- a/docs/source/methods/random_numbers.rst +++ b/docs/source/methods/random_numbers.rst @@ -67,6 +67,6 @@ the idea is to determine the new multiplicative and additive constants in .. rubric:: References -.. _L'Ecuyer: http://dx.doi.org/10.1090/S0025-5718-99-00996-5 +.. _L'Ecuyer: https://doi.org/10.1090/S0025-5718-99-00996-5 .. _Brown: https://laws.lanl.gov/vhosts/mcnp.lanl.gov/pdf_files/anl-rn-arb-stride.pdf -.. _linear congruential generator: http://en.wikipedia.org/wiki/Linear_congruential_generator +.. _linear congruential generator: https://en.wikipedia.org/wiki/Linear_congruential_generator diff --git a/docs/source/methods/tallies.rst b/docs/source/methods/tallies.rst index 9e7bd93bb0..d67584622c 100644 --- a/docs/source/methods/tallies.rst +++ b/docs/source/methods/tallies.rst @@ -484,31 +484,31 @@ improve the estimate of the percentile. .. rubric:: References -.. _following approximation: http://dx.doi.org/10.1080/03610918708812641 +.. _following approximation: https://doi.org/10.1080/03610918708812641 -.. _Bessel's correction: http://en.wikipedia.org/wiki/Bessel's_correction +.. _Bessel's correction: https://en.wikipedia.org/wiki/Bessel's_correction -.. _random variable: http://en.wikipedia.org/wiki/Random_variable +.. _random variable: https://en.wikipedia.org/wiki/Random_variable -.. _stochastic process: http://en.wikipedia.org/wiki/Stochastic_process +.. _stochastic process: https://en.wikipedia.org/wiki/Stochastic_process -.. _independent, identically-distributed random variables: http://en.wikipedia.org/wiki/Independent_and_identically_distributed_random_variables +.. _independent, identically-distributed random variables: https://en.wikipedia.org/wiki/Independent_and_identically_distributed_random_variables -.. _law of large numbers: http://en.wikipedia.org/wiki/Law_of_large_numbers +.. _law of large numbers: https://en.wikipedia.org/wiki/Law_of_large_numbers -.. _expected value: http://en.wikipedia.org/wiki/Expected_value +.. _expected value: https://en.wikipedia.org/wiki/Expected_value -.. _converges in probability: http://en.wikipedia.org/wiki/Convergence_of_random_variables#Convergence_in_probability +.. _converges in probability: https://en.wikipedia.org/wiki/Convergence_of_random_variables#Convergence_in_probability -.. _normal distribution: http://en.wikipedia.org/wiki/Normal_distribution +.. _normal distribution: https://en.wikipedia.org/wiki/Normal_distribution -.. _converges in distribution: http://en.wikipedia.org/wiki/Convergence_of_random_variables#Convergence_in_distribution +.. _converges in distribution: https://en.wikipedia.org/wiki/Convergence_of_random_variables#Convergence_in_distribution -.. _confidence intervals: http://en.wikipedia.org/wiki/Confidence_interval +.. _confidence intervals: https://en.wikipedia.org/wiki/Confidence_interval -.. _Student's t-distribution: http://en.wikipedia.org/wiki/Student%27s_t-distribution +.. _Student's t-distribution: https://en.wikipedia.org/wiki/Student%27s_t-distribution -.. _Cauchy distribution: http://en.wikipedia.org/wiki/Cauchy_distribution +.. _Cauchy distribution: https://en.wikipedia.org/wiki/Cauchy_distribution .. _unpublished rational approximation: https://web.archive.org/web/20150926021742/http://home.online.no/~pjacklam/notes/invnorm/ diff --git a/docs/source/publications.rst b/docs/source/publications.rst index 32b911e5b8..24bcc66a18 100644 --- a/docs/source/publications.rst +++ b/docs/source/publications.rst @@ -366,7 +366,7 @@ Nuclear Data - Jonathan A. Walsh, Benoit Forget, Kord S. Smith, and Forrest B. Brown, "`Uncertainty in Fast Reactor-Relevant Critical Benchmark Simulations Due to Unresolved Resonance Structure - `_," + `_," *Proc. Int. Conf. Mathematics & Computational Methods Applied to Nuclear Science and Engineering*, Jeju, Korea, Apr. 16-20, 2017. @@ -409,7 +409,7 @@ Parallelism - Paul K. Romano and Andrew R. Siegel, "`Limits on the efficiency of event-based algorithms for Monte Carlo neutron transport - `_," + `_," *Proc. Int. Conf. Mathematics & Computational Methods Applied to Nuclear Science and Engineering*, Jeju, Korea, Apr. 16-20, 2017. diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 82143e14a0..48cb3f1cbc 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -82,10 +82,10 @@ A minimal example for performing depletion would be: Internal Classes and Functions ------------------------------ -When running in parallel using `mpi4py `_, the MPI -intercommunicator used can be changed by modifying the following module -variable. If it is not explicitly modified, it defaults to -``mpi4py.MPI.COMM_WORLD``. +When running in parallel using `mpi4py +`_, the MPI intercommunicator used can +be changed by modifying the following module variable. If it is not explicitly +modified, it defaults to ``mpi4py.MPI.COMM_WORLD``. .. data:: comm @@ -161,7 +161,7 @@ Abstract Base Classes A good starting point for extending capabilities in :mod:`openmc.deplete` is to examine the following abstract base classes. Custom classes can -inherit from :class:`abc.TransportOperator` to implement alternative +inherit from :class:`abc.TransportOperator` to implement alternative schemes for collecting reaction rates and other data from a transport code prior to depleting materials diff --git a/docs/source/pythonapi/index.rst b/docs/source/pythonapi/index.rst index 87d0d2a245..7f67f95cef 100644 --- a/docs/source/pythonapi/index.rst +++ b/docs/source/pythonapi/index.rst @@ -27,7 +27,7 @@ there are many substantial benefits to using the Python API, including: For those new to Python, there are many good tutorials available online. We recommend going through the modules from `Codecademy -`_ and/or the `Scipy lectures +`_ and/or the `Scipy lectures `_. The full API documentation serves to provide more information on a given module diff --git a/docs/source/releasenotes/0.11.0.rst b/docs/source/releasenotes/0.11.0.rst index 170630ea72..ba9a35d1af 100644 --- a/docs/source/releasenotes/0.11.0.rst +++ b/docs/source/releasenotes/0.11.0.rst @@ -40,6 +40,7 @@ random, non-overlapping configuration of spheres within the region. New Features ------------ +- White boundary conditions can be applied to surfaces - Support for rectilinear meshes through :class:`openmc.RectilinearMesh`. - The :class:`Geometry`, :class:`Materials`, and :class:`Settings` classes now have a ``from_xml`` method that will build an instance from an existing XML @@ -47,7 +48,8 @@ New Features - Predefined energy group structures can be found in :data:`openmc.mgxs.GROUP_STRUCTURES`. - New tally scores: ``H1-production``, ``H2-production``, ``H3-production``, - ``He3-production``, ``He4-production``, ``heating``, and ``damage-energy`` + ``He3-production``, ``He4-production``, ``heating``, ``heating-local``, and + ``damage-energy``. - Switched to cell-based neighor lists (`PR 1140 `_) - Two new probability distributions that can be used for source distributions: @@ -129,3 +131,4 @@ This release contains new contributions from the following people: - `Jonathan Shimwell `_ - `Patrick Shriwise `_ - `John Tramm `_ +- `Jiankai Yu `_ diff --git a/docs/source/releasenotes/0.4.1.rst b/docs/source/releasenotes/0.4.1.rst index 8ec96897ef..4bc3bc6585 100644 --- a/docs/source/releasenotes/0.4.1.rst +++ b/docs/source/releasenotes/0.4.1.rst @@ -45,9 +45,9 @@ Bug Fixes - `95cfac`_: Fixed error in cell neighbor searches. - `83a803`_: Fixed bug related to probability tables. -.. _b206a8: https://github.com/mit-crpg/openmc/commit/b206a8 -.. _800742: https://github.com/mit-crpg/openmc/commit/800742 -.. _a07c08: https://github.com/mit-crpg/openmc/commit/a07c08 -.. _a75283: https://github.com/mit-crpg/openmc/commit/a75283 -.. _95cfac: https://github.com/mit-crpg/openmc/commit/95cfac -.. _83a803: https://github.com/mit-crpg/openmc/commit/83a803 +.. _b206a8: https://github.com/openmc-dev/openmc/commit/b206a8 +.. _800742: https://github.com/openmc-dev/openmc/commit/800742 +.. _a07c08: https://github.com/openmc-dev/openmc/commit/a07c08 +.. _a75283: https://github.com/openmc-dev/openmc/commit/a75283 +.. _95cfac: https://github.com/openmc-dev/openmc/commit/95cfac +.. _83a803: https://github.com/openmc-dev/openmc/commit/83a803 diff --git a/docs/source/releasenotes/0.4.2.rst b/docs/source/releasenotes/0.4.2.rst index c0d399301e..0e28eb6e66 100644 --- a/docs/source/releasenotes/0.4.2.rst +++ b/docs/source/releasenotes/0.4.2.rst @@ -42,13 +42,13 @@ Bug Fixes - d050c7_: Added Bessel's correction to make estimate of variance unbiased. - 2a5b9c_: Fixed regression in plotting. -.. _a27f8f: https://github.com/mit-crpg/openmc/commit/a27f8f -.. _afe121: https://github.com/mit-crpg/openmc/commit/afe121 -.. _e0968e: https://github.com/mit-crpg/openmc/commit/e0968e -.. _298db8: https://github.com/mit-crpg/openmc/commit/298db8 -.. _2f3bbe: https://github.com/mit-crpg/openmc/commit/2f3bbe -.. _671f30: https://github.com/mit-crpg/openmc/commit/671f30 -.. _b2c40e: https://github.com/mit-crpg/openmc/commit/b2c40e -.. _5524fd: https://github.com/mit-crpg/openmc/commit/5524fd -.. _d050c7: https://github.com/mit-crpg/openmc/commit/d050c7 -.. _2a5b9c: https://github.com/mit-crpg/openmc/commit/2a5b9c +.. _a27f8f: https://github.com/openmc-dev/openmc/commit/a27f8f +.. _afe121: https://github.com/openmc-dev/openmc/commit/afe121 +.. _e0968e: https://github.com/openmc-dev/openmc/commit/e0968e +.. _298db8: https://github.com/openmc-dev/openmc/commit/298db8 +.. _2f3bbe: https://github.com/openmc-dev/openmc/commit/2f3bbe +.. _671f30: https://github.com/openmc-dev/openmc/commit/671f30 +.. _b2c40e: https://github.com/openmc-dev/openmc/commit/b2c40e +.. _5524fd: https://github.com/openmc-dev/openmc/commit/5524fd +.. _d050c7: https://github.com/openmc-dev/openmc/commit/d050c7 +.. _2a5b9c: https://github.com/openmc-dev/openmc/commit/2a5b9c diff --git a/docs/source/releasenotes/0.4.3.rst b/docs/source/releasenotes/0.4.3.rst index 7ef12198d7..4023cb5255 100644 --- a/docs/source/releasenotes/0.4.3.rst +++ b/docs/source/releasenotes/0.4.3.rst @@ -40,12 +40,12 @@ Bug Fixes - 3212f5_: Fixed issue with blank line at beginning of XML files. .. _nelsonag: https://github.com/nelsonag -.. _33f29a: https://github.com/mit-crpg/openmc/commit/33f29a -.. _1c472d: https://github.com/mit-crpg/openmc/commit/1c472d -.. _3c6e80: https://github.com/mit-crpg/openmc/commit/3c6e80 -.. _3bd35b: https://github.com/mit-crpg/openmc/commit/3bd35b -.. _0069d5: https://github.com/mit-crpg/openmc/commit/0069d5 -.. _7af2cf: https://github.com/mit-crpg/openmc/commit/7af2cf -.. _460ef1: https://github.com/mit-crpg/openmc/commit/460ef1 -.. _85a60e: https://github.com/mit-crpg/openmc/commit/85a60e -.. _3212f5: https://github.com/mit-crpg/openmc/commit/3212f5 +.. _33f29a: https://github.com/openmc-dev/openmc/commit/33f29a +.. _1c472d: https://github.com/openmc-dev/openmc/commit/1c472d +.. _3c6e80: https://github.com/openmc-dev/openmc/commit/3c6e80 +.. _3bd35b: https://github.com/openmc-dev/openmc/commit/3bd35b +.. _0069d5: https://github.com/openmc-dev/openmc/commit/0069d5 +.. _7af2cf: https://github.com/openmc-dev/openmc/commit/7af2cf +.. _460ef1: https://github.com/openmc-dev/openmc/commit/460ef1 +.. _85a60e: https://github.com/openmc-dev/openmc/commit/85a60e +.. _3212f5: https://github.com/openmc-dev/openmc/commit/3212f5 diff --git a/docs/source/releasenotes/0.4.4.rst b/docs/source/releasenotes/0.4.4.rst index d700e610d2..c5201b30f3 100644 --- a/docs/source/releasenotes/0.4.4.rst +++ b/docs/source/releasenotes/0.4.4.rst @@ -36,8 +36,8 @@ Bug Fixes - 7fd617_: Fixed bug with restart runs in parallel. - dc4a8f_: Fixed bug with fixed source restart runs. -.. _4654ee: https://github.com/mit-crpg/openmc/commit/4654ee -.. _7ee461: https://github.com/mit-crpg/openmc/commit/7ee461 -.. _792eb3: https://github.com/mit-crpg/openmc/commit/792eb3 -.. _7fd617: https://github.com/mit-crpg/openmc/commit/7fd617 -.. _dc4a8f: https://github.com/mit-crpg/openmc/commit/dc4a8f +.. _4654ee: https://github.com/openmc-dev/openmc/commit/4654ee +.. _7ee461: https://github.com/openmc-dev/openmc/commit/7ee461 +.. _792eb3: https://github.com/openmc-dev/openmc/commit/792eb3 +.. _7fd617: https://github.com/openmc-dev/openmc/commit/7fd617 +.. _dc4a8f: https://github.com/openmc-dev/openmc/commit/dc4a8f diff --git a/docs/source/releasenotes/0.5.0.rst b/docs/source/releasenotes/0.5.0.rst index 443216e335..aa906a323b 100644 --- a/docs/source/releasenotes/0.5.0.rst +++ b/docs/source/releasenotes/0.5.0.rst @@ -39,11 +39,11 @@ Bug Fixes - 6f8d9d_: Set default tally labels. - 6a3a5e_: Fix problem with corner-crossing in lattices. -.. _737b90: https://github.com/mit-crpg/openmc/commit/737b90 -.. _a819b4: https://github.com/mit-crpg/openmc/commit/a819b4 -.. _b11696: https://github.com/mit-crpg/openmc/commit/b11696 -.. _2bd46a: https://github.com/mit-crpg/openmc/commit/2bd46a -.. _7a1f08: https://github.com/mit-crpg/openmc/commit/7a1f08 -.. _c0e3ec: https://github.com/mit-crpg/openmc/commit/c0e3ec -.. _6f8d9d: https://github.com/mit-crpg/openmc/commit/6f8d9d -.. _6a3a5e: https://github.com/mit-crpg/openmc/commit/6a3a5e +.. _737b90: https://github.com/openmc-dev/openmc/commit/737b90 +.. _a819b4: https://github.com/openmc-dev/openmc/commit/a819b4 +.. _b11696: https://github.com/openmc-dev/openmc/commit/b11696 +.. _2bd46a: https://github.com/openmc-dev/openmc/commit/2bd46a +.. _7a1f08: https://github.com/openmc-dev/openmc/commit/7a1f08 +.. _c0e3ec: https://github.com/openmc-dev/openmc/commit/c0e3ec +.. _6f8d9d: https://github.com/openmc-dev/openmc/commit/6f8d9d +.. _6a3a5e: https://github.com/openmc-dev/openmc/commit/6a3a5e diff --git a/docs/source/releasenotes/0.5.1.rst b/docs/source/releasenotes/0.5.1.rst index 6fa584fcf0..26a6927c79 100644 --- a/docs/source/releasenotes/0.5.1.rst +++ b/docs/source/releasenotes/0.5.1.rst @@ -36,8 +36,8 @@ Bug Fixes - 63bfd2_: Fix tracklength tallies with cell filter and universes. - 88daf7_: Fix analog tallies with survival biasing. -.. _94103e: https://github.com/mit-crpg/openmc/commit/94103e -.. _e77059: https://github.com/mit-crpg/openmc/commit/e77059 -.. _b0fe88: https://github.com/mit-crpg/openmc/commit/b0fe88 -.. _63bfd2: https://github.com/mit-crpg/openmc/commit/63bfd2 -.. _88daf7: https://github.com/mit-crpg/openmc/commit/88daf7 +.. _94103e: https://github.com/openmc-dev/openmc/commit/94103e +.. _e77059: https://github.com/openmc-dev/openmc/commit/e77059 +.. _b0fe88: https://github.com/openmc-dev/openmc/commit/b0fe88 +.. _63bfd2: https://github.com/openmc-dev/openmc/commit/63bfd2 +.. _88daf7: https://github.com/openmc-dev/openmc/commit/88daf7 diff --git a/docs/source/releasenotes/0.5.2.rst b/docs/source/releasenotes/0.5.2.rst index ba6374ea7c..1a57028953 100644 --- a/docs/source/releasenotes/0.5.2.rst +++ b/docs/source/releasenotes/0.5.2.rst @@ -41,15 +41,15 @@ Bug Fixes - ab0793_: Corrected PETSC_NULL references to their correct types. - 182ebd_: Use analog estimator with energyout filter. -.. _7632f3: https://github.com/mit-crpg/openmc/commit/7632f3 -.. _f85ac4: https://github.com/mit-crpg/openmc/commit/f85ac4 -.. _49c36b: https://github.com/mit-crpg/openmc/commit/49c36b -.. _5ccc78: https://github.com/mit-crpg/openmc/commit/5ccc78 -.. _b1f52f: https://github.com/mit-crpg/openmc/commit/b1f52f -.. _eae7e5: https://github.com/mit-crpg/openmc/commit/eae7e5 -.. _10c1cc: https://github.com/mit-crpg/openmc/commit/10c1cc -.. _afdb50: https://github.com/mit-crpg/openmc/commit/afdb50 -.. _a3c593: https://github.com/mit-crpg/openmc/commit/a3c593 -.. _3a66e3: https://github.com/mit-crpg/openmc/commit/3a66e3 -.. _ab0793: https://github.com/mit-crpg/openmc/commit/ab0793 -.. _182ebd: https://github.com/mit-crpg/openmc/commit/182ebd +.. _7632f3: https://github.com/openmc-dev/openmc/commit/7632f3 +.. _f85ac4: https://github.com/openmc-dev/openmc/commit/f85ac4 +.. _49c36b: https://github.com/openmc-dev/openmc/commit/49c36b +.. _5ccc78: https://github.com/openmc-dev/openmc/commit/5ccc78 +.. _b1f52f: https://github.com/openmc-dev/openmc/commit/b1f52f +.. _eae7e5: https://github.com/openmc-dev/openmc/commit/eae7e5 +.. _10c1cc: https://github.com/openmc-dev/openmc/commit/10c1cc +.. _afdb50: https://github.com/openmc-dev/openmc/commit/afdb50 +.. _a3c593: https://github.com/openmc-dev/openmc/commit/a3c593 +.. _3a66e3: https://github.com/openmc-dev/openmc/commit/3a66e3 +.. _ab0793: https://github.com/openmc-dev/openmc/commit/ab0793 +.. _182ebd: https://github.com/openmc-dev/openmc/commit/182ebd diff --git a/docs/source/releasenotes/0.5.3.rst b/docs/source/releasenotes/0.5.3.rst index a7305da716..7b2878d911 100644 --- a/docs/source/releasenotes/0.5.3.rst +++ b/docs/source/releasenotes/0.5.3.rst @@ -40,8 +40,8 @@ Bug Fixes - c18a6e_: Check for valid secondary mode on S(a,b) tables. - 82c456_: Fix bug where last process could have zero particles. -.. _2b1e8a: https://github.com/mit-crpg/openmc/commit/2b1e8a -.. _5853d2: https://github.com/mit-crpg/openmc/commit/5853d2 -.. _e178c7: https://github.com/mit-crpg/openmc/commit/e178c7 -.. _c18a6e: https://github.com/mit-crpg/openmc/commit/c18a6e -.. _82c456: https://github.com/mit-crpg/openmc/commit/82c456 +.. _2b1e8a: https://github.com/openmc-dev/openmc/commit/2b1e8a +.. _5853d2: https://github.com/openmc-dev/openmc/commit/5853d2 +.. _e178c7: https://github.com/openmc-dev/openmc/commit/e178c7 +.. _c18a6e: https://github.com/openmc-dev/openmc/commit/c18a6e +.. _82c456: https://github.com/openmc-dev/openmc/commit/82c456 diff --git a/docs/source/releasenotes/0.5.4.rst b/docs/source/releasenotes/0.5.4.rst index 36d9ec4d57..ed40d29df5 100644 --- a/docs/source/releasenotes/0.5.4.rst +++ b/docs/source/releasenotes/0.5.4.rst @@ -39,13 +39,13 @@ Bug Fixes - cf567c_: ENDF/B-VI data checked for compatibility - 6b9461_: Fix p_valid sampling inside of sample_energy -.. _32c03c: https://github.com/mit-crpg/openmc/commit/32c03c -.. _c71ef5: https://github.com/mit-crpg/openmc/commit/c71ef5 -.. _8884fb: https://github.com/mit-crpg/openmc/commit/8884fb -.. _b38af0: https://github.com/mit-crpg/openmc/commit/b38af0 -.. _d28750: https://github.com/mit-crpg/openmc/commit/d28750 -.. _cf567c: https://github.com/mit-crpg/openmc/commit/cf567c -.. _6b9461: https://github.com/mit-crpg/openmc/commit/6b9461 +.. _32c03c: https://github.com/openmc-dev/openmc/commit/32c03c +.. _c71ef5: https://github.com/openmc-dev/openmc/commit/c71ef5 +.. _8884fb: https://github.com/openmc-dev/openmc/commit/8884fb +.. _b38af0: https://github.com/openmc-dev/openmc/commit/b38af0 +.. _d28750: https://github.com/openmc-dev/openmc/commit/d28750 +.. _cf567c: https://github.com/openmc-dev/openmc/commit/cf567c +.. _6b9461: https://github.com/openmc-dev/openmc/commit/6b9461 ------------ Contributors diff --git a/docs/source/releasenotes/0.6.0.rst b/docs/source/releasenotes/0.6.0.rst index 63e234583c..8d0d2957be 100644 --- a/docs/source/releasenotes/0.6.0.rst +++ b/docs/source/releasenotes/0.6.0.rst @@ -35,13 +35,13 @@ Bug Fixes - d7a7d0_: Fix bug with specifying xs attribute - 85b3cb_: Fix out-of-bounds error with OpenMP threading -.. _41f7ca: https://github.com/mit-crpg/openmc/commit/41f7ca -.. _038736: https://github.com/mit-crpg/openmc/commit/038736 -.. _46f9e8: https://github.com/mit-crpg/openmc/commit/46f9e8 -.. _d1ca35: https://github.com/mit-crpg/openmc/commit/d1ca35 -.. _0291c0: https://github.com/mit-crpg/openmc/commit/0291c0 -.. _d7a7d0: https://github.com/mit-crpg/openmc/commit/d7a7d0 -.. _85b3cb: https://github.com/mit-crpg/openmc/commit/85b3cb +.. _41f7ca: https://github.com/openmc-dev/openmc/commit/41f7ca +.. _038736: https://github.com/openmc-dev/openmc/commit/038736 +.. _46f9e8: https://github.com/openmc-dev/openmc/commit/46f9e8 +.. _d1ca35: https://github.com/openmc-dev/openmc/commit/d1ca35 +.. _0291c0: https://github.com/openmc-dev/openmc/commit/0291c0 +.. _d7a7d0: https://github.com/openmc-dev/openmc/commit/d7a7d0 +.. _85b3cb: https://github.com/openmc-dev/openmc/commit/85b3cb ------------ Contributors diff --git a/docs/source/releasenotes/0.6.1.rst b/docs/source/releasenotes/0.6.1.rst index a8c59aa519..c0f11ef685 100644 --- a/docs/source/releasenotes/0.6.1.rst +++ b/docs/source/releasenotes/0.6.1.rst @@ -38,16 +38,16 @@ Bug Fixes - 2a95ef_: Prevent segmentation fault on "current" score without mesh filter - 93e482_: Check for negative values in probability tables -.. _03e890: https://github.com/mit-crpg/openmc/commit/03e890 -.. _4439de: https://github.com/mit-crpg/openmc/commit/4439de -.. _5808ed: https://github.com/mit-crpg/openmc/commit/5808ed -.. _2e60c0: https://github.com/mit-crpg/openmc/commit/2e60c0 -.. _3e0870: https://github.com/mit-crpg/openmc/commit/3e0870 -.. _dc4776: https://github.com/mit-crpg/openmc/commit/dc4776 -.. _01178b: https://github.com/mit-crpg/openmc/commit/01178b -.. _62ec43: https://github.com/mit-crpg/openmc/commit/62ec43 -.. _2a95ef: https://github.com/mit-crpg/openmc/commit/2a95ef -.. _93e482: https://github.com/mit-crpg/openmc/commit/93e482 +.. _03e890: https://github.com/openmc-dev/openmc/commit/03e890 +.. _4439de: https://github.com/openmc-dev/openmc/commit/4439de +.. _5808ed: https://github.com/openmc-dev/openmc/commit/5808ed +.. _2e60c0: https://github.com/openmc-dev/openmc/commit/2e60c0 +.. _3e0870: https://github.com/openmc-dev/openmc/commit/3e0870 +.. _dc4776: https://github.com/openmc-dev/openmc/commit/dc4776 +.. _01178b: https://github.com/openmc-dev/openmc/commit/01178b +.. _62ec43: https://github.com/openmc-dev/openmc/commit/62ec43 +.. _2a95ef: https://github.com/openmc-dev/openmc/commit/2a95ef +.. _93e482: https://github.com/openmc-dev/openmc/commit/93e482 ------------ Contributors diff --git a/docs/source/releasenotes/0.6.2.rst b/docs/source/releasenotes/0.6.2.rst index 6fe6c28dac..9a5400cac3 100644 --- a/docs/source/releasenotes/0.6.2.rst +++ b/docs/source/releasenotes/0.6.2.rst @@ -33,10 +33,10 @@ Bug Fixes - e6abb9_: Fix segfault when tallying in a void material - 291b45_: Handle metastable nuclides in NNDC data and multiplicities in MT=5 data -.. _26fb93: https://github.com/mit-crpg/openmc/commit/26fb93 -.. _2f07c0: https://github.com/mit-crpg/openmc/commit/2f07c0 -.. _e6abb9: https://github.com/mit-crpg/openmc/commit/e6abb9 -.. _291b45: https://github.com/mit-crpg/openmc/commit/291b45 +.. _26fb93: https://github.com/openmc-dev/openmc/commit/26fb93 +.. _2f07c0: https://github.com/openmc-dev/openmc/commit/2f07c0 +.. _e6abb9: https://github.com/openmc-dev/openmc/commit/e6abb9 +.. _291b45: https://github.com/openmc-dev/openmc/commit/291b45 ------------ Contributors diff --git a/docs/source/releasenotes/0.7.0.rst b/docs/source/releasenotes/0.7.0.rst index 8f86631ea5..2a3063a92b 100644 --- a/docs/source/releasenotes/0.7.0.rst +++ b/docs/source/releasenotes/0.7.0.rst @@ -40,11 +40,11 @@ Bug Fixes - 6121d9_: Fix bugs related to particle track files - 2f0e89_: Fixes for nuclide specification in tallies -.. _b5f712: https://github.com/mit-crpg/openmc/commit/b5f712 -.. _e6675b: https://github.com/mit-crpg/openmc/commit/e6675b -.. _04e2c1: https://github.com/mit-crpg/openmc/commit/04e2c1 -.. _6121d9: https://github.com/mit-crpg/openmc/commit/6121d9 -.. _2f0e89: https://github.com/mit-crpg/openmc/commit/2f0e89 +.. _b5f712: https://github.com/openmc-dev/openmc/commit/b5f712 +.. _e6675b: https://github.com/openmc-dev/openmc/commit/e6675b +.. _04e2c1: https://github.com/openmc-dev/openmc/commit/04e2c1 +.. _6121d9: https://github.com/openmc-dev/openmc/commit/6121d9 +.. _2f0e89: https://github.com/openmc-dev/openmc/commit/2f0e89 ------------ Contributors diff --git a/docs/source/releasenotes/0.7.1.rst b/docs/source/releasenotes/0.7.1.rst index 5ee2c9f69f..01af742390 100644 --- a/docs/source/releasenotes/0.7.1.rst +++ b/docs/source/releasenotes/0.7.1.rst @@ -62,15 +62,15 @@ Bug Fixes - 441fd4_: Fix bug in kappa-fission score - 7e5974_: Allow fixed source simulations from Python API -.. _299322: https://github.com/mit-crpg/openmc/commit/299322 -.. _d74840: https://github.com/mit-crpg/openmc/commit/d74840 -.. _c29a81: https://github.com/mit-crpg/openmc/commit/c29a81 -.. _3edc23: https://github.com/mit-crpg/openmc/commit/3edc23 -.. _629e3b: https://github.com/mit-crpg/openmc/commit/629e3b -.. _5dbe8b: https://github.com/mit-crpg/openmc/commit/5dbe8b -.. _ff66f4: https://github.com/mit-crpg/openmc/commit/ff66f4 -.. _441fd4: https://github.com/mit-crpg/openmc/commit/441fd4 -.. _7e5974: https://github.com/mit-crpg/openmc/commit/7e5974 +.. _299322: https://github.com/openmc-dev/openmc/commit/299322 +.. _d74840: https://github.com/openmc-dev/openmc/commit/d74840 +.. _c29a81: https://github.com/openmc-dev/openmc/commit/c29a81 +.. _3edc23: https://github.com/openmc-dev/openmc/commit/3edc23 +.. _629e3b: https://github.com/openmc-dev/openmc/commit/629e3b +.. _5dbe8b: https://github.com/openmc-dev/openmc/commit/5dbe8b +.. _ff66f4: https://github.com/openmc-dev/openmc/commit/ff66f4 +.. _441fd4: https://github.com/openmc-dev/openmc/commit/441fd4 +.. _7e5974: https://github.com/openmc-dev/openmc/commit/7e5974 ------------ Contributors diff --git a/docs/source/releasenotes/0.8.0.rst b/docs/source/releasenotes/0.8.0.rst index bd8a89a64a..d4f5a878be 100644 --- a/docs/source/releasenotes/0.8.0.rst +++ b/docs/source/releasenotes/0.8.0.rst @@ -65,17 +65,17 @@ Bug Fixes - 8467ae_: Better threshold for allowable lost particles - 493c6f_: Fix type of return argument for h5pget_driver_f -.. _70daa7: https://github.com/mit-crpg/openmc/commit/70daa7 -.. _40b05f: https://github.com/mit-crpg/openmc/commit/40b05f -.. _9586ed: https://github.com/mit-crpg/openmc/commit/9586ed -.. _a855e8: https://github.com/mit-crpg/openmc/commit/a855e8 -.. _7294a1: https://github.com/mit-crpg/openmc/commit/7294a1 -.. _12f246: https://github.com/mit-crpg/openmc/commit/12f246 -.. _0227f4: https://github.com/mit-crpg/openmc/commit/0227f4 -.. _51deaa: https://github.com/mit-crpg/openmc/commit/51deaa -.. _fed74b: https://github.com/mit-crpg/openmc/commit/fed74b -.. _8467ae: https://github.com/mit-crpg/openmc/commit/8467ae -.. _493c6f: https://github.com/mit-crpg/openmc/commit/493c6f +.. _70daa7: https://github.com/openmc-dev/openmc/commit/70daa7 +.. _40b05f: https://github.com/openmc-dev/openmc/commit/40b05f +.. _9586ed: https://github.com/openmc-dev/openmc/commit/9586ed +.. _a855e8: https://github.com/openmc-dev/openmc/commit/a855e8 +.. _7294a1: https://github.com/openmc-dev/openmc/commit/7294a1 +.. _12f246: https://github.com/openmc-dev/openmc/commit/12f246 +.. _0227f4: https://github.com/openmc-dev/openmc/commit/0227f4 +.. _51deaa: https://github.com/openmc-dev/openmc/commit/51deaa +.. _fed74b: https://github.com/openmc-dev/openmc/commit/fed74b +.. _8467ae: https://github.com/openmc-dev/openmc/commit/8467ae +.. _493c6f: https://github.com/openmc-dev/openmc/commit/493c6f ------------ Contributors diff --git a/docs/source/releasenotes/0.9.0.rst b/docs/source/releasenotes/0.9.0.rst index 478c570962..778a7d1357 100644 --- a/docs/source/releasenotes/0.9.0.rst +++ b/docs/source/releasenotes/0.9.0.rst @@ -111,25 +111,25 @@ Bug Fixes - 489540_: Check for void materials in tracklength tallies - f0214f_: Fixes/improvements to the ARES algorithm -.. _c5df6c: https://github.com/mit-crpg/openmc/commit/c5df6c -.. _1cfa39: https://github.com/mit-crpg/openmc/commit/1cfa39 -.. _335359: https://github.com/mit-crpg/openmc/commit/335359 -.. _17c678: https://github.com/mit-crpg/openmc/commit/17c678 -.. _23ec0b: https://github.com/mit-crpg/openmc/commit/23ec0b -.. _7eefb7: https://github.com/mit-crpg/openmc/commit/7eefb7 -.. _7880d4: https://github.com/mit-crpg/openmc/commit/7880d4 -.. _ad2d9f: https://github.com/mit-crpg/openmc/commit/ad2d9f -.. _59fdca: https://github.com/mit-crpg/openmc/commit/59fdca -.. _9eff5b: https://github.com/mit-crpg/openmc/commit/9eff5b -.. _7848a9: https://github.com/mit-crpg/openmc/commit/7848a9 -.. _f139ce: https://github.com/mit-crpg/openmc/commit/f139ce -.. _b8ddfa: https://github.com/mit-crpg/openmc/commit/b8ddfa -.. _ec3cfb: https://github.com/mit-crpg/openmc/commit/ec3cfb -.. _5e9b06: https://github.com/mit-crpg/openmc/commit/5e9b06 -.. _c39990: https://github.com/mit-crpg/openmc/commit/c39990 -.. _c6b67e: https://github.com/mit-crpg/openmc/commit/c6b67e -.. _489540: https://github.com/mit-crpg/openmc/commit/489540 -.. _f0214f: https://github.com/mit-crpg/openmc/commit/f0214f +.. _c5df6c: https://github.com/openmc-dev/openmc/commit/c5df6c +.. _1cfa39: https://github.com/openmc-dev/openmc/commit/1cfa39 +.. _335359: https://github.com/openmc-dev/openmc/commit/335359 +.. _17c678: https://github.com/openmc-dev/openmc/commit/17c678 +.. _23ec0b: https://github.com/openmc-dev/openmc/commit/23ec0b +.. _7eefb7: https://github.com/openmc-dev/openmc/commit/7eefb7 +.. _7880d4: https://github.com/openmc-dev/openmc/commit/7880d4 +.. _ad2d9f: https://github.com/openmc-dev/openmc/commit/ad2d9f +.. _59fdca: https://github.com/openmc-dev/openmc/commit/59fdca +.. _9eff5b: https://github.com/openmc-dev/openmc/commit/9eff5b +.. _7848a9: https://github.com/openmc-dev/openmc/commit/7848a9 +.. _f139ce: https://github.com/openmc-dev/openmc/commit/f139ce +.. _b8ddfa: https://github.com/openmc-dev/openmc/commit/b8ddfa +.. _ec3cfb: https://github.com/openmc-dev/openmc/commit/ec3cfb +.. _5e9b06: https://github.com/openmc-dev/openmc/commit/5e9b06 +.. _c39990: https://github.com/openmc-dev/openmc/commit/c39990 +.. _c6b67e: https://github.com/openmc-dev/openmc/commit/c6b67e +.. _489540: https://github.com/openmc-dev/openmc/commit/489540 +.. _f0214f: https://github.com/openmc-dev/openmc/commit/f0214f ------------ Contributors diff --git a/docs/source/usersguide/beginners.rst b/docs/source/usersguide/beginners.rst index 38e016d1ed..85d50a2d4e 100644 --- a/docs/source/usersguide/beginners.rst +++ b/docs/source/usersguide/beginners.rst @@ -141,13 +141,13 @@ and `Volume II`_. You may also find it helpful to review the following terms: - `Effective multiplication factor`_ - `Flux`_ -.. _nuclear reactor: http://en.wikipedia.org/wiki/Nuclear_reactor -.. _Monte Carlo: http://en.wikipedia.org/wiki/Monte_Carlo_method -.. _fission: http://en.wikipedia.org/wiki/Nuclear_fission -.. _deterministic: http://en.wikipedia.org/wiki/Deterministic_algorithm -.. _neutron transport: http://en.wikipedia.org/wiki/Neutron_transport -.. _discretization: http://en.wikipedia.org/wiki/Discretization -.. _constructive solid geometry: http://en.wikipedia.org/wiki/Constructive_solid_geometry +.. _nuclear reactor: https://en.wikipedia.org/wiki/Nuclear_reactor +.. _Monte Carlo: https://en.wikipedia.org/wiki/Monte_Carlo_method +.. _fission: https://en.wikipedia.org/wiki/Nuclear_fission +.. _deterministic: https://en.wikipedia.org/wiki/Deterministic_algorithm +.. _neutron transport: https://en.wikipedia.org/wiki/Neutron_transport +.. _discretization: https://en.wikipedia.org/wiki/Discretization +.. _constructive solid geometry: https://en.wikipedia.org/wiki/Constructive_solid_geometry .. _git: http://git-scm.com/ .. _git tutorials: http://git-scm.com/documentation .. _Reactor Concepts Manual: http://www.tayloredge.com/periodic/trivia/ReactorConcepts.pdf @@ -156,6 +156,6 @@ and `Volume II`_. You may also find it helpful to review the following terms: .. _OpenMC source code: https://github.com/openmc-dev/openmc .. _GitHub: https://github.com/ .. _bug reports: https://github.com/openmc-dev/openmc/issues -.. _Neutron cross section: http://en.wikipedia.org/wiki/Neutron_cross_section +.. _Neutron cross section: https://en.wikipedia.org/wiki/Neutron_cross_section .. _Effective multiplication factor: https://en.wikipedia.org/wiki/Nuclear_chain_reaction#Effective_neutron_multiplication_factor -.. _Flux: http://en.wikipedia.org/wiki/Neutron_flux +.. _Flux: https://en.wikipedia.org/wiki/Neutron_flux diff --git a/docs/source/usersguide/geometry.rst b/docs/source/usersguide/geometry.rst index 51daf7b5d1..5e583cfd2b 100644 --- a/docs/source/usersguide/geometry.rst +++ b/docs/source/usersguide/geometry.rst @@ -396,8 +396,8 @@ if needed, lattices, the last step is to create an instance of geom.root_universe = root_univ geom.export_to_xml() -.. _constructive solid geometry: http://en.wikipedia.org/wiki/Constructive_solid_geometry -.. _quadratic surfaces: http://en.wikipedia.org/wiki/Quadric +.. _constructive solid geometry: https://en.wikipedia.org/wiki/Constructive_solid_geometry +.. _quadratic surfaces: https://en.wikipedia.org/wiki/Quadric -------------------------- Using CAD-based Geometry diff --git a/docs/source/usersguide/install.rst b/docs/source/usersguide/install.rst index 96b4f5024c..8ca7e0b351 100644 --- a/docs/source/usersguide/install.rst +++ b/docs/source/usersguide/install.rst @@ -17,7 +17,8 @@ system for installing multiple versions of software packages and their dependencies and switching easily between them. `conda-forge `_ is a community-led conda channel of installable packages. For instructions on installing conda, please consult their -`documentation `_. +`documentation +`_. Once you have `conda` installed on your system, add the `conda-forge` channel to your configuration with: @@ -444,7 +445,7 @@ as for OpenMC. .. admonition:: Optional :class: note - `mpi4py `_ + `mpi4py `_ mpi4py provides Python bindings to MPI for running distributed-memory parallel runs. This package is needed if you plan on running depletion simulations in parallel using MPI. @@ -483,9 +484,9 @@ Make sure to replace the last string on the second line with the path to the schemas.xml file in your own OpenMC source directory. .. _GNU Emacs: http://www.gnu.org/software/emacs/ -.. _validation: http://en.wikipedia.org/wiki/XML_validation +.. _validation: https://en.wikipedia.org/wiki/XML_validation .. _RELAX NG: http://relaxng.org/ .. _NNDC: http://www.nndc.bnl.gov/endf/b7.1/acefiles.html .. _ctest: http://www.cmake.org/cmake/help/v2.8.12/ctest.html -.. _Conda: https://conda.io/docs/ +.. _Conda: https://docs.conda.io/en/latest/ .. _pip: https://pip.pypa.io/en/stable/ diff --git a/docs/source/usersguide/plots.rst b/docs/source/usersguide/plots.rst index f21d2c8c59..a10f370d6b 100644 --- a/docs/source/usersguide/plots.rst +++ b/docs/source/usersguide/plots.rst @@ -66,7 +66,7 @@ particular cells/materials should be given colors of your choosing:: } Note that colors can be given as RGB tuples or by a string indicating a valid -`SVG color `_. +`SVG color `_. When you're done creating your :class:`openmc.Plot` instances, you need to then assign them to a :class:`openmc.Plots` collection and export it to XML:: diff --git a/openmc/mgxs/__init__.py b/openmc/mgxs/__init__.py index 886cc904a2..1b1ad0dce2 100644 --- a/openmc/mgxs/__init__.py +++ b/openmc/mgxs/__init__.py @@ -18,7 +18,7 @@ GROUP_STRUCTURES = {} .. _CASMO: https://www.studsvik.com/SharepointFiles/CASMO-5%20Development%20and%20Applications.pdf .. _XMAS-172: https://www-nds.iaea.org/wimsd/energy.htm -.. _SHEM-361: https://www.polymtl.ca/merlin/libraries.htm +.. _SHEM-361: https://www.polymtl.ca/merlin/downloads/FP214.pdf .. _activation: https://fispact.ukaea.uk/wiki/Keyword:GETXS .. _CCFE-709: https://fispact.ukaea.uk/wiki/CCFE-709_group_structure .. _UKAEA-1102: https://fispact.ukaea.uk/wiki/UKAEA-1102_group_structure diff --git a/openmc/plots.py b/openmc/plots.py index b4712c9026..10d7738689 100644 --- a/openmc/plots.py +++ b/openmc/plots.py @@ -173,7 +173,7 @@ class Plot(IDManagerMixin): OpenMC is capable of generating two-dimensional slice plots and three-dimensional voxel plots. Colors that are used in plots can be given as RGB tuples, e.g. (255, 255, 255) would be white, or by a string indicating a - valid `SVG color `_. + valid `SVG color `_. Parameters ---------- From d32c2175ba840af04b65ede5f6a6915adf808b97 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 4 Oct 2019 11:26:20 -0500 Subject: [PATCH 106/158] Add symbolic link to chain_simple.xml in jupyter notebook directory --- examples/jupyter/chain_simple.xml | 1 + 1 file changed, 1 insertion(+) create mode 120000 examples/jupyter/chain_simple.xml diff --git a/examples/jupyter/chain_simple.xml b/examples/jupyter/chain_simple.xml new file mode 120000 index 0000000000..8b1b45535c --- /dev/null +++ b/examples/jupyter/chain_simple.xml @@ -0,0 +1 @@ +/home/romano/openmc/tests/chain_simple.xml \ No newline at end of file From e869d8eef1b766d8d0c4f221a385677f67f1017d Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 4 Oct 2019 12:48:23 -0500 Subject: [PATCH 107/158] Fix documentation build (deplete module was causing issues) --- docs/source/conf.py | 2 +- docs/source/pythonapi/deplete.rst | 12 +++++++++--- openmc/deplete/__init__.py | 7 +++---- openmc/deplete/abc.py | 2 +- openmc/deplete/cram.py | 12 +++--------- 5 files changed, 17 insertions(+), 18 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 55776b8f49..f46b11334b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -27,7 +27,7 @@ MOCK_MODULES = [ 'scipy.interpolate', 'scipy.integrate', 'scipy.optimize', 'scipy.special', 'scipy.stats', 'scipy.spatial', 'h5py', 'pandas', 'uncertainties', 'matplotlib', 'matplotlib.pyplot', 'openmoc', - 'openmc.data.reconstruct' + 'openmc.data.reconstruct', 'openmc.checkvalue' ] sys.modules.update((mod_name, MagicMock()) for mod_name in MOCK_MODULES) diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 48cb3f1cbc..b864529d16 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -126,8 +126,15 @@ data, such as number densities and reaction rates for each material. Results ResultsList -The following functions are used to solve the depletion equations, with -:func:`cram.CRAM48` being the default. +The following class and functions are used to solve the depletion equations, +with :func:`cram.CRAM48` being the default. + +.. autosummary:: + :toctree: generated + :nosignatures: + :template: myintegrator.rst + + cram.IPFCramSolver .. autosummary:: :toctree: generated @@ -196,4 +203,3 @@ the following abstract base classes: abc.Integrator abc.SIIntegrator abc.DepSystemSolver - abc.IPFCramSolver diff --git a/openmc/deplete/__init__.py b/openmc/deplete/__init__.py index f0b04c3de5..b54d7f11d3 100644 --- a/openmc/deplete/__init__.py +++ b/openmc/deplete/__init__.py @@ -4,12 +4,11 @@ openmc.deplete A depletion front-end tool. """ -from sys import exit +import sys +from unittest.mock import Mock from h5py import get_config -from unittest.mock import Mock - from .dummy_comm import DummyCommunicator try: @@ -22,7 +21,7 @@ try: if not get_config().mpi and comm.size > 1: # Raise exception only on process 0 if comm.rank: - exit() + sys.exit() raise RuntimeError( "Need parallel HDF5 installed to perform depletion with MPI" ) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 0bcd1eb2cf..462d0f3460 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -28,7 +28,7 @@ from .results_list import ResultsList __all__ = [ "OperatorResult", "TransportOperator", "ReactionRateHelper", "EnergyHelper", "FissionYieldHelper", "TalliedFissionYieldHelper", - "Integrator", "SIIntegrator"] + "Integrator", "SIIntegrator", "DepSystemSolver"] OperatorResult = namedtuple('OperatorResult', ['k', 'rates']) diff --git a/openmc/deplete/cram.py b/openmc/deplete/cram.py index f4a847907b..c684b33943 100644 --- a/openmc/deplete/cram.py +++ b/openmc/deplete/cram.py @@ -17,7 +17,7 @@ from .abc import DepSystemSolver __all__ = [ "deplete", "timed_deplete", "CRAM16", "CRAM48", - "Cram16Solver", "Cram48Solver"] + "Cram16Solver", "Cram48Solver", "IPFCramSolver"] def deplete(chain, x, rates, dt, matrix_func=None): @@ -88,16 +88,11 @@ def timed_deplete(*args, **kwargs): class IPFCramSolver(DepSystemSolver): - r"""Class for solving depletion systems with IPF Cram + r"""CRAM depletion solver that uses incomplete partial factorization Provides a :meth:`__call__` that utilizes an incomplete partial factorization (IPF) for the Chebyshev Rational Approximation - Method (CRAM) - - M. Pusa, "Higher-Order Chebyshev Rational Approximation - Method and Application to Burnup Equations," Nuclear Science And - Engineering, 182:3,297-318 - `DOI: 10.13182/NSE15-26 `_ + Method (CRAM), as described in the following paper: M. Pusa, "`Higher-Order Chebyshev Rational Approximation Method and Application to Burnup Equations `_," Nucl. Sci. Eng., 182:3, 297-318. Parameters ---------- @@ -256,4 +251,3 @@ Cram48Solver = IPFCramSolver(c48_alpha, c48_theta, c48_alpha0) del c48_alpha, c48_alpha0, c48_theta, alpha_r, alpha_i, theta_r, theta_i CRAM48 = Cram48Solver.__call__ - From 86c081b28b16827d29416dc3a08eb7d07b25bd31 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 4 Oct 2019 13:59:19 -0500 Subject: [PATCH 108/158] Update MG mode part 3 notebook --- examples/jupyter/mg-mode-part-iii.ipynb | 433 ++++++++++++------------ 1 file changed, 210 insertions(+), 223 deletions(-) diff --git a/examples/jupyter/mg-mode-part-iii.ipynb b/examples/jupyter/mg-mode-part-iii.ipynb index 2cf9981889..e953d64d33 100644 --- a/examples/jupyter/mg-mode-part-iii.ipynb +++ b/examples/jupyter/mg-mode-part-iii.ipynb @@ -30,7 +30,6 @@ "\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", - "\n", "import openmc\n", "\n", "%matplotlib inline" @@ -40,7 +39,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We will be running a rodded 8x8 assembly with Gadolinia fuel pins. Let's create all the elemental data we would need for this case." + "We will be running a rodded 8x8 assembly with Gadolinia fuel pins. Let's start by creating the materials that we will use later.\n", + "\n", + "Material Definition Simplifications:\n", + "\n", + "- This model will be run at room temperature so the NNDC ENDF-B/VII.1 data set can be used but the water density will be representative of a module with around 20% voiding. This water density will be non-physically used in all regions of the problem.\n", + "- Steel is composed of more than just iron, but we will only treat it as such here." ] }, { @@ -48,67 +52,43 @@ "execution_count": 2, "metadata": {}, "outputs": [], - "source": [ - "# Instantiate some elements\n", - "elements = {}\n", - "for elem in ['H', 'O', 'U', 'Zr', 'Gd', 'B', 'C', 'Fe']:\n", - " elements[elem] = openmc.Element(elem)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "With the elements we defined, we will now create the materials we will use later.\n", - "\n", - "Material Definition Simplifications:\n", - "\n", - "- This model will be run at room temperature so the NNDC ENDF-B/VII.1 data set can be used but the water density will be representative of a module with around 20% voiding. This water density will be non-physically used in all regions of the problem.\n", - "- Steel is composed of more than just iron, but we will only treat it as such here.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], "source": [ "materials = {}\n", "\n", "# Fuel\n", "materials['Fuel'] = openmc.Material(name='Fuel')\n", "materials['Fuel'].set_density('g/cm3', 10.32)\n", - "materials['Fuel'].add_element(elements['O'], 2)\n", - "materials['Fuel'].add_element(elements['U'], 1, enrichment=3.)\n", + "materials['Fuel'].add_element('O', 2)\n", + "materials['Fuel'].add_element('U', 1, enrichment=3.)\n", "\n", "# Gadolinia bearing fuel\n", "materials['Gad'] = openmc.Material(name='Gad')\n", "materials['Gad'].set_density('g/cm3', 10.23)\n", - "materials['Gad'].add_element(elements['O'], 2)\n", - "materials['Gad'].add_element(elements['U'], 1, enrichment=3.)\n", - "materials['Gad'].add_element(elements['Gd'], .02)\n", + "materials['Gad'].add_element('O', 2)\n", + "materials['Gad'].add_element('U', 1, enrichment=3.)\n", + "materials['Gad'].add_element('Gd', .02)\n", "\n", "# Zircaloy\n", "materials['Zirc2'] = openmc.Material(name='Zirc2')\n", "materials['Zirc2'].set_density('g/cm3', 6.55)\n", - "materials['Zirc2'].add_element(elements['Zr'], 1)\n", + "materials['Zirc2'].add_element('Zr', 1)\n", "\n", "# Boiling Water\n", "materials['Water'] = openmc.Material(name='Water')\n", "materials['Water'].set_density('g/cm3', 0.6)\n", - "materials['Water'].add_element(elements['H'], 2)\n", - "materials['Water'].add_element(elements['O'], 1)\n", + "materials['Water'].add_element('H', 2)\n", + "materials['Water'].add_element('O', 1)\n", "\n", "# Boron Carbide for the Control Rods\n", "materials['B4C'] = openmc.Material(name='B4C')\n", "materials['B4C'].set_density('g/cm3', 0.7 * 2.52)\n", - "materials['B4C'].add_element(elements['B'], 4)\n", - "materials['B4C'].add_element(elements['C'], 1)\n", + "materials['B4C'].add_element('B', 4)\n", + "materials['B4C'].add_element('C', 1)\n", "\n", "# Steel \n", "materials['Steel'] = openmc.Material(name='Steel')\n", "materials['Steel'].set_density('g/cm3', 7.75)\n", - "materials['Steel'].add_element(elements['Fe'], 1)" + "materials['Steel'].add_element('Fe', 1)" ] }, { @@ -120,7 +100,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -147,7 +127,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -164,27 +144,27 @@ "surfaces = {}\n", "\n", "# Create boundary planes to surround the geometry\n", - "surfaces['Global x-'] = openmc.XPlane(x0=0., boundary_type='reflective')\n", - "surfaces['Global x+'] = openmc.XPlane(x0=length, boundary_type='reflective')\n", - "surfaces['Global y-'] = openmc.YPlane(y0=0., boundary_type='reflective')\n", - "surfaces['Global y+'] = openmc.YPlane(y0=length, boundary_type='reflective')\n", + "surfaces['Global x-'] = openmc.XPlane(0., boundary_type='reflective')\n", + "surfaces['Global x+'] = openmc.XPlane(length, boundary_type='reflective')\n", + "surfaces['Global y-'] = openmc.YPlane(0., boundary_type='reflective')\n", + "surfaces['Global y+'] = openmc.YPlane(length, boundary_type='reflective')\n", "\n", "# Create cylinders for the fuel and clad\n", - "surfaces['Fuel Radius'] = openmc.ZCylinder(R=fuel_rad)\n", - "surfaces['Clad Radius'] = openmc.ZCylinder(R=clad_rad)\n", + "surfaces['Fuel Radius'] = openmc.ZCylinder(r=fuel_rad)\n", + "surfaces['Clad Radius'] = openmc.ZCylinder(r=clad_rad)\n", "\n", - "surfaces['Assembly x-'] = openmc.XPlane(x0=pin_pitch)\n", - "surfaces['Assembly x+'] = openmc.XPlane(x0=length - pin_pitch)\n", - "surfaces['Assembly y-'] = openmc.YPlane(y0=pin_pitch)\n", - "surfaces['Assembly y+'] = openmc.YPlane(y0=length - pin_pitch)\n", + "surfaces['Assembly x-'] = openmc.XPlane(pin_pitch)\n", + "surfaces['Assembly x+'] = openmc.XPlane(length - pin_pitch)\n", + "surfaces['Assembly y-'] = openmc.YPlane(pin_pitch)\n", + "surfaces['Assembly y+'] = openmc.YPlane(length - pin_pitch)\n", "\n", "# Set surfaces for the control blades\n", - "surfaces['Top Blade y-'] = openmc.YPlane(y0=length - rod_thick)\n", - "surfaces['Top Blade x-'] = openmc.XPlane(x0=pin_pitch)\n", - "surfaces['Top Blade x+'] = openmc.XPlane(x0=rod_span)\n", - "surfaces['Left Blade x+'] = openmc.XPlane(x0=rod_thick)\n", - "surfaces['Left Blade y-'] = openmc.YPlane(y0=length - rod_span)\n", - "surfaces['Left Blade y+'] = openmc.YPlane(y0=9. * pin_pitch)" + "surfaces['Top Blade y-'] = openmc.YPlane(length - rod_thick)\n", + "surfaces['Top Blade x-'] = openmc.XPlane(pin_pitch)\n", + "surfaces['Top Blade x+'] = openmc.XPlane(rod_span)\n", + "surfaces['Left Blade x+'] = openmc.XPlane(rod_thick)\n", + "surfaces['Left Blade y-'] = openmc.YPlane(length - rod_span)\n", + "surfaces['Left Blade y+'] = openmc.YPlane(9. * pin_pitch)" ] }, { @@ -196,7 +176,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -240,7 +220,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -280,7 +260,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -319,7 +299,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -353,7 +333,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -375,27 +355,29 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 11, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ - "" + "
" ] }, - "metadata": {}, + "metadata": { + "needs_background": "light" + }, "output_type": "display_data" } ], @@ -422,7 +404,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -444,7 +426,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 12, "metadata": {}, "outputs": [], "source": [ @@ -481,7 +463,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ @@ -499,7 +481,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ @@ -520,7 +502,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -540,7 +522,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 16, "metadata": {}, "outputs": [], "source": [ @@ -572,7 +554,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 17, "metadata": {}, "outputs": [], "source": [ @@ -602,7 +584,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 18, "metadata": {}, "outputs": [], "source": [ @@ -632,7 +614,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 19, "metadata": {}, "outputs": [], "source": [ @@ -652,14 +634,14 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "/home/nelsonag/git/openmc/openmc/mgxs/mgxs.py:4116: UserWarning: The legendre order will be ignored since the scatter format is set to histogram\n", + "/home/romano/openmc/openmc/mgxs/mgxs.py:4144: UserWarning: The legendre order will be ignored since the scatter format is set to histogram\n", " warnings.warn(msg)\n" ] } @@ -679,7 +661,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 21, "metadata": {}, "outputs": [], "source": [ @@ -698,26 +680,26 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=1.\n", + "/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=1.\n", " warn(msg, IDWarning)\n", - "/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=2.\n", + "/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=2.\n", " warn(msg, IDWarning)\n", - "/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=11.\n", + "/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=11.\n", " warn(msg, IDWarning)\n", - "/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=21.\n", + "/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=21.\n", " warn(msg, IDWarning)\n", - "/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=22.\n", + "/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=22.\n", " warn(msg, IDWarning)\n", - "/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=12.\n", + "/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=12.\n", " warn(msg, IDWarning)\n", - "/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=18.\n", + "/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=18.\n", " warn(msg, IDWarning)\n" ] } @@ -749,57 +731,58 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "\n", - " %%%%%%%%%%%%%%%\n", - " %%%%%%%%%%%%%%%%%%%%%%%%\n", - " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n", - " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n", - " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n", - " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n", - " %%%%%%%%%%%%%%%%%%%%%%%%\n", + " %%%%%%%%%%%%%%%\n", + " %%%%%%%%%%%%%%%%%%%%%%%%\n", + " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n", + " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n", + " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n", + " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n", " %%%%%%%%%%%%%%%%%%%%%%%%\n", - " ############### %%%%%%%%%%%%%%%%%%%%%%%%\n", - " ################## %%%%%%%%%%%%%%%%%%%%%%%\n", - " ################### %%%%%%%%%%%%%%%%%%%%%%%\n", - " #################### %%%%%%%%%%%%%%%%%%%%%%\n", - " ##################### %%%%%%%%%%%%%%%%%%%%%\n", - " ###################### %%%%%%%%%%%%%%%%%%%%\n", - " ####################### %%%%%%%%%%%%%%%%%%\n", - " ####################### %%%%%%%%%%%%%%%%%\n", - " ###################### %%%%%%%%%%%%%%%%%\n", - " #################### %%%%%%%%%%%%%%%%%\n", - " ################# %%%%%%%%%%%%%%%%%\n", - " ############### %%%%%%%%%%%%%%%%\n", - " ############ %%%%%%%%%%%%%%%\n", - " ######## %%%%%%%%%%%%%%\n", - " %%%%%%%%%%%\n", + " %%%%%%%%%%%%%%%%%%%%%%%%\n", + " ############### %%%%%%%%%%%%%%%%%%%%%%%%\n", + " ################## %%%%%%%%%%%%%%%%%%%%%%%\n", + " ################### %%%%%%%%%%%%%%%%%%%%%%%\n", + " #################### %%%%%%%%%%%%%%%%%%%%%%\n", + " ##################### %%%%%%%%%%%%%%%%%%%%%\n", + " ###################### %%%%%%%%%%%%%%%%%%%%\n", + " ####################### %%%%%%%%%%%%%%%%%%\n", + " ####################### %%%%%%%%%%%%%%%%%\n", + " ###################### %%%%%%%%%%%%%%%%%\n", + " #################### %%%%%%%%%%%%%%%%%\n", + " ################# %%%%%%%%%%%%%%%%%\n", + " ############### %%%%%%%%%%%%%%%%\n", + " ############ %%%%%%%%%%%%%%%\n", + " ######## %%%%%%%%%%%%%%\n", + " %%%%%%%%%%%\n", "\n", " | The OpenMC Monte Carlo Code\n", - " Copyright | 2011-2018 Massachusetts Institute of Technology\n", + " Copyright | 2011-2019 MIT and OpenMC contributors\n", " License | http://openmc.readthedocs.io/en/latest/license.html\n", - " Version | 0.10.0\n", - " Git SHA1 | 6c2d82a4d7dfe10312329d5969568fc03a698416\n", - " Date/Time | 2018-04-24 19:15:17\n", - " OpenMP Threads | 8\n", + " Version | 0.11.0-dev\n", + " Git SHA1 | ed7123f4e7ce7b097c4b2bfb0ef5283eaa8afaea\n", + " Date/Time | 2019-10-04 10:54:35\n", + " OpenMP Threads | 4\n", "\n", + " Minimum neutron data temperature: 294.000000 K\n", + " Maximum neutron data temperature: 294.000000 K\n", "\n", " ====================> K EIGENVALUE SIMULATION <====================\n", "\n", "\n", " ============================> RESULTS <============================\n", "\n", - " k-effective (Collision) = 0.83880 +/- 0.00101\n", - " k-effective (Track-length) = 0.83917 +/- 0.00118\n", - " k-effective (Absorption) = 0.83859 +/- 0.00104\n", - " Combined k-effective = 0.83879 +/- 0.00086\n", - " Leakage Fraction = 0.00000 +/- 0.00000\n", + " k-effective (Collision) = 0.83866 +/- 0.00102\n", + " k-effective (Track-length) = 0.83799 +/- 0.00118\n", + " k-effective (Absorption) = 0.83968 +/- 0.00103\n", + " Combined k-effective = 0.83900 +/- 0.00085\n", + " Leakage Fraction = 0.00000 +/- 0.00000\n", "\n" ] } @@ -818,7 +801,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 24, "metadata": {}, "outputs": [], "source": [ @@ -841,7 +824,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 25, "metadata": {}, "outputs": [], "source": [ @@ -858,7 +841,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 26, "metadata": {}, "outputs": [], "source": [ @@ -875,7 +858,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 27, "metadata": {}, "outputs": [], "source": [ @@ -912,14 +895,14 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 28, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Universe instance already exists with id=0.\n", + "/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Universe instance already exists with id=0.\n", " warn(msg, IDWarning)\n" ] } @@ -947,7 +930,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 29, "metadata": {}, "outputs": [], "source": [ @@ -967,7 +950,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 30, "metadata": {}, "outputs": [], "source": [ @@ -990,27 +973,29 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 32, + "execution_count": 31, "metadata": {}, "output_type": "execute_result" }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ - "" + "
" ] }, - "metadata": {}, + "metadata": { + "needs_background": "light" + }, "output_type": "display_data" } ], @@ -1031,45 +1016,44 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 32, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "\n", - " %%%%%%%%%%%%%%%\n", - " %%%%%%%%%%%%%%%%%%%%%%%%\n", - " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n", - " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n", - " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n", - " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n", - " %%%%%%%%%%%%%%%%%%%%%%%%\n", + " %%%%%%%%%%%%%%%\n", + " %%%%%%%%%%%%%%%%%%%%%%%%\n", + " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n", + " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n", + " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n", + " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n", " %%%%%%%%%%%%%%%%%%%%%%%%\n", - " ############### %%%%%%%%%%%%%%%%%%%%%%%%\n", - " ################## %%%%%%%%%%%%%%%%%%%%%%%\n", - " ################### %%%%%%%%%%%%%%%%%%%%%%%\n", - " #################### %%%%%%%%%%%%%%%%%%%%%%\n", - " ##################### %%%%%%%%%%%%%%%%%%%%%\n", - " ###################### %%%%%%%%%%%%%%%%%%%%\n", - " ####################### %%%%%%%%%%%%%%%%%%\n", - " ####################### %%%%%%%%%%%%%%%%%\n", - " ###################### %%%%%%%%%%%%%%%%%\n", - " #################### %%%%%%%%%%%%%%%%%\n", - " ################# %%%%%%%%%%%%%%%%%\n", - " ############### %%%%%%%%%%%%%%%%\n", - " ############ %%%%%%%%%%%%%%%\n", - " ######## %%%%%%%%%%%%%%\n", - " %%%%%%%%%%%\n", + " %%%%%%%%%%%%%%%%%%%%%%%%\n", + " ############### %%%%%%%%%%%%%%%%%%%%%%%%\n", + " ################## %%%%%%%%%%%%%%%%%%%%%%%\n", + " ################### %%%%%%%%%%%%%%%%%%%%%%%\n", + " #################### %%%%%%%%%%%%%%%%%%%%%%\n", + " ##################### %%%%%%%%%%%%%%%%%%%%%\n", + " ###################### %%%%%%%%%%%%%%%%%%%%\n", + " ####################### %%%%%%%%%%%%%%%%%%\n", + " ####################### %%%%%%%%%%%%%%%%%\n", + " ###################### %%%%%%%%%%%%%%%%%\n", + " #################### %%%%%%%%%%%%%%%%%\n", + " ################# %%%%%%%%%%%%%%%%%\n", + " ############### %%%%%%%%%%%%%%%%\n", + " ############ %%%%%%%%%%%%%%%\n", + " ######## %%%%%%%%%%%%%%\n", + " %%%%%%%%%%%\n", "\n", " | The OpenMC Monte Carlo Code\n", - " Copyright | 2011-2018 Massachusetts Institute of Technology\n", + " Copyright | 2011-2019 MIT and OpenMC contributors\n", " License | http://openmc.readthedocs.io/en/latest/license.html\n", - " Version | 0.10.0\n", - " Git SHA1 | 6c2d82a4d7dfe10312329d5969568fc03a698416\n", - " Date/Time | 2018-04-24 19:16:03\n", - " OpenMP Threads | 8\n", + " Version | 0.11.0-dev\n", + " Git SHA1 | ed7123f4e7ce7b097c4b2bfb0ef5283eaa8afaea\n", + " Date/Time | 2019-10-04 10:56:58\n", + " OpenMP Threads | 4\n", "\n", "\n", " ====================> K EIGENVALUE SIMULATION <====================\n", @@ -1077,11 +1061,11 @@ "\n", " ============================> RESULTS <============================\n", "\n", - " k-effective (Collision) = 0.82313 +/- 0.00100\n", - " k-effective (Track-length) = 0.82363 +/- 0.00102\n", - " k-effective (Absorption) = 0.82532 +/- 0.00076\n", - " Combined k-effective = 0.82484 +/- 0.00067\n", - " Leakage Fraction = 0.00000 +/- 0.00000\n", + " k-effective (Collision) = 0.82471 +/- 0.00104\n", + " k-effective (Track-length) = 0.82466 +/- 0.00103\n", + " k-effective (Absorption) = 0.82482 +/- 0.00075\n", + " Combined k-effective = 0.82477 +/- 0.00068\n", + " Leakage Fraction = 0.00000 +/- 0.00000\n", "\n" ] } @@ -1100,7 +1084,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 33, "metadata": {}, "outputs": [], "source": [ @@ -1125,14 +1109,14 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 34, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Universe instance already exists with id=0.\n", + "/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Universe instance already exists with id=0.\n", " warn(msg, IDWarning)\n" ] } @@ -1153,45 +1137,44 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 35, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "\n", - " %%%%%%%%%%%%%%%\n", - " %%%%%%%%%%%%%%%%%%%%%%%%\n", - " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n", - " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n", - " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n", - " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n", - " %%%%%%%%%%%%%%%%%%%%%%%%\n", + " %%%%%%%%%%%%%%%\n", + " %%%%%%%%%%%%%%%%%%%%%%%%\n", + " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n", + " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n", + " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n", + " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n", " %%%%%%%%%%%%%%%%%%%%%%%%\n", - " ############### %%%%%%%%%%%%%%%%%%%%%%%%\n", - " ################## %%%%%%%%%%%%%%%%%%%%%%%\n", - " ################### %%%%%%%%%%%%%%%%%%%%%%%\n", - " #################### %%%%%%%%%%%%%%%%%%%%%%\n", - " ##################### %%%%%%%%%%%%%%%%%%%%%\n", - " ###################### %%%%%%%%%%%%%%%%%%%%\n", - " ####################### %%%%%%%%%%%%%%%%%%\n", - " ####################### %%%%%%%%%%%%%%%%%\n", - " ###################### %%%%%%%%%%%%%%%%%\n", - " #################### %%%%%%%%%%%%%%%%%\n", - " ################# %%%%%%%%%%%%%%%%%\n", - " ############### %%%%%%%%%%%%%%%%\n", - " ############ %%%%%%%%%%%%%%%\n", - " ######## %%%%%%%%%%%%%%\n", - " %%%%%%%%%%%\n", + " %%%%%%%%%%%%%%%%%%%%%%%%\n", + " ############### %%%%%%%%%%%%%%%%%%%%%%%%\n", + " ################## %%%%%%%%%%%%%%%%%%%%%%%\n", + " ################### %%%%%%%%%%%%%%%%%%%%%%%\n", + " #################### %%%%%%%%%%%%%%%%%%%%%%\n", + " ##################### %%%%%%%%%%%%%%%%%%%%%\n", + " ###################### %%%%%%%%%%%%%%%%%%%%\n", + " ####################### %%%%%%%%%%%%%%%%%%\n", + " ####################### %%%%%%%%%%%%%%%%%\n", + " ###################### %%%%%%%%%%%%%%%%%\n", + " #################### %%%%%%%%%%%%%%%%%\n", + " ################# %%%%%%%%%%%%%%%%%\n", + " ############### %%%%%%%%%%%%%%%%\n", + " ############ %%%%%%%%%%%%%%%\n", + " ######## %%%%%%%%%%%%%%\n", + " %%%%%%%%%%%\n", "\n", " | The OpenMC Monte Carlo Code\n", - " Copyright | 2011-2018 Massachusetts Institute of Technology\n", + " Copyright | 2011-2019 MIT and OpenMC contributors\n", " License | http://openmc.readthedocs.io/en/latest/license.html\n", - " Version | 0.10.0\n", - " Git SHA1 | 6c2d82a4d7dfe10312329d5969568fc03a698416\n", - " Date/Time | 2018-04-24 19:16:12\n", - " OpenMP Threads | 8\n", + " Version | 0.11.0-dev\n", + " Git SHA1 | ed7123f4e7ce7b097c4b2bfb0ef5283eaa8afaea\n", + " Date/Time | 2019-10-04 10:57:24\n", + " OpenMP Threads | 4\n", "\n", "\n", " ====================> K EIGENVALUE SIMULATION <====================\n", @@ -1199,11 +1182,11 @@ "\n", " ============================> RESULTS <============================\n", "\n", - " k-effective (Collision) = 0.83745 +/- 0.00108\n", - " k-effective (Track-length) = 0.83712 +/- 0.00108\n", - " k-effective (Absorption) = 0.83694 +/- 0.00078\n", - " Combined k-effective = 0.83695 +/- 0.00070\n", - " Leakage Fraction = 0.00000 +/- 0.00000\n", + " k-effective (Collision) = 0.83564 +/- 0.00103\n", + " k-effective (Track-length) = 0.83572 +/- 0.00104\n", + " k-effective (Absorption) = 0.83478 +/- 0.00073\n", + " Combined k-effective = 0.83504 +/- 0.00066\n", + " Leakage Fraction = 0.00000 +/- 0.00000\n", "\n" ] } @@ -1225,7 +1208,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 36, "metadata": {}, "outputs": [], "source": [ @@ -1248,7 +1231,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 37, "metadata": {}, "outputs": [], "source": [ @@ -1257,8 +1240,8 @@ "angle_mg_keff = angle_mgsp.k_combined\n", "\n", "# Find eigenvalue bias\n", - "iso_bias = 1.0E5 * (ce_keff - iso_mg_keff)\n", - "angle_bias = 1.0E5 * (ce_keff - angle_mg_keff)" + "iso_bias = 1.0e5 * (ce_keff - iso_mg_keff)\n", + "angle_bias = 1.0e5 * (ce_keff - angle_mg_keff)" ] }, { @@ -1270,15 +1253,15 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 38, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Isotropic to CE Bias [pcm]: 1394.6\n", - "Angle to CE Bias [pcm]: 183.4\n" + "Isotropic to CE Bias [pcm]: 1423.5\n", + "Angle to CE Bias [pcm]: 396.6\n" ] } ], @@ -1305,19 +1288,21 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 39, "metadata": { "scrolled": false }, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ - "" + "
" ] }, - "metadata": {}, + "metadata": { + "needs_background": "light" + }, "output_type": "display_data" } ], @@ -1363,27 +1348,29 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 40, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 41, + "execution_count": 40, "metadata": {}, "output_type": "execute_result" }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ - "" + "
" ] }, - "metadata": {}, + "metadata": { + "needs_background": "light" + }, "output_type": "display_data" } ], @@ -1430,7 +1417,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.7" + "version": "3.7.0" } }, "nbformat": 4, From 5b195b6e9fed2b5c144bdaa9aa6feea3b4fa6d12 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 4 Oct 2019 14:00:22 -0500 Subject: [PATCH 109/158] Various fixes and updates in documentation --- docs/source/io_formats/statepoint.rst | 3 +- docs/source/methods/cross_sections.rst | 25 ++-- docs/source/methods/energy_deposition.rst | 10 +- docs/source/methods/introduction.rst | 2 +- docs/source/methods/neutron_physics.rst | 5 +- docs/source/pythonapi/data.rst | 160 +++++++++++----------- docs/source/pythonapi/deplete.rst | 4 +- docs/source/usersguide/cross_sections.rst | 18 +-- docs/source/usersguide/geometry.rst | 13 +- docs/source/usersguide/settings.rst | 15 -- 10 files changed, 125 insertions(+), 130 deletions(-) diff --git a/docs/source/io_formats/statepoint.rst b/docs/source/io_formats/statepoint.rst index 5a373580df..9156e848f3 100644 --- a/docs/source/io_formats/statepoint.rst +++ b/docs/source/io_formats/statepoint.rst @@ -109,7 +109,8 @@ The current version of the statepoint file format is 17.0. **/tallies/tally /** -:Attributes: - **internal** (*int*) -- Flag indicating the presence of tally +:Attributes: + - **internal** (*int*) -- Flag indicating the presence of tally data (0) or absence of tally data (1). All user defined tallies will have a value of 0 unless otherwise instructed. diff --git a/docs/source/methods/cross_sections.rst b/docs/source/methods/cross_sections.rst index dbf078654f..70d80a5376 100644 --- a/docs/source/methods/cross_sections.rst +++ b/docs/source/methods/cross_sections.rst @@ -8,13 +8,14 @@ Cross Section Representations Continuous-Energy Data ---------------------- -The data governing the interaction of neutrons with -various nuclei for continous-energy problems are represented using the ACE -format which is used by MCNP_ and Serpent_. ACE-format data can be generated -with the NJOY_ nuclear data processing system which converts raw -`ENDF/B data`_ into linearly-interpolable data as required by most Monte Carlo -codes. The use of a standard cross section format allows for a direct comparison -of OpenMC with other codes since the same cross section libraries can be used. +In OpenMC, the data governing the interaction of neutrons with various nuclei +for continous-energy problems are represented using an HDF5 format that can be +produced by converting files in the ACE format, which is used by MCNP_ and +Serpent_. ACE-format data can be generated with the NJOY_ nuclear data +processing system, which converts raw `ENDF/B data`_ into linearly-interpolable +data as required by most Monte Carlo codes. Since ACE-format data can be +converted into OpenMC's HDF5 format, it is possible to perform direct comparison +of OpenMC with other codes using the same underlying nuclear data library. The ACE format contains continuous-energy cross sections for the following types of reactions: elastic scattering, fission (or first-chance fission, @@ -31,7 +32,7 @@ data can be used. Energy Grid Methods ------------------- -The method by which continuous energy cross sections for each nuclide in a +The method by which continuous-energy cross sections for each nuclide in a problem are stored as a function of energy can have a substantial effect on the performance of a Monte Carlo simulation. Since the ACE format is based on linearly-interpolable cross sections, each nuclide has cross sections tabulated @@ -72,9 +73,9 @@ Windowed Multipole Representation --------------------------------- In addition to the usual pointwise representation of cross sections, OpenMC -offers support for an experimental data format called windowed multipole (WMP). -This data format requires less memory than pointwise cross sections, and it -allows on-the-fly Doppler broadening to arbitrary temperature. +offers support for an data format called windowed multipole (WMP). This data +format requires less memory than pointwise cross sections, and it allows +on-the-fly Doppler broadening to arbitrary temperature. The multipole method was introduced by Hwang_ and the faster windowed multipole method by Josey_. In the multipole format, cross section resonances are @@ -258,7 +259,7 @@ where a material has a very large cross sections relative to the other material used to minimize this error. Finally, the above options for representing the physics do not have to be -consistent across the problem. The number of groups and the structure, however, +consistent across the problem. The number of groups and the structure, however, does have to be consistent across the data sets. That is to say that each microscopic or macroscopic data set does not have to apply the same scattering expansion, treatment of multiplicity or angular representation of the cross diff --git a/docs/source/methods/energy_deposition.rst b/docs/source/methods/energy_deposition.rst index af4962c553..86dfb9bf9d 100644 --- a/docs/source/methods/energy_deposition.rst +++ b/docs/source/methods/energy_deposition.rst @@ -4,11 +4,11 @@ Heating and Energy Deposition ============================= -As particles traverse a problem, some portion of their energy is deposited at +As particles traverse a problem, some portion of their energy is deposited at collision sites. This energy is deposited when charged particles, including electrons and recoil nuclei, undergo electromagnetic interactions with surrounding electons and ions. The information describing how much energy -is deposited for a specific reaction is referred to as +is deposited for a specific reaction is referred to as "heating numbers" and can be computed using a program like NJOY with the ``heatr`` module. @@ -108,7 +108,7 @@ Neutron Transport For this case, OpenMC instructs ``heatr`` to produce heating coefficients assuming that energy from photons, :math:`E_{\gamma, p}` and :math:`E_{\gamma, d}`, is deposited at the fission site. -Let :math:`N901` represent the total heating number returned from this ``heatr`` +Let :math:`N901` represent the total heating number returned from this ``heatr`` run with :math:`N918` reflecting fission heating computed from NJOY. :math:`M901` represent the following modification @@ -119,7 +119,7 @@ run with :math:`N918` reflecting fission heating computed from NJOY. + E_{i, \gamma, d}\right]\sigma_{i, f}(E). This modified heating data is stored as the MT=901 reaction and will be scored -if ``901`` is included in :attr:`openmc.Tally.scores`. +if ``heating-local`` is included in :attr:`openmc.Tally.scores`. Coupled neutron-photon transport -------------------------------- @@ -146,4 +146,4 @@ References .. [Mack97] Abdou, M.A., Maynard, C.W., and Wright, R.Q. MACK: computer program to calculate neutron energy release parameters (fluence-to-kerma factors) and multigroup neutron reaction cross sections from nuclear data - in ENDF Format. Oak Ridge National Laboratory report ORNL-TM-3994. \ No newline at end of file + in ENDF Format. Oak Ridge National Laboratory report ORNL-TM-3994. diff --git a/docs/source/methods/introduction.rst b/docs/source/methods/introduction.rst index 0b9544d632..adb52c02e1 100644 --- a/docs/source/methods/introduction.rst +++ b/docs/source/methods/introduction.rst @@ -139,7 +139,7 @@ be performed before the run is finished. This include the following: - If requested, a source file is written to disk. - - All allocatable arrays are deallocated. + - Dynamically-allocated memory should be freed. .. _probability distributions: https://en.wikipedia.org/wiki/Probability_distribution .. _Monte Carlo: https://en.wikipedia.org/wiki/Monte_Carlo_method diff --git a/docs/source/methods/neutron_physics.rst b/docs/source/methods/neutron_physics.rst index 71e9b6bcdd..384e79bdd6 100644 --- a/docs/source/methods/neutron_physics.rst +++ b/docs/source/methods/neutron_physics.rst @@ -1298,11 +1298,10 @@ section over the range of velocities considered: where it should be noted that the maximum is taken over the range :math:`[v_n - 4/\beta, 4_n + 4\beta]`. This method is known as Doppler broadening rejection correction (DBRC) and was first introduced by `Becker et al.`_. OpenMC has an -implementation of DBRC as well as an accelerated sampling method that are -described fully in `Walsh et al.`_ +implementation of DBRC as well as an accelerated sampling method that samples the `relative velocity`_ directly. .. _Becker et al.: https://doi.org/10.1016/j.anucene.2008.12.001 -.. _Walsh et al.: https://doi.org/10.1016/j.anucene.2014.01.017 +.. _relative velocity: https://doi.org/10.1016/j.anucene.2017.12.044 .. _sab_tables: diff --git a/docs/source/pythonapi/data.rst b/docs/source/pythonapi/data.rst index 92cb785481..be29d7c0eb 100644 --- a/docs/source/pythonapi/data.rst +++ b/docs/source/pythonapi/data.rst @@ -2,6 +2,8 @@ :mod:`openmc.data` -- Nuclear Data Interface -------------------------------------------- +.. module:: openmc.data + Core Classes ------------ @@ -13,15 +15,15 @@ and product yields. :nosignatures: :template: myclass.rst - openmc.data.IncidentNeutron - openmc.data.Reaction - openmc.data.Product - openmc.data.FissionEnergyRelease - openmc.data.DataLibrary - openmc.data.Decay - openmc.data.FissionProductYields - openmc.data.WindowedMultipole - openmc.data.ProbabilityTables + IncidentNeutron + Reaction + Product + FissionEnergyRelease + DataLibrary + Decay + FissionProductYields + WindowedMultipole + ProbabilityTables The following classes are used for storing atomic data (incident photon cross sections, atomic relaxation): @@ -31,9 +33,9 @@ sections, atomic relaxation): :nosignatures: :template: myclass.rst - openmc.data.IncidentPhoton - openmc.data.PhotonReaction - openmc.data.AtomicRelaxation + IncidentPhoton + PhotonReaction + AtomicRelaxation The following classes are used for storing thermal neutron scattering data: @@ -43,10 +45,10 @@ The following classes are used for storing thermal neutron scattering data: :nosignatures: :template: myclass.rst - openmc.data.ThermalScattering - openmc.data.ThermalScatteringReaction - openmc.data.CoherentElastic - openmc.data.IncoherentElastic + ThermalScattering + ThermalScatteringReaction + CoherentElastic + IncoherentElastic Core Functions @@ -57,12 +59,12 @@ Core Functions :nosignatures: :template: myfunction.rst - openmc.data.atomic_mass - openmc.data.gnd_name - openmc.data.linearize - openmc.data.thin - openmc.data.water_density - openmc.data.zam + atomic_mass + gnd_name + linearize + thin + water_density + zam One-dimensional Functions ------------------------- @@ -72,13 +74,13 @@ One-dimensional Functions :nosignatures: :template: myclass.rst - openmc.data.Function1D - openmc.data.Tabulated1D - openmc.data.Polynomial - openmc.data.Combination - openmc.data.Sum - openmc.data.Regions1D - openmc.data.ResonancesWithBackground + Function1D + Tabulated1D + Polynomial + Combination + Sum + Regions1D + ResonancesWithBackground Angle-Energy Distributions -------------------------- @@ -88,27 +90,27 @@ Angle-Energy Distributions :nosignatures: :template: myclass.rst - openmc.data.AngleEnergy - openmc.data.KalbachMann - openmc.data.CorrelatedAngleEnergy - openmc.data.UncorrelatedAngleEnergy - openmc.data.NBodyPhaseSpace - openmc.data.LaboratoryAngleEnergy - openmc.data.AngleDistribution - openmc.data.EnergyDistribution - openmc.data.ArbitraryTabulated - openmc.data.GeneralEvaporation - openmc.data.MaxwellEnergy - openmc.data.Evaporation - openmc.data.WattEnergy - openmc.data.MadlandNix - openmc.data.DiscretePhoton - openmc.data.LevelInelastic - openmc.data.ContinuousTabular - openmc.data.CoherentElasticAE - openmc.data.IncoherentElasticAE - openmc.data.IncoherentElasticAEDiscrete - openmc.data.IncoherentInelasticAEDiscrete + AngleEnergy + KalbachMann + CorrelatedAngleEnergy + UncorrelatedAngleEnergy + NBodyPhaseSpace + LaboratoryAngleEnergy + AngleDistribution + EnergyDistribution + ArbitraryTabulated + GeneralEvaporation + MaxwellEnergy + Evaporation + WattEnergy + MadlandNix + DiscretePhoton + LevelInelastic + ContinuousTabular + CoherentElasticAE + IncoherentElasticAE + IncoherentElasticAEDiscrete + IncoherentInelasticAEDiscrete Resonance Data -------------- @@ -118,20 +120,20 @@ Resonance Data :nosignatures: :template: myclass.rst - openmc.data.Resonances - openmc.data.ResonanceRange - openmc.data.SingleLevelBreitWigner - openmc.data.MultiLevelBreitWigner - openmc.data.ReichMoore - openmc.data.RMatrixLimited - openmc.data.ResonanceCovariances - openmc.data.ResonanceCovarianceRange - openmc.data.SingleLevelBreitWignerCovariance - openmc.data.MultiLevelBreitWignerCovariance - openmc.data.ReichMooreCovariance - openmc.data.ParticlePair - openmc.data.SpinGroup - openmc.data.Unresolved + Resonances + ResonanceRange + SingleLevelBreitWigner + MultiLevelBreitWigner + ReichMoore + RMatrixLimited + ResonanceCovariances + ResonanceCovarianceRange + SingleLevelBreitWignerCovariance + MultiLevelBreitWignerCovariance + ReichMooreCovariance + ParticlePair + SpinGroup + Unresolved ACE Format ---------- @@ -144,8 +146,8 @@ Classes :nosignatures: :template: myclass.rst - openmc.data.ace.Library - openmc.data.ace.Table + ace.Library + ace.Table Functions +++++++++ @@ -155,7 +157,7 @@ Functions :nosignatures: :template: myfunction.rst - openmc.data.ace.ascii_to_binary + ace.ascii_to_binary ENDF Format ----------- @@ -168,7 +170,7 @@ Classes :nosignatures: :template: myclass.rst - openmc.data.endf.Evaluation + endf.Evaluation Functions +++++++++ @@ -178,13 +180,13 @@ Functions :nosignatures: :template: myfunction.rst - openmc.data.endf.float_endf - openmc.data.endf.get_cont_record - openmc.data.endf.get_evaluations - openmc.data.endf.get_head_record - openmc.data.endf.get_tab1_record - openmc.data.endf.get_tab2_record - openmc.data.endf.get_text_record + endf.float_endf + endf.get_cont_record + endf.get_evaluations + endf.get_head_record + endf.get_tab1_record + endf.get_tab2_record + endf.get_text_record NJOY Interface -------------- @@ -194,7 +196,7 @@ NJOY Interface :nosignatures: :template: myfunction.rst - openmc.data.njoy.run - openmc.data.njoy.make_pendf - openmc.data.njoy.make_ace - openmc.data.njoy.make_ace_thermal + njoy.run + njoy.make_pendf + njoy.make_ace + njoy.make_ace_thermal diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index b864529d16..b9970ffb11 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -1,11 +1,11 @@ .. _pythonapi_deplete: +.. module:: openmc.deplete + ---------------------------------- :mod:`openmc.deplete` -- Depletion ---------------------------------- -.. module:: openmc.deplete - Primary API ----------- diff --git a/docs/source/usersguide/cross_sections.rst b/docs/source/usersguide/cross_sections.rst index 636b44db46..144b1c1527 100644 --- a/docs/source/usersguide/cross_sections.rst +++ b/docs/source/usersguide/cross_sections.rst @@ -16,10 +16,10 @@ recommended to use one of the pregenerated libraries. Alternatively, if you have ACE format data that was produced with NJOY_, such as that distributed with MCNP_ or Serpent_, it can be converted to the HDF5 format using the :ref:`using the Python API `. Several sources provide openly available -ACE data including the `ENDF/B`_, JEFF_, and TENDL_ -libraries. In addition to tabulated cross sections in the HDF5 files, OpenMC -relies on :ref:`windowed multipole ` data to perform -on-the-fly Doppler broadening. +ACE data including the `ENDF/B`_, JEFF_, and TENDL_ libraries as well as the +`LANL Nuclear Data Team `_. In addition to +tabulated cross sections in the HDF5 files, OpenMC relies on :ref:`windowed +multipole ` data to perform on-the-fly Doppler broadening. In multi-group mode, OpenMC utilizes an HDF5-based library format which can be used to describe nuclide- or material-specific quantities. @@ -30,11 +30,11 @@ Environment Variables When :ref:`scripts_openmc` is run, it will look for several environment variables that indicate where cross sections can be found. While the location of -cross sections can also be indicated through the :class:`openmc.Materials` class -(or in the :ref:`materials.xml ` file), if you always use the same -set of cross section data, it is often easier to just set an environment -variable that will be picked up by default every time OpenMC is run. The -following environment variables are used: +cross sections can also be indicated through the +:attr:`openmc.Materials.cross_setion` attribute (or in the :ref:`materials.xml +` file), if you always use the same set of cross section data, it +is often easier to just set an environment variable that will be picked up by +default every time OpenMC is run. The following environment variables are used: :envvar:`OPENMC_CROSS_SECTIONS` Indicates the path to the :ref:`cross_sections.xml ` diff --git a/docs/source/usersguide/geometry.rst b/docs/source/usersguide/geometry.rst index 5e583cfd2b..dc7dd978eb 100644 --- a/docs/source/usersguide/geometry.rst +++ b/docs/source/usersguide/geometry.rst @@ -121,11 +121,11 @@ For many regions, a bounding-box can be determined automatically:: While a bounding box can be determined for regions involving half-spaces of spheres, cylinders, and axis-aligned planes, it generally cannot be determined if the region involves cones, non-axis-aligned planes, or other exotic -second-order surfaces. For example, the :func:`openmc.get_hexagonal_prism` +second-order surfaces. For example, the :func:`openmc.model.hexagonal_prism` function returns the interior region of a hexagonal prism; because it is bounded by a :class:`openmc.Plane`, trying to get its bounding box won't work:: - >>> hex = openmc.get_hexagonal_prism() + >>> hex = openmc.model.hexagonal_prism() >>> hex.bounding_box (array([-0.8660254, -inf, -inf]), array([ 0.8660254, inf, inf])) @@ -374,7 +374,7 @@ code would work:: hexlat.universes = [outer_ring, middle_ring, inner_ring] If you need to create a hexagonal boundary (composed of six planar surfaces) for -a hexagonal lattice, :func:`openmc.get_hexagonal_prism` can be used. +a hexagonal lattice, :func:`openmc.model.hexagonal_prism` can be used. .. _usersguide_geom_export: @@ -396,6 +396,13 @@ if needed, lattices, the last step is to create an instance of geom.root_universe = root_univ geom.export_to_xml() +Note that it's not strictly required to manually create a root universe. You can +also pass a list of cells to the :class:`openmc.Geometry` constructor and it +will handle creating the unverse:: + + geom = openmc.Geometry([cell1, cell2, cell3]) + geom.export_to_xml() + .. _constructive solid geometry: https://en.wikipedia.org/wiki/Constructive_solid_geometry .. _quadratic surfaces: https://en.wikipedia.org/wiki/Quadric diff --git a/docs/source/usersguide/settings.rst b/docs/source/usersguide/settings.rst index 496d18f7fb..1254c4ed5c 100644 --- a/docs/source/usersguide/settings.rst +++ b/docs/source/usersguide/settings.rst @@ -221,26 +221,11 @@ selected:: settings.electron_treatment = 'led' -.. warning:: - Currently, collision stopping powers used in the TTB approximation come from - the `NIST ESTAR database`_, which provides data for each element calculated - using by default the material density at standard temperature and pressure. - In OpenMC, stopping powers for compounds are calculated from this elemental - data using Bragg's additivity rule. However, this is not a good - approximation --- the collision stopping power is a function of certain - quantities, such as the mean excitation energy and particularly the density - effect correction, that depend on material properties. Data for constituent - elements in a compound cannot simply be summed together, but rather these - quantities should be calculated for the material. This treatment will be - especially poor when the density of a material is different from the - densities used in the NIST data. - .. note:: Some features related to photon transport are not currently implemented, including: * Tallying photon energy deposition. - * Properly accounting for energy deposition in coupled n-p calculations. * Generating a photon source from a neutron calculation that can be used for a later fixed source photon calculation. * Photoneutron reactions. From 130b7a5eebea279206ac84138608fa990fe0f6dd Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Sat, 5 Oct 2019 01:14:27 -0500 Subject: [PATCH 110/158] Setting up new function implement a stopping criterion for volume calculations. --- include/openmc/volume_calc.h | 29 +++++++++++++++++++++++++++ src/volume_calc.cpp | 38 ++++++++++++++++++++++++++++++++++-- 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/include/openmc/volume_calc.h b/include/openmc/volume_calc.h index 7ee660d8bd..c7f6413b09 100644 --- a/include/openmc/volume_calc.h +++ b/include/openmc/volume_calc.h @@ -8,6 +8,7 @@ #include #include +#include namespace openmc { @@ -23,6 +24,31 @@ public: std::vector nuclides; //!< Index of nuclides std::vector atoms; //!< Number of atoms for each nuclide std::vector uncertainty; //!< Uncertainty on number of atoms + size_t num_samples; + + Result& operator +=( const Result& other) { + Expects(volume.size() == other.volume.size()); + Expects(atoms.size() == atoms.size()); + + size_t total_samples = num_samples + other.num_samples; + + for (int i = 0; i < volume.size(); i++) { + // average volume results + volume[0] = (num_samples * volume[0] + other.num_samples * other.volume[0]) / total_samples; + // propagate error + volume[1] = std::sqrt(num_samples * volume[1] *volume[1] + other.num_samples * other.volume[1] * other.volume[1]) / total_samples; + } + + for (int i = 0; i < atoms.size(); i++) { + atoms[i] = (num_samples * atoms[i] + other.num_samples * other.atoms[i]) / total_samples; + uncertainty[i] = std::sqrt(num_samples * uncertainty[i] * uncertainty[i] + other.num_samples * other.uncertainty[i] * other.uncertainty[i]) / total_samples; + } + + num_samples = total_samples; + + return *this; + + } }; // Results for a single domain // Constructors @@ -36,6 +62,8 @@ public: //! \return Vector of results for each user-specified domain std::vector execute() const; + std::vector _execute(size_t seed_offset = 0) const; + //! \brief Write volume calculation results to HDF5 file // //! \param[in] filename Path to HDF5 file to write @@ -45,6 +73,7 @@ public: // Data members int domain_type_; //!< Type of domain (cell, material, etc.) int n_samples_; //!< Number of samples to use + int seed_offset_; Position lower_left_; //!< Lower-left position of bounding box Position upper_right_; //!< Upper-right position of bounding box std::vector domain_ids_; //!< IDs of domains to find volumes of diff --git a/src/volume_calc.cpp b/src/volume_calc.cpp index 6c1c48932f..bd6ada96a7 100644 --- a/src/volume_calc.cpp +++ b/src/volume_calc.cpp @@ -71,7 +71,40 @@ VolumeCalculation::VolumeCalculation(pugi::xml_node node) } -std::vector VolumeCalculation::execute() const +std::vector VolumeCalculation::execute() const { + + std::vector results; + size_t offset = 0; + + results = _execute(offset); + offset += n_samples_; + + double max_err = -INFTY; + for (int i = 0; i < results.size(); i++) { + max_err = std::max(max_err, results[i].volume[1]); + } + + double error_limit = 1E-05; + int iters = 1; + while (max_err > error_limit) { + std::cout << "Iter " << iters++ << std::endl; + std::vector tmp = _execute(offset); + max_err = -INFTY; + for (int i = 0; i < results.size(); i++) { + auto& result = results[i]; + result += tmp[i]; + max_err = std::max(max_err, result.volume[1]); + } + + offset += n_samples_; + + std::cout << "Max error: " << max_err << std::endl; + } + + return results; +} + +std::vector VolumeCalculation::_execute(size_t seed_offset) const { // Shared data that is collected from all threads int n = domain_ids_.size(); @@ -102,7 +135,7 @@ std::vector VolumeCalculation::execute() const // Sample locations and count hits #pragma omp for for (int i = i_start; i < i_end; i++) { - set_particle_seed(i); + set_particle_seed(seed_offset + i); p.n_coord_ = 1; Position xi {prn(), prn(), prn()}; @@ -248,6 +281,7 @@ std::vector VolumeCalculation::execute() const result.volume[0] = static_cast(total_hits) / n_samples_ * volume_sample; result.volume[1] = std::sqrt(result.volume[0] * (volume_sample - result.volume[0]) / n_samples_); + result.num_samples = n_samples_; for (int j = 0; j < n_nuc; ++j) { // Determine total number of atoms. At this point, we have values in From 1c5218aad6cc14772c454360ced1da08ad69c645 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Sat, 5 Oct 2019 01:26:21 -0500 Subject: [PATCH 111/158] Cleaning up operator a bit. --- include/openmc/volume_calc.h | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/include/openmc/volume_calc.h b/include/openmc/volume_calc.h index c7f6413b09..b5e8477424 100644 --- a/include/openmc/volume_calc.h +++ b/include/openmc/volume_calc.h @@ -30,25 +30,41 @@ public: Expects(volume.size() == other.volume.size()); Expects(atoms.size() == atoms.size()); + auto& a_samples = num_samples; + auto& b_samples = other.num_samples; + size_t total_samples = num_samples + other.num_samples; for (int i = 0; i < volume.size(); i++) { - // average volume results - volume[0] = (num_samples * volume[0] + other.num_samples * other.volume[0]) / total_samples; + // calculate weighted average of volume results + auto& a_vol = volume[0]; + auto& b_vol = other.volume[1]; + volume[0] = (a_samples * a_vol + b_samples * b_vol) / total_samples; + // propagate error - volume[1] = std::sqrt(num_samples * volume[1] *volume[1] + other.num_samples * other.volume[1] * other.volume[1]) / total_samples; + auto& a_err = volume[1]; + auto& b_err = other.volume[1]; + volume[1] = std::sqrt(a_samples * a_err * a_err + b_samples * b_err * b_err) / total_samples; } for (int i = 0; i < atoms.size(); i++) { - atoms[i] = (num_samples * atoms[i] + other.num_samples * other.atoms[i]) / total_samples; - uncertainty[i] = std::sqrt(num_samples * uncertainty[i] * uncertainty[i] + other.num_samples * other.uncertainty[i] * other.uncertainty[i]) / total_samples; + // calculate weighted average of atom results + auto& a_atoms = atoms[i]; + auto& b_atoms = other.atoms[i]; + atoms[i] = (a_samples * a_atoms + b_samples * b_atoms) / total_samples; + + // propagate error + auto& a_err = uncertainty[i]; + auto& b_err = other.uncertainty[i]; + uncertainty[i] = std::sqrt(a_samples * a_err * a_err + b_samples * b_err * b_err) / total_samples; } + // update number of samples on the returned set of results; num_samples = total_samples; - - return *this; + return *this; } + }; // Results for a single domain // Constructors From d3bcfeda0f667650c2e6d5fdb948f5b0db8f26af Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Sat, 5 Oct 2019 01:41:07 -0500 Subject: [PATCH 112/158] Cleaning up while loop. --- src/volume_calc.cpp | 36 ++++++++++++++---------------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/src/volume_calc.cpp b/src/volume_calc.cpp index bd6ada96a7..40b5f08dde 100644 --- a/src/volume_calc.cpp +++ b/src/volume_calc.cpp @@ -73,32 +73,24 @@ VolumeCalculation::VolumeCalculation(pugi::xml_node node) std::vector VolumeCalculation::execute() const { - std::vector results; - size_t offset = 0; - - results = _execute(offset); - offset += n_samples_; - - double max_err = -INFTY; - for (int i = 0; i < results.size(); i++) { - max_err = std::max(max_err, results[i].volume[1]); - } - + std::vector results = _execute(); + size_t offset = n_samples_; double error_limit = 1E-05; - int iters = 1; - while (max_err > error_limit) { - std::cout << "Iter " << iters++ << std::endl; - std::vector tmp = _execute(offset); + double max_err; + + while (true) { + // check maximum error value for all domains max_err = -INFTY; - for (int i = 0; i < results.size(); i++) { - auto& result = results[i]; - result += tmp[i]; - max_err = std::max(max_err, result.volume[1]); - } - + for (const auto& result : results) { max_err = std::max(max_err, result.volume[1]); } + + // exit once we're below our error limit + if (max_err <= error_limit) { break; } + + std::vector tmp = _execute(offset); offset += n_samples_; - std::cout << "Max error: " << max_err << std::endl; + // update current results + for (int i = 0; i < results.size(); i++) { results[i] += tmp[i]; } } return results; From 5109bd0de1222f3096f9f511b194c35e25a69696 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Sat, 5 Oct 2019 02:13:12 -0500 Subject: [PATCH 113/158] Adding error trigger attribute to volume calc. --- include/openmc/volume_calc.h | 4 ++-- src/volume_calc.cpp | 32 +++++++++++++++++++++++++------- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/include/openmc/volume_calc.h b/include/openmc/volume_calc.h index b5e8477424..2a8273f9bf 100644 --- a/include/openmc/volume_calc.h +++ b/include/openmc/volume_calc.h @@ -88,8 +88,8 @@ public: // Data members int domain_type_; //!< Type of domain (cell, material, etc.) - int n_samples_; //!< Number of samples to use - int seed_offset_; + size_t n_samples_; //!< Number of samples to use + double error_trigger_ {-1.0}; //!< Error below which the calculation will stop Position lower_left_; //!< Lower-left position of bounding box Position upper_right_; //!< Upper-right position of bounding box std::vector domain_ids_; //!< IDs of domains to find volumes of diff --git a/src/volume_calc.cpp b/src/volume_calc.cpp index 40b5f08dde..a57305db93 100644 --- a/src/volume_calc.cpp +++ b/src/volume_calc.cpp @@ -59,7 +59,23 @@ VolumeCalculation::VolumeCalculation(pugi::xml_node node) domain_ids_ = get_node_array(node, "domain_ids"); lower_left_ = get_node_array(node, "lower_left"); upper_right_ = get_node_array(node, "upper_right"); - n_samples_ = std::stoi(get_node_value(node, "samples")); + std::stringstream size_t_stream(get_node_value(node, "samples")); + size_t_stream >> n_samples_; + if (size_t_stream.fail()) { + std::stringstream msg; + msg << "Could not read number of samples (" + << size_t_stream.str() << ")\n"; + fatal_error(msg); + } + + if (check_for_node(node, "error_trigger")) { + error_trigger_ = std::stod(get_node_value(node, "error_trigger")); + if (error_trigger_ <= 0.0) { + std::stringstream msg; + msg << "Invalid error trigger " << error_trigger_ << " provided for a volume calculation."; + fatal_error(msg); + } + } // Ensure there are no duplicates by copying elements to a set and then // comparing the length with the original vector @@ -74,8 +90,10 @@ VolumeCalculation::VolumeCalculation(pugi::xml_node node) std::vector VolumeCalculation::execute() const { std::vector results = _execute(); + + if (error_trigger_ <= 0.0) { return results; } + size_t offset = n_samples_; - double error_limit = 1E-05; double max_err; while (true) { @@ -84,7 +102,7 @@ std::vector VolumeCalculation::execute() const { for (const auto& result : results) { max_err = std::max(max_err, result.volume[1]); } // exit once we're below our error limit - if (max_err <= error_limit) { break; } + if (max_err <= error_trigger_) { break; } std::vector tmp = _execute(offset); offset += n_samples_; @@ -104,9 +122,9 @@ std::vector VolumeCalculation::_execute(size_t seed_o std::vector> master_hits(n); // Number of hits for each material in each domain // Divide work over MPI processes - int min_samples = n_samples_ / mpi::n_procs; - int remainder = n_samples_ % mpi::n_procs; - int i_start, i_end; + size_t min_samples = n_samples_ / mpi::n_procs; + size_t remainder = n_samples_ % mpi::n_procs; + size_t i_start, i_end; if (mpi::rank < remainder) { i_start = (min_samples + 1)*mpi::rank; i_end = i_start + min_samples + 1; @@ -126,7 +144,7 @@ std::vector VolumeCalculation::_execute(size_t seed_o // Sample locations and count hits #pragma omp for - for (int i = i_start; i < i_end; i++) { + for (size_t i = i_start; i < i_end; i++) { set_particle_seed(seed_offset + i); p.n_coord_ = 1; From 4b690ab7518e6de2376ab3ebd01a12c7e463cba1 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Sat, 5 Oct 2019 02:18:16 -0500 Subject: [PATCH 114/158] Fixing a bug. --- include/openmc/volume_calc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/openmc/volume_calc.h b/include/openmc/volume_calc.h index 2a8273f9bf..18aa2e2d18 100644 --- a/include/openmc/volume_calc.h +++ b/include/openmc/volume_calc.h @@ -38,7 +38,7 @@ public: for (int i = 0; i < volume.size(); i++) { // calculate weighted average of volume results auto& a_vol = volume[0]; - auto& b_vol = other.volume[1]; + auto& b_vol = other.volume[0]; volume[0] = (a_samples * a_vol + b_samples * b_vol) / total_samples; // propagate error From 507bc0d9339a896dd00ed80ed00f0e5f6724eca7 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Sat, 5 Oct 2019 10:23:11 -0500 Subject: [PATCH 115/158] Exposing volume trigger through Python API. --- include/openmc/volume_calc.h | 12 ++++++--- openmc/volume.py | 31 +++++++++++++++++++--- src/volume_calc.cpp | 16 ++++++----- tests/regression_tests/volume_calc/test.py | 2 +- 4 files changed, 48 insertions(+), 13 deletions(-) diff --git a/include/openmc/volume_calc.h b/include/openmc/volume_calc.h index 18aa2e2d18..4e2b2a2a6e 100644 --- a/include/openmc/volume_calc.h +++ b/include/openmc/volume_calc.h @@ -78,8 +78,6 @@ public: //! \return Vector of results for each user-specified domain std::vector execute() const; - std::vector _execute(size_t seed_offset = 0) const; - //! \brief Write volume calculation results to HDF5 file // //! \param[in] filename Path to HDF5 file to write @@ -89,7 +87,7 @@ public: // Data members int domain_type_; //!< Type of domain (cell, material, etc.) size_t n_samples_; //!< Number of samples to use - double error_trigger_ {-1.0}; //!< Error below which the calculation will stop + double trigger_ {-1.0}; //!< Error threshold for domain volumes Position lower_left_; //!< Lower-left position of bounding box Position upper_right_; //!< Upper-right position of bounding box std::vector domain_ids_; //!< IDs of domains to find volumes of @@ -104,6 +102,14 @@ private: void check_hit(int i_material, std::vector& indices, std::vector& hits) const; + //! \brief Perform calculation for domain volumes and average nuclide density + //! using n_samples_ + // + //! \param[in] seed_offset Seed offset used for independent calculations + //! \return Vector of results for each user-specified domain + std::vector _execute(size_t seed_offset = 0) const; + + }; //============================================================================== diff --git a/openmc/volume.py b/openmc/volume.py index 91ff829cbd..189287bef8 100644 --- a/openmc/volume.py +++ b/openmc/volume.py @@ -12,7 +12,7 @@ from uncertainties import ufloat import openmc import openmc.checkvalue as cv -_VERSION_VOLUME = 1 +_VERSION_VOLUME = 2 class VolumeCalculation(object): @@ -32,6 +32,8 @@ class VolumeCalculation(object): Upper-right coordinates of bounding box used to sample points. If this argument is not supplied, an attempt is made to automatically determine a bounding box. + trigger : float + Threshold for the maxmimum standard deviation of volumes Attributes ---------- @@ -45,6 +47,8 @@ class VolumeCalculation(object): Lower-left coordinates of bounding box used to sample points upper_right : Iterable of float Upper-right coordinates of bounding box used to sample points + trigger : float + Threshold for the maximum standard deviation of volume in the calculation atoms : dict Dictionary mapping unique IDs of domains to a mapping of nuclides to total number of atoms for each nuclide present in the domain. For @@ -57,9 +61,10 @@ class VolumeCalculation(object): """ def __init__(self, domains, samples, lower_left=None, - upper_right=None): + upper_right=None, trigger=None): self._atoms = {} self._volumes = {} + self._trigger = None cv.check_type('domains', domains, Iterable, (openmc.Cell, openmc.Material, openmc.Universe)) @@ -72,6 +77,9 @@ class VolumeCalculation(object): self.ids = [d.id for d in domains] self.samples = samples + + if trigger is not None: + self.trigger = trigger if lower_left is not None: if upper_right is None: @@ -123,6 +131,10 @@ class VolumeCalculation(object): def upper_right(self): return self._upper_right + @property + def trigger(self): + return self._trigger + @property def domain_type(self): return self._domain_type @@ -170,6 +182,12 @@ class VolumeCalculation(object): cv.check_length(name, upper_right, 3) self._upper_right = upper_right + @trigger.setter + def trigger(self, trigger): + name = 'Volume std. dev. trigger' + cv.check_type(name, trigger, Real) + self._trigger = trigger + @volumes.setter def volumes(self, volumes): cv.check_type('volumes', volumes, Mapping) @@ -202,6 +220,10 @@ class VolumeCalculation(object): samples = f.attrs['samples'] lower_left = f.attrs['lower_left'] upper_right = f.attrs['upper_right'] + trigger = f.attrs['trigger'] + + if trigger == -1.0: + trigger = None volumes = {} atoms = {} @@ -232,7 +254,7 @@ class VolumeCalculation(object): domains = [openmc.Universe(uid) for uid in ids] # Instantiate the class and assign results - vol = cls(domains, samples, lower_left, upper_right) + vol = cls(domains, samples, lower_left, upper_right, trigger) vol.volumes = volumes vol.atoms = atoms return vol @@ -277,4 +299,7 @@ class VolumeCalculation(object): ll_elem.text = ' '.join(str(x) for x in self.lower_left) ur_elem = ET.SubElement(element, "upper_right") ur_elem.text = ' '.join(str(x) for x in self.upper_right) + if self.trigger: + trigger_elem = ET.SubElement(element, "trigger") + trigger_elem.text = str(self.trigger) return element diff --git a/src/volume_calc.cpp b/src/volume_calc.cpp index a57305db93..358d8cfd45 100644 --- a/src/volume_calc.cpp +++ b/src/volume_calc.cpp @@ -68,11 +68,11 @@ VolumeCalculation::VolumeCalculation(pugi::xml_node node) fatal_error(msg); } - if (check_for_node(node, "error_trigger")) { - error_trigger_ = std::stod(get_node_value(node, "error_trigger")); - if (error_trigger_ <= 0.0) { + if (check_for_node(node, "trigger")) { + trigger_ = std::stod(get_node_value(node, "trigger")); + if (trigger_ <= 0.0) { std::stringstream msg; - msg << "Invalid error trigger " << error_trigger_ << " provided for a volume calculation."; + msg << "Invalid error threshold " << trigger_ << " provided for a volume calculation."; fatal_error(msg); } } @@ -89,9 +89,11 @@ VolumeCalculation::VolumeCalculation(pugi::xml_node node) std::vector VolumeCalculation::execute() const { + // execute the calculation once std::vector results = _execute(); - if (error_trigger_ <= 0.0) { return results; } + // if no std. dev. threshold is set, return these resuls + if (trigger_ == -1.0) { return results; } size_t offset = n_samples_; double max_err; @@ -102,8 +104,9 @@ std::vector VolumeCalculation::execute() const { for (const auto& result : results) { max_err = std::max(max_err, result.volume[1]); } // exit once we're below our error limit - if (max_err <= error_trigger_) { break; } + if (max_err <= trigger_) { break; } + // perform the calculation std::vector tmp = _execute(offset); offset += n_samples_; @@ -333,6 +336,7 @@ void VolumeCalculation::to_hdf5(const std::string& filename, write_attribute(file_id, "samples", n_samples_); write_attribute(file_id, "lower_left", lower_left_); write_attribute(file_id, "upper_right", upper_right_); + write_attribute(file_id, "trigger", trigger_); if (domain_type_ == FILTER_CELL) { write_attribute(file_id, "domain_type", "cell"); } diff --git a/tests/regression_tests/volume_calc/test.py b/tests/regression_tests/volume_calc/test.py index 8ea4ab04e0..2affc40852 100644 --- a/tests/regression_tests/volume_calc/test.py +++ b/tests/regression_tests/volume_calc/test.py @@ -74,4 +74,4 @@ class VolumeTest(PyAPITestHarness): def test_volume_calc(): harness = VolumeTest('') - harness.main() + harness._build_inputs() From 48fab6dcac29a9935a575d26d158503de420379b Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Sat, 5 Oct 2019 10:50:32 -0500 Subject: [PATCH 116/158] Only write threshold to file if used. --- include/openmc/volume_calc.h | 2 +- openmc/volume.py | 46 ++++++++++++++++++------------------ src/volume_calc.cpp | 17 +++++++------ 3 files changed, 34 insertions(+), 31 deletions(-) diff --git a/include/openmc/volume_calc.h b/include/openmc/volume_calc.h index 4e2b2a2a6e..33c8cbfead 100644 --- a/include/openmc/volume_calc.h +++ b/include/openmc/volume_calc.h @@ -87,7 +87,7 @@ public: // Data members int domain_type_; //!< Type of domain (cell, material, etc.) size_t n_samples_; //!< Number of samples to use - double trigger_ {-1.0}; //!< Error threshold for domain volumes + double threshold_ {-1.0}; //!< Error threshold for domain volumes Position lower_left_; //!< Lower-left position of bounding box Position upper_right_; //!< Upper-right position of bounding box std::vector domain_ids_; //!< IDs of domains to find volumes of diff --git a/openmc/volume.py b/openmc/volume.py index 189287bef8..bcc46d19e7 100644 --- a/openmc/volume.py +++ b/openmc/volume.py @@ -12,7 +12,7 @@ from uncertainties import ufloat import openmc import openmc.checkvalue as cv -_VERSION_VOLUME = 2 +_VERSION_VOLUME = 1 class VolumeCalculation(object): @@ -32,7 +32,7 @@ class VolumeCalculation(object): Upper-right coordinates of bounding box used to sample points. If this argument is not supplied, an attempt is made to automatically determine a bounding box. - trigger : float + threshold : float Threshold for the maxmimum standard deviation of volumes Attributes @@ -47,7 +47,7 @@ class VolumeCalculation(object): Lower-left coordinates of bounding box used to sample points upper_right : Iterable of float Upper-right coordinates of bounding box used to sample points - trigger : float + threshold : float Threshold for the maximum standard deviation of volume in the calculation atoms : dict Dictionary mapping unique IDs of domains to a mapping of nuclides to @@ -61,10 +61,10 @@ class VolumeCalculation(object): """ def __init__(self, domains, samples, lower_left=None, - upper_right=None, trigger=None): + upper_right=None, threshold=None): self._atoms = {} self._volumes = {} - self._trigger = None + self._threshold = None cv.check_type('domains', domains, Iterable, (openmc.Cell, openmc.Material, openmc.Universe)) @@ -78,8 +78,8 @@ class VolumeCalculation(object): self.samples = samples - if trigger is not None: - self.trigger = trigger + if threshold is not None: + self.threshold = threshold if lower_left is not None: if upper_right is None: @@ -132,8 +132,8 @@ class VolumeCalculation(object): return self._upper_right @property - def trigger(self): - return self._trigger + def threshold(self): + return self._threshold @property def domain_type(self): @@ -182,11 +182,11 @@ class VolumeCalculation(object): cv.check_length(name, upper_right, 3) self._upper_right = upper_right - @trigger.setter - def trigger(self, trigger): - name = 'Volume std. dev. trigger' - cv.check_type(name, trigger, Real) - self._trigger = trigger + @threshold.setter + def threshold(self, threshold): + name = 'volume std. dev. threshold' + cv.check_type(name, threshold, Real) + self._threshold = threshold @volumes.setter def volumes(self, volumes): @@ -220,11 +220,11 @@ class VolumeCalculation(object): samples = f.attrs['samples'] lower_left = f.attrs['lower_left'] upper_right = f.attrs['upper_right'] - trigger = f.attrs['trigger'] - - if trigger == -1.0: - trigger = None - + try: + threshold = f.attrs['threshold'] + except KeyError: + threshold = None + volumes = {} atoms = {} ids = [] @@ -254,7 +254,7 @@ class VolumeCalculation(object): domains = [openmc.Universe(uid) for uid in ids] # Instantiate the class and assign results - vol = cls(domains, samples, lower_left, upper_right, trigger) + vol = cls(domains, samples, lower_left, upper_right, threshold) vol.volumes = volumes vol.atoms = atoms return vol @@ -299,7 +299,7 @@ class VolumeCalculation(object): ll_elem.text = ' '.join(str(x) for x in self.lower_left) ur_elem = ET.SubElement(element, "upper_right") ur_elem.text = ' '.join(str(x) for x in self.upper_right) - if self.trigger: - trigger_elem = ET.SubElement(element, "trigger") - trigger_elem.text = str(self.trigger) + if self.threshold: + threshold_elem = ET.SubElement(element, "threshold") + threshold_elem.text = str(self.threshold) return element diff --git a/src/volume_calc.cpp b/src/volume_calc.cpp index 358d8cfd45..9b2ef7c514 100644 --- a/src/volume_calc.cpp +++ b/src/volume_calc.cpp @@ -68,11 +68,11 @@ VolumeCalculation::VolumeCalculation(pugi::xml_node node) fatal_error(msg); } - if (check_for_node(node, "trigger")) { - trigger_ = std::stod(get_node_value(node, "trigger")); - if (trigger_ <= 0.0) { + if (check_for_node(node, "threshold")) { + threshold_ = std::stod(get_node_value(node, "threshold")); + if (threshold_ <= 0.0) { std::stringstream msg; - msg << "Invalid error threshold " << trigger_ << " provided for a volume calculation."; + msg << "Invalid error threshold " << threshold_ << " provided for a volume calculation."; fatal_error(msg); } } @@ -93,7 +93,7 @@ std::vector VolumeCalculation::execute() const { std::vector results = _execute(); // if no std. dev. threshold is set, return these resuls - if (trigger_ == -1.0) { return results; } + if (threshold_ == -1.0) { return results; } size_t offset = n_samples_; double max_err; @@ -104,7 +104,7 @@ std::vector VolumeCalculation::execute() const { for (const auto& result : results) { max_err = std::max(max_err, result.volume[1]); } // exit once we're below our error limit - if (max_err <= trigger_) { break; } + if (max_err <= threshold_) { break; } // perform the calculation std::vector tmp = _execute(offset); @@ -336,7 +336,10 @@ void VolumeCalculation::to_hdf5(const std::string& filename, write_attribute(file_id, "samples", n_samples_); write_attribute(file_id, "lower_left", lower_left_); write_attribute(file_id, "upper_right", upper_right_); - write_attribute(file_id, "trigger", trigger_); + if (threshold_ != -1.0) { + write_attribute(file_id, "threshold", threshold_); + } + if (domain_type_ == FILTER_CELL) { write_attribute(file_id, "domain_type", "cell"); } From 0914330f8d773b62f321b6ceb21a218b95247bcb Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Sat, 5 Oct 2019 10:58:11 -0500 Subject: [PATCH 117/158] Updating volume documentation. --- docs/source/io_formats/volume.rst | 1 + docs/source/usersguide/volume.rst | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/source/io_formats/volume.rst b/docs/source/io_formats/volume.rst index 456a06eef5..e421de566f 100644 --- a/docs/source/io_formats/volume.rst +++ b/docs/source/io_formats/volume.rst @@ -23,6 +23,7 @@ The current version of the volume file format is 1.0. bounding box - **upper_right** (*double[3]*) -- Upper-right coordinates of bounding box + - **threshold** (*double*) -- Threshold used for volume uncertainty **/domain_/** diff --git a/docs/source/usersguide/volume.rst b/docs/source/usersguide/volume.rst index e4bdbf7155..f347102321 100644 --- a/docs/source/usersguide/volume.rst +++ b/docs/source/usersguide/volume.rst @@ -37,6 +37,16 @@ arguments are not necessary. For example, Of course, the volumes that you *need* this capability for are often the ones with complex definitions. +A threshold for the uncertainty in volume estimates can be specified using +::attr::`openmc.VolumeCalculation.threshold` :: + + vol_calc.threshold = 1E-05 + +If a threshold is provided, calculations will be performed iteratively using the +number of samples specified on the calculation until all volume estimates have a +standard deviation lower than this value. If no threshold is provided, the +calculation will run the number of samples specified once and return the result. + Once you have one or more :class:`openmc.VolumeCalculation` objects created, you can then assign then to :attr:`Settings.volume_calculations`:: @@ -66,4 +76,4 @@ After the results are loaded, volume estimates will be stored in :attr:`VolumeCalculation.volumes`. There is also a :attr:`VolumeCalculation.atoms_dataframe` attribute that shows stochastic estimates of the number of atoms of each type of nuclide within the specified -domains along with their uncertainties. +domains along with their uncertainties. \ No newline at end of file From 9415bbb71550772c7152836ab1452b635ad5f720 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Sat, 5 Oct 2019 11:02:23 -0500 Subject: [PATCH 118/158] Adding check for valid threshold value in Python API. --- openmc/volume.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openmc/volume.py b/openmc/volume.py index bcc46d19e7..f185620cde 100644 --- a/openmc/volume.py +++ b/openmc/volume.py @@ -186,6 +186,9 @@ class VolumeCalculation(object): def threshold(self, threshold): name = 'volume std. dev. threshold' cv.check_type(name, threshold, Real) + if (threshold <= 0.0): + raise ValueError("Invalid value '{}' (<= 0.0) provided for volume " + "calculation threshold.".format(threshold)) self._threshold = threshold @volumes.setter @@ -224,7 +227,7 @@ class VolumeCalculation(object): threshold = f.attrs['threshold'] except KeyError: threshold = None - + volumes = {} atoms = {} ids = [] From 8d1e8711f564f4f7e97fb79265bf4fbbaa0174ed Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Sat, 5 Oct 2019 12:33:00 -0500 Subject: [PATCH 119/158] Adding volume trigger via different methods. --- include/openmc/volume_calc.h | 8 +++++++ src/volume_calc.cpp | 44 ++++++++++++++++++++++++++++++++---- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/include/openmc/volume_calc.h b/include/openmc/volume_calc.h index 33c8cbfead..e2cf1b3ef2 100644 --- a/include/openmc/volume_calc.h +++ b/include/openmc/volume_calc.h @@ -12,11 +12,18 @@ namespace openmc { +enum class ThresholdType { + VARIANCE = 0, + STD_DEV = 1, + REL_ERR = 2 +}; + //============================================================================== // Volume calculation class //============================================================================== class VolumeCalculation { + public: // Aliases, types struct Result { @@ -88,6 +95,7 @@ public: int domain_type_; //!< Type of domain (cell, material, etc.) size_t n_samples_; //!< Number of samples to use double threshold_ {-1.0}; //!< Error threshold for domain volumes + ThresholdType threshold_type_; Position lower_left_; //!< Lower-left position of bounding box Position upper_right_; //!< Upper-right position of bounding box std::vector domain_ids_; //!< IDs of domains to find volumes of diff --git a/src/volume_calc.cpp b/src/volume_calc.cpp index 9b2ef7c514..870721f3e4 100644 --- a/src/volume_calc.cpp +++ b/src/volume_calc.cpp @@ -75,6 +75,21 @@ VolumeCalculation::VolumeCalculation(pugi::xml_node node) msg << "Invalid error threshold " << threshold_ << " provided for a volume calculation."; fatal_error(msg); } + + pugi::xml_node threshold_node = node.child("threshold"); + std::string tmp = get_node_value(threshold_node, "type"); + if (tmp == "variance") { + threshold_type_ = ThresholdType::VARIANCE; + } else if (tmp == "std_dev") { + threshold_type_ = ThresholdType::STD_DEV; + } else if ( tmp == "rel_err") { + threshold_type_ = ThresholdType::REL_ERR; + } else { + std::stringstream msg; + msg << "Invalid volume calculation trigger type '" << tmp << "' provided."; + fatal_error(msg); + } + } // Ensure there are no duplicates by copying elements to a set and then @@ -96,15 +111,30 @@ std::vector VolumeCalculation::execute() const { if (threshold_ == -1.0) { return results; } size_t offset = n_samples_; - double max_err; + double max_val; while (true) { // check maximum error value for all domains - max_err = -INFTY; - for (const auto& result : results) { max_err = std::max(max_err, result.volume[1]); } + max_val = -INFTY; + for (const auto& result : results) { + double val; + switch (threshold_type_) { + case ThresholdType::STD_DEV: + val = result.volume[1]; + break; + case ThresholdType::REL_ERR: + val = result.volume[1] / result.volume[0]; + break; + case ThresholdType::VARIANCE: + val = result.volume[1] * result.volume[1]; + break; + } + // update max + max_val = std::max(max_val, val); + } // exit once we're below our error limit - if (max_err <= threshold_) { break; } + if (max_val <= threshold_) { break; } // perform the calculation std::vector tmp = _execute(offset); @@ -307,6 +337,10 @@ std::vector VolumeCalculation::_execute(size_t seed_o result.nuclides.push_back(j); result.atoms.push_back(mean); result.uncertainty.push_back(stdev); + } else { + result.nuclides.push_back(j); + result.atoms.push_back(0.0); + result.uncertainty.push_back(INFTY); } } } @@ -339,7 +373,7 @@ void VolumeCalculation::to_hdf5(const std::string& filename, if (threshold_ != -1.0) { write_attribute(file_id, "threshold", threshold_); } - + if (domain_type_ == FILTER_CELL) { write_attribute(file_id, "domain_type", "cell"); } From 85e25d79eafd65a064c3afeea3a77e30e50b0047 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Sat, 5 Oct 2019 13:12:31 -0500 Subject: [PATCH 120/158] Finishing addition of a trigger type. --- include/openmc/volume_calc.h | 2 +- src/volume_calc.cpp | 21 +++++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/include/openmc/volume_calc.h b/include/openmc/volume_calc.h index e2cf1b3ef2..a7dd750094 100644 --- a/include/openmc/volume_calc.h +++ b/include/openmc/volume_calc.h @@ -95,7 +95,7 @@ public: int domain_type_; //!< Type of domain (cell, material, etc.) size_t n_samples_; //!< Number of samples to use double threshold_ {-1.0}; //!< Error threshold for domain volumes - ThresholdType threshold_type_; + ThresholdType trigger_type_; Position lower_left_; //!< Lower-left position of bounding box Position upper_right_; //!< Upper-right position of bounding box std::vector domain_ids_; //!< IDs of domains to find volumes of diff --git a/src/volume_calc.cpp b/src/volume_calc.cpp index 870721f3e4..f352e71f82 100644 --- a/src/volume_calc.cpp +++ b/src/volume_calc.cpp @@ -79,11 +79,11 @@ VolumeCalculation::VolumeCalculation(pugi::xml_node node) pugi::xml_node threshold_node = node.child("threshold"); std::string tmp = get_node_value(threshold_node, "type"); if (tmp == "variance") { - threshold_type_ = ThresholdType::VARIANCE; + trigger_type_ = ThresholdType::VARIANCE; } else if (tmp == "std_dev") { - threshold_type_ = ThresholdType::STD_DEV; + trigger_type_ = ThresholdType::STD_DEV; } else if ( tmp == "rel_err") { - threshold_type_ = ThresholdType::REL_ERR; + trigger_type_ = ThresholdType::REL_ERR; } else { std::stringstream msg; msg << "Invalid volume calculation trigger type '" << tmp << "' provided."; @@ -118,7 +118,7 @@ std::vector VolumeCalculation::execute() const { max_val = -INFTY; for (const auto& result : results) { double val; - switch (threshold_type_) { + switch (trigger_type_) { case ThresholdType::STD_DEV: val = result.volume[1]; break; @@ -372,6 +372,19 @@ void VolumeCalculation::to_hdf5(const std::string& filename, write_attribute(file_id, "upper_right", upper_right_); if (threshold_ != -1.0) { write_attribute(file_id, "threshold", threshold_); + + switch(trigger_type_) { + case ThresholdType::VARIANCE: + write_attribute(file_id, "trigger_type", "variance"); + break; + case ThresholdType::STD_DEV: + write_attribute(file_id, "trigger_type", "std_dev"); + break; + case ThresholdType::REL_ERR: + write_attribute(file_id, "trigger_type", "rel_err"); + break; + } + } if (domain_type_ == FILTER_CELL) { From 8aed7b019abc7666d553b4f508deee2a5fb9cf94 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Sat, 5 Oct 2019 13:12:56 -0500 Subject: [PATCH 121/158] Adding support for trigger type in the Python API. --- openmc/volume.py | 50 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/openmc/volume.py b/openmc/volume.py index f185620cde..a72f32ab3d 100644 --- a/openmc/volume.py +++ b/openmc/volume.py @@ -32,8 +32,7 @@ class VolumeCalculation(object): Upper-right coordinates of bounding box used to sample points. If this argument is not supplied, an attempt is made to automatically determine a bounding box. - threshold : float - Threshold for the maxmimum standard deviation of volumes + Attributes ---------- @@ -58,13 +57,17 @@ class VolumeCalculation(object): in each domain specified. volumes : dict Dictionary mapping unique IDs of domains to estimated volumes in cm^3. + threshold : float + Threshold for the maxmimum standard deviation of volumes + trigger_type : {'variance', 'std_dev', 'rel_err'} + Value type used to halt volume calculation """ - def __init__(self, domains, samples, lower_left=None, - upper_right=None, threshold=None): + def __init__(self, domains, samples, lower_left=None, upper_right=None): self._atoms = {} self._volumes = {} self._threshold = None + self._trigger_type = None cv.check_type('domains', domains, Iterable, (openmc.Cell, openmc.Material, openmc.Universe)) @@ -78,9 +81,6 @@ class VolumeCalculation(object): self.samples = samples - if threshold is not None: - self.threshold = threshold - if lower_left is not None: if upper_right is None: raise ValueError('Both lower-left and upper-right coordinates ' @@ -135,6 +135,10 @@ class VolumeCalculation(object): def threshold(self): return self._threshold + @property + def trigger_type(self): + return self._trigger_type + @property def domain_type(self): return self._domain_type @@ -191,6 +195,12 @@ class VolumeCalculation(object): "calculation threshold.".format(threshold)) self._threshold = threshold + @trigger_type.setter + def trigger_type(self, trigger_type): + cv.check_value('tally trigger type', trigger_type, + ['variance', 'std_dev', 'rel_err']) + self._trigger_type = trigger_type + @volumes.setter def volumes(self, volumes): cv.check_type('volumes', volumes, Mapping) @@ -201,6 +211,19 @@ class VolumeCalculation(object): cv.check_type('atoms', atoms, Mapping) self._atoms = atoms + def set_trigger(self, threshold, trigger_type): + """Set a trigger on the voulme calculation + + Parameters + ---------- + threshold : float + Threshold for the maxmimum standard deviation of volumes + trigger_type : {'variance', 'std_dev', 'rel_err'} + Value type used to halt volume calculation + """ + self.trigger_type = trigger_type + self.threshold = threshold + @classmethod def from_hdf5(cls, filename): """Load stochastic volume calculation results from HDF5 file. @@ -223,11 +246,17 @@ class VolumeCalculation(object): samples = f.attrs['samples'] lower_left = f.attrs['lower_left'] upper_right = f.attrs['upper_right'] + try: threshold = f.attrs['threshold'] except KeyError: threshold = None + try: + trigger_type = f.attrs['trigger_type'].decode() + except KeyError: + trigger_type = None + volumes = {} atoms = {} ids = [] @@ -257,7 +286,11 @@ class VolumeCalculation(object): domains = [openmc.Universe(uid) for uid in ids] # Instantiate the class and assign results - vol = cls(domains, samples, lower_left, upper_right, threshold) + vol = cls(domains, samples, lower_left, upper_right) + + if threshold is not None: + vol.set_trigger(threshold, trigger_type) + vol.volumes = volumes vol.atoms = atoms return vol @@ -305,4 +338,5 @@ class VolumeCalculation(object): if self.threshold: threshold_elem = ET.SubElement(element, "threshold") threshold_elem.text = str(self.threshold) + threshold_elem.set("type", self.trigger_type) return element From 1d27972d5040ad869818492f1e3efbaafe83800e Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Sat, 5 Oct 2019 13:25:25 -0500 Subject: [PATCH 122/158] Updating volume documentation to include info about volume trigger types. --- docs/source/io_formats/volume.rst | 1 + docs/source/usersguide/volume.rst | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/source/io_formats/volume.rst b/docs/source/io_formats/volume.rst index e421de566f..c79255a1c9 100644 --- a/docs/source/io_formats/volume.rst +++ b/docs/source/io_formats/volume.rst @@ -24,6 +24,7 @@ The current version of the volume file format is 1.0. - **upper_right** (*double[3]*) -- Upper-right coordinates of bounding box - **threshold** (*double*) -- Threshold used for volume uncertainty + - **trigger_type** (*char[]*) -- Trigger type used for volume uncertainty **/domain_/** diff --git a/docs/source/usersguide/volume.rst b/docs/source/usersguide/volume.rst index f347102321..d35386b381 100644 --- a/docs/source/usersguide/volume.rst +++ b/docs/source/usersguide/volume.rst @@ -37,15 +37,15 @@ arguments are not necessary. For example, Of course, the volumes that you *need* this capability for are often the ones with complex definitions. -A threshold for the uncertainty in volume estimates can be specified using -::attr::`openmc.VolumeCalculation.threshold` :: +A threshold can be applied for the calculation's variance, standard deviation, +or relative error of volume estimates using ::attr::`openmc.VolumeCalculation.set_trigger`:: - vol_calc.threshold = 1E-05 + vol_calc.set_trigger(1e-05, 'std_dev') If a threshold is provided, calculations will be performed iteratively using the -number of samples specified on the calculation until all volume estimates have a -standard deviation lower than this value. If no threshold is provided, the -calculation will run the number of samples specified once and return the result. +number of samples specified on the calculation until all volume uncertainties are below +the threshold value. If no threshold is provided, the calculation will run the number of +samples specified once and return the result. Once you have one or more :class:`openmc.VolumeCalculation` objects created, you can then assign then to :attr:`Settings.volume_calculations`:: From f3fa1f25782fa1ebc8c8427f7f950be4abf053b7 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Mon, 7 Oct 2019 00:08:47 -0500 Subject: [PATCH 123/158] Fixing issue with INF error values. Adding test case. --- openmc/volume.py | 6 +-- src/volume_calc.cpp | 22 ++++---- .../volume_calc/inputs_true.dat | 8 +++ .../volume_calc/results_true.dat | 54 +++++++++++++++---- tests/regression_tests/volume_calc/test.py | 12 ++++- 5 files changed, 76 insertions(+), 26 deletions(-) diff --git a/openmc/volume.py b/openmc/volume.py index a72f32ab3d..3996ce9dd8 100644 --- a/openmc/volume.py +++ b/openmc/volume.py @@ -336,7 +336,7 @@ class VolumeCalculation(object): ur_elem = ET.SubElement(element, "upper_right") ur_elem.text = ' '.join(str(x) for x in self.upper_right) if self.threshold: - threshold_elem = ET.SubElement(element, "threshold") - threshold_elem.text = str(self.threshold) - threshold_elem.set("type", self.trigger_type) + trigger_elem = ET.SubElement(element, "threshold") + trigger_elem.set("type", self.trigger_type) + trigger_elem.set("threshold", str(self.threshold)) return element diff --git a/src/volume_calc.cpp b/src/volume_calc.cpp index f352e71f82..f65cb89edf 100644 --- a/src/volume_calc.cpp +++ b/src/volume_calc.cpp @@ -69,19 +69,20 @@ VolumeCalculation::VolumeCalculation(pugi::xml_node node) } if (check_for_node(node, "threshold")) { - threshold_ = std::stod(get_node_value(node, "threshold")); + pugi::xml_node threshold_node = node.child("threshold"); + + threshold_ = std::stod(get_node_value(threshold_node, "threshold")); if (threshold_ <= 0.0) { std::stringstream msg; msg << "Invalid error threshold " << threshold_ << " provided for a volume calculation."; fatal_error(msg); } - pugi::xml_node threshold_node = node.child("threshold"); std::string tmp = get_node_value(threshold_node, "type"); if (tmp == "variance") { trigger_type_ = ThresholdType::VARIANCE; } else if (tmp == "std_dev") { - trigger_type_ = ThresholdType::STD_DEV; + trigger_type_ = ThresholdType::STD_DEV; } else if ( tmp == "rel_err") { trigger_type_ = ThresholdType::REL_ERR; } else { @@ -116,11 +117,11 @@ std::vector VolumeCalculation::execute() const { while (true) { // check maximum error value for all domains max_val = -INFTY; - for (const auto& result : results) { + for (const auto& result : results) { double val; switch (trigger_type_) { case ThresholdType::STD_DEV: - val = result.volume[1]; + val = result.volume[1]; break; case ThresholdType::REL_ERR: val = result.volume[1] / result.volume[0]; @@ -129,8 +130,9 @@ std::vector VolumeCalculation::execute() const { val = result.volume[1] * result.volume[1]; break; } - // update max - max_val = std::max(max_val, val); + // update max if entry is valid + if (val > 0.0) { max_val = std::max(max_val, val); } + } // exit once we're below our error limit @@ -306,7 +308,7 @@ std::vector VolumeCalculation::_execute(size_t seed_o for (int j = 0; j < master_indices[i_domain].size(); ++j) { total_hits += master_hits[i_domain][j]; double f = static_cast(master_hits[i_domain][j]) / n_samples_; - double var_f = f*(1.0 - f)/n_samples_; + double var_f = f*(1.0 - f) / n_samples_; int i_material = master_indices[i_domain][j]; if (i_material == MATERIAL_VOID) continue; @@ -340,7 +342,7 @@ std::vector VolumeCalculation::_execute(size_t seed_o } else { result.nuclides.push_back(j); result.atoms.push_back(0.0); - result.uncertainty.push_back(INFTY); + result.uncertainty.push_back(0.0); } } } @@ -384,7 +386,7 @@ void VolumeCalculation::to_hdf5(const std::string& filename, write_attribute(file_id, "trigger_type", "rel_err"); break; } - + } if (domain_type_ == FILTER_CELL) { diff --git a/tests/regression_tests/volume_calc/inputs_true.dat b/tests/regression_tests/volume_calc/inputs_true.dat index 607921af26..1af3454053 100644 --- a/tests/regression_tests/volume_calc/inputs_true.dat +++ b/tests/regression_tests/volume_calc/inputs_true.dat @@ -48,4 +48,12 @@ -1.0 -1.0 -6.0 1.0 1.0 6.0 + + cell + 1 2 3 + 100 + -1.0 -1.0 -6.0 + 1.0 1.0 6.0 + + diff --git a/tests/regression_tests/volume_calc/results_true.dat b/tests/regression_tests/volume_calc/results_true.dat index 466139cd68..4fe3b4b6ef 100644 --- a/tests/regression_tests/volume_calc/results_true.dat +++ b/tests/regression_tests/volume_calc/results_true.dat @@ -2,15 +2,22 @@ Volume calculation 0 Domain 1: 31.47+/-0.07 cm^3 Domain 2: 2.093+/-0.031 cm^3 Domain 3: 2.049+/-0.031 cm^3 - Cell Nuclide Atoms -0 1 U235 (3.482+/-0.008)e+23 -1 1 Mo99 (3.482+/-0.008)e+22 -2 2 H1 (1.400+/-0.021)e+23 -3 2 O16 (7.00+/-0.10)e+22 -4 2 B10 (7.00+/-0.10)e+18 -5 3 H1 (1.370+/-0.021)e+23 -6 3 O16 (6.85+/-0.10)e+22 -7 3 B10 (6.85+/-0.10)e+18 + Cell Nuclide Atoms +0 1 H1 0.0+/-0 +1 1 O16 0.0+/-0 +2 1 B10 0.0+/-0 +3 1 U235 (3.482+/-0.008)e+23 +4 1 Mo99 (3.482+/-0.008)e+22 +5 2 H1 (1.400+/-0.021)e+23 +6 2 O16 (7.00+/-0.10)e+22 +7 2 B10 (7.00+/-0.10)e+18 +8 2 U235 0.0+/-0 +9 2 Mo99 0.0+/-0 +10 3 H1 (1.370+/-0.021)e+23 +11 3 O16 (6.85+/-0.10)e+22 +12 3 B10 (6.85+/-0.10)e+18 +13 3 U235 0.0+/-0 +14 3 Mo99 0.0+/-0 Volume calculation 1 Domain 1: 4.14+/-0.04 cm^3 Domain 2: 31.47+/-0.07 cm^3 @@ -18,8 +25,13 @@ Domain 2: 31.47+/-0.07 cm^3 0 1 H1 (2.770+/-0.029)e+23 1 1 O16 (1.385+/-0.014)e+23 2 1 B10 (1.385+/-0.014)e+19 -3 2 U235 (3.482+/-0.008)e+23 -4 2 Mo99 (3.482+/-0.008)e+22 +3 1 U235 0.0+/-0 +4 1 Mo99 0.0+/-0 +5 2 H1 0.0+/-0 +6 2 O16 0.0+/-0 +7 2 B10 0.0+/-0 +8 2 U235 (3.482+/-0.008)e+23 +9 2 Mo99 (3.482+/-0.008)e+22 Volume calculation 2 Domain 0: 35.61+/-0.07 cm^3 Universe Nuclide Atoms @@ -28,3 +40,23 @@ Domain 0: 35.61+/-0.07 cm^3 2 0 B10 (1.385+/-0.014)e+19 3 0 U235 (3.482+/-0.008)e+23 4 0 Mo99 (3.482+/-0.008)e+22 +Volume calculation 3 +Domain 1: 31.35476+/-0.00010 cm^3 +Domain 2: 2.09147+/-0.00005 cm^3 +Domain 3: 2.110402+/-0.000033 cm^3 + Cell Nuclide Atoms +0 1 H1 0.0+/-0 +1 1 O16 0.0+/-0 +2 1 B10 0.0+/-0 +3 1 U235 (3.474110+/-0.000011)e+23 +4 1 Mo99 (3.474110+/-0.000011)e+22 +5 2 H1 (1.398833+/-0.000031)e+23 +6 2 O16 (6.99416+/-0.00015)e+22 +7 2 B10 (6.99416+/-0.00015)e+18 +8 2 U235 0.0+/-0 +9 2 Mo99 0.0+/-0 +10 3 H1 (1.401947+/-0.000022)e+23 +11 3 O16 (7.00974+/-0.00011)e+22 +12 3 B10 (7.00974+/-0.00011)e+18 +13 3 U235 0.0+/-0 +14 3 Mo99 0.0+/-0 diff --git a/tests/regression_tests/volume_calc/test.py b/tests/regression_tests/volume_calc/test.py index 2affc40852..6bcedd397a 100644 --- a/tests/regression_tests/volume_calc/test.py +++ b/tests/regression_tests/volume_calc/test.py @@ -45,9 +45,12 @@ class VolumeTest(PyAPITestHarness): vol_calcs = [ openmc.VolumeCalculation(list(root.cells.values()), 100000), openmc.VolumeCalculation([water, fuel], 100000, ll, ur), - openmc.VolumeCalculation([root], 100000, ll, ur) + openmc.VolumeCalculation([root], 100000, ll, ur), + openmc.VolumeCalculation(list(root.cells.values()), 100) ] + vol_calcs[-1].set_trigger(1e-04, 'std_dev') + # Define settings settings = openmc.Settings() settings.run_mode = 'volume' @@ -62,6 +65,11 @@ class VolumeTest(PyAPITestHarness): # Read volume calculation results volume_calc = openmc.VolumeCalculation.from_hdf5(filename) + if volume_calc.samples == 100: + assert(volume_calc.trigger_type == 'std_dev') + assert(volume_calc.threshold == 1e-04) + + # Write cell volumes and total # of atoms for each nuclide for uid, volume in sorted(volume_calc.volumes.items()): outstr += 'Domain {}: {} cm^3\n'.format(uid, volume) @@ -74,4 +82,4 @@ class VolumeTest(PyAPITestHarness): def test_volume_calc(): harness = VolumeTest('') - harness._build_inputs() + harness.main() From 1c1fca3d708db4d131806ccc79a00952d8c5c350 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 7 Oct 2019 07:40:56 -0500 Subject: [PATCH 124/158] Respond to @drewejohnson comments on #1371 --- docs/source/methods/cross_sections.rst | 2 +- openmc/deplete/cram.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/source/methods/cross_sections.rst b/docs/source/methods/cross_sections.rst index 70d80a5376..c360805bea 100644 --- a/docs/source/methods/cross_sections.rst +++ b/docs/source/methods/cross_sections.rst @@ -73,7 +73,7 @@ Windowed Multipole Representation --------------------------------- In addition to the usual pointwise representation of cross sections, OpenMC -offers support for an data format called windowed multipole (WMP). This data +offers support for a data format called windowed multipole (WMP). This data format requires less memory than pointwise cross sections, and it allows on-the-fly Doppler broadening to arbitrary temperature. diff --git a/openmc/deplete/cram.py b/openmc/deplete/cram.py index c684b33943..7dad820a9c 100644 --- a/openmc/deplete/cram.py +++ b/openmc/deplete/cram.py @@ -92,7 +92,9 @@ class IPFCramSolver(DepSystemSolver): Provides a :meth:`__call__` that utilizes an incomplete partial factorization (IPF) for the Chebyshev Rational Approximation - Method (CRAM), as described in the following paper: M. Pusa, "`Higher-Order Chebyshev Rational Approximation Method and Application to Burnup Equations `_," Nucl. Sci. Eng., 182:3, 297-318. + Method (CRAM), as described in the following paper: M. Pusa, "`Higher-Order + Chebyshev Rational Approximation Method and Application to Burnup Equations + `_," Nucl. Sci. Eng., 182:3, 297-318. Parameters ---------- From e07fe0632319e40f4e9ca0f56402468b0b2070af Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 4 Oct 2019 14:27:55 -0500 Subject: [PATCH 125/158] Remove -dev tag from version number. Small updates to release notes --- docs/source/conf.py | 2 +- docs/source/releasenotes/0.11.0.rst | 9 ++++----- include/openmc/constants.h | 2 +- openmc/__init__.py | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index f46b11334b..f54a8df0e7 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -81,7 +81,7 @@ copyright = '2011-2019, Massachusetts Institute of Technology and OpenMC contrib # The short X.Y version. version = "0.11" # The full version, including alpha/beta/rc tags. -release = "0.11.0-dev" +release = "0.11.0" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/source/releasenotes/0.11.0.rst b/docs/source/releasenotes/0.11.0.rst index ba9a35d1af..221d576580 100644 --- a/docs/source/releasenotes/0.11.0.rst +++ b/docs/source/releasenotes/0.11.0.rst @@ -2,11 +2,6 @@ What's New in 0.11.0 ==================== -.. note:: - These release notes are for a future release of OpenMC and are still subject - to change. The new features and bug fixes documented here reflect the - current status of the ``develop`` branch of OpenMC. - .. currentmodule:: openmc ------- @@ -90,6 +85,7 @@ Python API Changes Bug Fixes --------- +- `Rotate azimuthal distributions correctly for source sampling `_ - `Fix reading ASCII ACE tables in Python 3 `_ - `Fix bug for distributed temperatures `_ - `Fix bug for distance to boundary in complex cells `_ @@ -108,6 +104,7 @@ This release contains new contributions from the following people: - `Brody Bassett `_ - `Will Boyd `_ - `Andrew Davis `_ +- `Iurii Drobyshev `_ - `Guillaume Giudicelli `_ - `Brittany Grayson `_ - `Zhuoran Han `_ @@ -125,6 +122,7 @@ This release contains new contributions from the following people: - `Isaac Meyer `_ - `April Novak `_ - `Adam Nelson `_ +- `Gavin Ridley `_ - `Jose Salcedo Perez `_ - `Paul Romano `_ - `Sam Shaner `_ @@ -132,3 +130,4 @@ This release contains new contributions from the following people: - `Patrick Shriwise `_ - `John Tramm `_ - `Jiankai Yu `_ +- `Xiaokang Zhang `_ diff --git a/include/openmc/constants.h b/include/openmc/constants.h index 51df59accf..046a7abefd 100644 --- a/include/openmc/constants.h +++ b/include/openmc/constants.h @@ -22,7 +22,7 @@ using double_4dvec = std::vector>>>; constexpr int VERSION_MAJOR {0}; constexpr int VERSION_MINOR {11}; constexpr int VERSION_RELEASE {0}; -constexpr bool VERSION_DEV {true}; +constexpr bool VERSION_DEV {false}; constexpr std::array VERSION {VERSION_MAJOR, VERSION_MINOR, VERSION_RELEASE}; // HDF5 data format diff --git a/openmc/__init__.py b/openmc/__init__.py index 1195db725e..f1703dfea0 100644 --- a/openmc/__init__.py +++ b/openmc/__init__.py @@ -34,4 +34,4 @@ from . import examples # Import a few convencience functions that used to be here from openmc.model import rectangular_prism, hexagonal_prism -__version__ = '0.11.0-dev' +__version__ = '0.11.0' From a85438b8253da4f8b629162eb7fc98acb2e8ad79 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Mon, 7 Oct 2019 01:04:37 -0500 Subject: [PATCH 126/158] Updating DAGMC legacy test. --- tests/regression_tests/dagmc/legacy/test.py | 82 +++++++++++---------- 1 file changed, 44 insertions(+), 38 deletions(-) diff --git a/tests/regression_tests/dagmc/legacy/test.py b/tests/regression_tests/dagmc/legacy/test.py index 963e73226e..1551256f9a 100644 --- a/tests/regression_tests/dagmc/legacy/test.py +++ b/tests/regression_tests/dagmc/legacy/test.py @@ -8,42 +8,48 @@ pytestmark = pytest.mark.skipif( not openmc.lib._dagmc_enabled(), reason="DAGMC CAD geometry is not enabled.") +class DAGMCLegacyTest(PyAPITestHarness): + + def _build_inputs(self): + model = openmc.model.Model() + + # settings + model.settings.batches = 5 + model.settings.inactive = 0 + model.settings.particles = 100 + + source_box = openmc.stats.Box([-4, -4, -4], + [ 4, 4, 4]) + source = openmc.Source(space=source_box) + + model.settings.source = source + + model.settings.dagmc = True + + # tally + tally = openmc.Tally() + tally.scores = ['total'] + tally.filters = [openmc.CellFilter(1)] + model.tallies = [tally] + + # materials + u235 = openmc.Material(name="fuel") + u235.add_nuclide('U235', 1.0, 'ao') + u235.set_density('g/cc', 11) + u235.id = 40 + + water = openmc.Material(name="water") + water.add_nuclide('H1', 2.0, 'ao') + water.add_nuclide('O16', 1.0, 'ao') + water.set_density('g/cc', 1.0) + water.add_s_alpha_beta('c_H_in_H2O') + water.id = 41 + + mats = openmc.Materials([u235, water]) + model.materials = mats + + model.export_to_xml() + def test_dagmc(): - model = openmc.model.Model() - - # settings - model.settings.batches = 5 - model.settings.inactive = 0 - model.settings.particles = 100 - - source_box = openmc.stats.Box([-4, -4, -4], - [ 4, 4, 4]) - source = openmc.Source(space=source_box) - - model.settings.source = source - - model.settings.dagmc = True - - # tally - tally = openmc.Tally() - tally.scores = ['total'] - tally.filters = [openmc.CellFilter(1)] - model.tallies = [tally] - - # materials - u235 = openmc.Material(name="fuel") - u235.add_nuclide('U235', 1.0, 'ao') - u235.set_density('g/cc', 11) - u235.id = 40 - - water = openmc.Material(name="water") - water.add_nuclide('H1', 2.0, 'ao') - water.add_nuclide('O16', 1.0, 'ao') - water.set_density('g/cc', 1.0) - water.add_s_alpha_beta('c_H_in_H2O') - water.id = 41 - - mats = openmc.Materials([u235, water]) - model.materials = mats - - model.export_to_xml() + harness = DAGMCLegacyTest('statepoint.5.h5') + harness.main() From 6e9a78c431e98b048f63bf7061b8c8b1d6907716 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Mon, 7 Oct 2019 18:10:05 -0500 Subject: [PATCH 127/158] Updating check for graveyard and other void volumes. --- src/dagmc.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/dagmc.cpp b/src/dagmc.cpp index 04d4f08c12..9d14c1b864 100644 --- a/src/dagmc.cpp +++ b/src/dagmc.cpp @@ -268,14 +268,12 @@ void load_dagmc_geometry() std::string cmp_str = mat_value; to_lower(cmp_str); - if (cmp_str.find("graveyard") != std::string::npos) { + if (cmp_str == "graveyard") { graveyard = vol_handle; } // material void checks - if (cmp_str.find("void") != std::string::npos || - cmp_str.find("vacuum") != std::string::npos || - cmp_str.find("graveyard") != std::string::npos) { + if (cmp_str == "void" || cmp_str == "vacuum" || cmp_str == "graveyard") { c->material_.push_back(MATERIAL_VOID); } else { if (using_uwuw) { From ec59d02300ed1426e16818c55db6a3cf25c5e14f Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Mon, 7 Oct 2019 23:21:42 -0500 Subject: [PATCH 128/158] Updating test to add check for 'void' in material name. --- tests/regression_tests/dagmc/legacy/dagmc.h5m | Bin 1233364 -> 1233364 bytes .../dagmc/legacy/inputs_true.dat | 2 +- tests/regression_tests/dagmc/legacy/test.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/regression_tests/dagmc/legacy/dagmc.h5m b/tests/regression_tests/dagmc/legacy/dagmc.h5m index fbbe9a34a8c01b245375acaffc5e3f2f33fd60aa..bd50a9691464941dec3526cb67f992138ba0a3f3 100644 GIT binary patch delta 487 zcmcbz*!#+2?+tHcm}~gvOnxu(P{GhZ-@sho(2|J(0yvd3#<)utE17{(=SWJ3<6DzjuN%{yR?>$z2`UOy9?F*MebkDCnJb6{Q7g*2w zx<#93RxpDlHqWnxDqUaql?^OCzxK%F>^4s@+rDt&=7{zy5T*LPFjMd3#<)utE17{(=SWJ3<6DzjuN%{yR?>$z2`UOy9?F*MebkDCnJb6{Q7g*2w zx<#93RxpDlHqWnxDqUaql?^OCzxK%F>^4s@+rDt&=7{zy5T*LPFjM;$MlkmLJ0ugJ-{&l diff --git a/tests/regression_tests/dagmc/legacy/inputs_true.dat b/tests/regression_tests/dagmc/legacy/inputs_true.dat index 8ca49c3246..769a3384c3 100644 --- a/tests/regression_tests/dagmc/legacy/inputs_true.dat +++ b/tests/regression_tests/dagmc/legacy/inputs_true.dat @@ -1,6 +1,6 @@ - + diff --git a/tests/regression_tests/dagmc/legacy/test.py b/tests/regression_tests/dagmc/legacy/test.py index 1551256f9a..624a7f97b5 100644 --- a/tests/regression_tests/dagmc/legacy/test.py +++ b/tests/regression_tests/dagmc/legacy/test.py @@ -33,7 +33,7 @@ class DAGMCLegacyTest(PyAPITestHarness): model.tallies = [tally] # materials - u235 = openmc.Material(name="fuel") + u235 = openmc.Material(name="no-void fuel") u235.add_nuclide('U235', 1.0, 'ao') u235.set_density('g/cc', 11) u235.id = 40 From 51ea427b25214a31033bad675069ee00f3e9d20c Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 8 Oct 2019 09:44:24 -0500 Subject: [PATCH 129/158] Moved trigger logic into main func. --- include/openmc/volume_calc.h | 9 +- src/volume_calc.cpp | 329 +++++++++++++++++++---------------- 2 files changed, 186 insertions(+), 152 deletions(-) diff --git a/include/openmc/volume_calc.h b/include/openmc/volume_calc.h index a7dd750094..982e6a9e75 100644 --- a/include/openmc/volume_calc.h +++ b/include/openmc/volume_calc.h @@ -13,9 +13,10 @@ namespace openmc { enum class ThresholdType { - VARIANCE = 0, - STD_DEV = 1, - REL_ERR = 2 + NONE = 0, + VARIANCE = 1, + STD_DEV = 2, + REL_ERR = 3 }; //============================================================================== @@ -115,7 +116,7 @@ private: // //! \param[in] seed_offset Seed offset used for independent calculations //! \return Vector of results for each user-specified domain - std::vector _execute(size_t seed_offset = 0) const; + std::vector _execute() const; }; diff --git a/src/volume_calc.cpp b/src/volume_calc.cpp index f65cb89edf..f2cb7a7d18 100644 --- a/src/volume_calc.cpp +++ b/src/volume_calc.cpp @@ -107,6 +107,7 @@ std::vector VolumeCalculation::execute() const { // execute the calculation once std::vector results = _execute(); + return results; // if no std. dev. threshold is set, return these resuls if (threshold_ == -1.0) { return results; } @@ -139,7 +140,7 @@ std::vector VolumeCalculation::execute() const { if (max_val <= threshold_) { break; } // perform the calculation - std::vector tmp = _execute(offset); + std::vector tmp = _execute(); offset += n_samples_; // update current results @@ -149,12 +150,13 @@ std::vector VolumeCalculation::execute() const { return results; } -std::vector VolumeCalculation::_execute(size_t seed_offset) const +std::vector VolumeCalculation::_execute() const { // Shared data that is collected from all threads int n = domain_ids_.size(); std::vector> master_indices(n); // List of material indices for each domain std::vector> master_hits(n); // Number of hits for each material in each domain + int iterations = 0; // Divide work over MPI processes size_t min_samples = n_samples_ / mpi::n_procs; @@ -168,187 +170,218 @@ std::vector VolumeCalculation::_execute(size_t seed_o i_end = i_start + min_samples; } - #pragma omp parallel - { - // Variables that are private to each thread - std::vector> indices(n); - std::vector> hits(n); - Particle p; + while (true) { + #pragma omp parallel + { + // Variables that are private to each thread + std::vector> indices(n); + std::vector> hits(n); + Particle p; - prn_set_stream(STREAM_VOLUME); + prn_set_stream(STREAM_VOLUME); - // Sample locations and count hits - #pragma omp for - for (size_t i = i_start; i < i_end; i++) { - set_particle_seed(seed_offset + i); + // Sample locations and count hits + #pragma omp for + for (size_t i = i_start; i < i_end; i++) { + set_particle_seed(iterations * n_samples_ + i); - p.n_coord_ = 1; - Position xi {prn(), prn(), prn()}; - p.r() = lower_left_ + xi*(upper_right_ - lower_left_); - p.u() = {0.5, 0.5, 0.5}; + p.n_coord_ = 1; + Position xi {prn(), prn(), prn()}; + p.r() = lower_left_ + xi*(upper_right_ - lower_left_); + p.u() = {0.5, 0.5, 0.5}; - // If this location is not in the geometry at all, move on to next block - if (!find_cell(&p, false)) continue; + // If this location is not in the geometry at all, move on to next block + if (!find_cell(&p, false)) continue; - if (domain_type_ == FILTER_MATERIAL) { - if (p.material_ != MATERIAL_VOID) { - for (int i_domain = 0; i_domain < n; i_domain++) { - if (model::materials[p.material_]->id_ == domain_ids_[i_domain]) { - this->check_hit(p.material_, indices[i_domain], hits[i_domain]); - break; + if (domain_type_ == FILTER_MATERIAL) { + if (p.material_ != MATERIAL_VOID) { + for (int i_domain = 0; i_domain < n; i_domain++) { + if (model::materials[p.material_]->id_ == domain_ids_[i_domain]) { + this->check_hit(p.material_, indices[i_domain], hits[i_domain]); + break; + } } } - } - } else if (domain_type_ == FILTER_CELL) { - for (int level = 0; level < p.n_coord_; ++level) { - for (int i_domain=0; i_domain < n; i_domain++) { - if (model::cells[p.coord_[level].cell]->id_ == domain_ids_[i_domain]) { - this->check_hit(p.material_, indices[i_domain], hits[i_domain]); - break; + } else if (domain_type_ == FILTER_CELL) { + for (int level = 0; level < p.n_coord_; ++level) { + for (int i_domain=0; i_domain < n; i_domain++) { + if (model::cells[p.coord_[level].cell]->id_ == domain_ids_[i_domain]) { + this->check_hit(p.material_, indices[i_domain], hits[i_domain]); + break; + } } } - } - } else if (domain_type_ == FILTER_UNIVERSE) { - for (int level = 0; level < p.n_coord_; ++level) { - for (int i_domain = 0; i_domain < n; ++i_domain) { - if (model::universes[p.coord_[level].universe]->id_ == domain_ids_[i_domain]) { - check_hit(p.material_, indices[i_domain], hits[i_domain]); - break; + } else if (domain_type_ == FILTER_UNIVERSE) { + for (int level = 0; level < p.n_coord_; ++level) { + for (int i_domain = 0; i_domain < n; ++i_domain) { + if (model::universes[p.coord_[level].universe]->id_ == domain_ids_[i_domain]) { + check_hit(p.material_, indices[i_domain], hits[i_domain]); + break; + } } } } } - } - // At this point, each thread has its own pair of index/hits lists and we now - // need to reduce them. OpenMP is not nearly smart enough to do this on its own, - // so we have to manually reduce them + // At this point, each thread has its own pair of index/hits lists and we now + // need to reduce them. OpenMP is not nearly smart enough to do this on its own, + // so we have to manually reduce them -#ifdef _OPENMP - #pragma omp for ordered schedule(static) - for (int i = 0; i < omp_get_num_threads(); ++i) { - #pragma omp ordered - for (int i_domain = 0; i_domain < n; ++i_domain) { - for (int j = 0; j < indices[i_domain].size(); ++j) { - // Check if this material has been added to the master list and if so, - // accumulate the number of hits - bool already_added = false; - for (int k = 0; k < master_indices[i_domain].size(); k++) { - if (indices[i_domain][j] == master_indices[i_domain][k]) { - master_hits[i_domain][k] += hits[i_domain][j]; - already_added = true; + #ifdef _OPENMP + int n_threads = omp_get_num_threads(); + #else + int n_threads = 1; + #endif + + #pragma omp for ordered schedule(static) + for (int i = 0; i < n_threads; ++i) { + #pragma omp ordered + for (int i_domain = 0; i_domain < n; ++i_domain) { + for (int j = 0; j < indices[i_domain].size(); ++j) { + // Check if this material has been added to the master list and if so, + // accumulate the number of hits + bool already_added = false; + for (int k = 0; k < master_indices[i_domain].size(); k++) { + if (indices[i_domain][j] == master_indices[i_domain][k]) { + master_hits[i_domain][k] += hits[i_domain][j]; + already_added = true; + } + } + if (!already_added) { + // If we made it here, the material hasn't yet been added to the master + // list, so add entries to the master indices and master hits lists + master_indices[i_domain].push_back(indices[i_domain][j]); + master_hits[i_domain].push_back(hits[i_domain][j]); } - } - if (!already_added) { - // If we made it here, the material hasn't yet been added to the master - // list, so add entries to the master indices and master hits lists - master_indices[i_domain].push_back(indices[i_domain][j]); - master_hits[i_domain].push_back(hits[i_domain][j]); } } } - } -#else - master_indices = indices; - master_hits = hits; -#endif + prn_set_stream(STREAM_TRACKING); + } // omp parallel - prn_set_stream(STREAM_TRACKING); - } // omp parallel + // Reduce hits onto master process - // Reduce hits onto master process + // Determine volume of bounding box + Position d {upper_right_ - lower_left_}; + double volume_sample = d.x*d.y*d.z; - // Determine volume of bounding box - Position d {upper_right_ - lower_left_}; - double volume_sample = d.x*d.y*d.z; + // bump iteration counter and get total number + // of samples at this point + iterations++; + size_t total_samples = iterations * n_samples_; - // Set size for members of the Result struct - std::vector results(n); + double max_vol_err = -INFTY; - for (int i_domain = 0; i_domain < n; ++i_domain) { - // Get reference to result for this domain - auto& result {results[i_domain]}; + // Set size for members of the Result struct + std::vector results(n); - // Create 2D array to store atoms/uncertainty for each nuclide. Later this - // is compressed into vectors storing only those nuclides that are non-zero - auto n_nuc = data::nuclides.size(); - xt::xtensor atoms({n_nuc, 2}, 0.0); + for (int i_domain = 0; i_domain < n; ++i_domain) { + // Get reference to result for this domain + auto& result {results[i_domain]}; -#ifdef OPENMC_MPI - if (mpi::master) { - for (int j = 1; j < mpi::n_procs; j++) { - int q; - MPI_Recv(&q, 1, MPI_INTEGER, j, 0, mpi::intracomm, MPI_STATUS_IGNORE); + // Create 2D array to store atoms/uncertainty for each nuclide. Later this + // is compressed into vectors storing only those nuclides that are non-zero + auto n_nuc = data::nuclides.size(); + xt::xtensor atoms({n_nuc, 2}, 0.0); + + #ifdef OPENMC_MPI + if (mpi::master) { + for (int j = 1; j < mpi::n_procs; j++) { + int q; + MPI_Recv(&q, 1, MPI_INTEGER, j, 0, mpi::intracomm, MPI_STATUS_IGNORE); + int buffer[2*q]; + MPI_Recv(&buffer[0], 2*q, MPI_INTEGER, j, 1, mpi::intracomm, MPI_STATUS_IGNORE); + for (int k = 0; k < q; ++k) { + for (int m = 0; m < master_indices[i_domain].size(); ++m) { + if (buffer[2*k] == master_indices[i_domain][m]) { + master_hits[i_domain][m] += buffer[2*k + 1]; + break; + } + } + } + } + } else { + int q = master_indices[i_domain].size(); int buffer[2*q]; - MPI_Recv(&buffer[0], 2*q, MPI_INTEGER, j, 1, mpi::intracomm, MPI_STATUS_IGNORE); for (int k = 0; k < q; ++k) { - for (int m = 0; m < master_indices[i_domain].size(); ++m) { - if (buffer[2*k] == master_indices[i_domain][m]) { - master_hits[i_domain][m] += buffer[2*k + 1]; + buffer[2*k] = master_indices[i_domain][k]; + buffer[2*k + 1] = master_hits[i_domain][k]; + } + + MPI_Send(&q, 1, MPI_INTEGER, 0, 0, mpi::intracomm); + MPI_Send(&buffer[0], 2*q, MPI_INTEGER, 0, 1, mpi::intracomm); + } + #endif + + if (mpi::master) { + int total_hits = 0; + for (int j = 0; j < master_indices[i_domain].size(); ++j) { + total_hits += master_hits[i_domain][j]; + double f = static_cast(master_hits[i_domain][j]) / total_samples; + double var_f = f*(1.0 - f) / total_samples; + + int i_material = master_indices[i_domain][j]; + if (i_material == MATERIAL_VOID) continue; + + const auto& mat = model::materials[i_material]; + for (int k = 0; k < mat->nuclide_.size(); ++k) { + // Accumulate nuclide density + int i_nuclide = mat->nuclide_[k]; + atoms(i_nuclide, 0) += mat->atom_density_[k] * f; + atoms(i_nuclide, 1) += std::pow(mat->atom_density_[k], 2) * var_f; + } + } + + // Determine volume + result.volume[0] = static_cast(total_hits) / total_samples * volume_sample; + result.volume[1] = std::sqrt(result.volume[0] + * (volume_sample - result.volume[0]) / total_samples); + result.num_samples = total_samples; + + // update threshold value if needed + if (trigger_type_ != ThresholdType::NONE) { + double val = 0.0; + switch (trigger_type_) { + case ThresholdType::STD_DEV: + val = result.volume[1]; break; - } + case ThresholdType::REL_ERR: + val = result.volume[1] / result.volume[0]; + break; + case ThresholdType::VARIANCE: + val = result.volume[1] * result.volume[1]; + break; + } + // update max if entry is valid + if (val > 0.0) { max_vol_err = std::max(max_vol_err, val); } + } + + for (int j = 0; j < n_nuc; ++j) { + // Determine total number of atoms. At this point, we have values in + // atoms/b-cm. To get to atoms we multiply by 10^24 V. + double mean = 1.0e24 * volume_sample * atoms(j, 0); + double stdev = 1.0e24 * volume_sample * std::sqrt(atoms(j, 1)); + + // Convert full arrays to vectors + if (mean > 0.0) { + result.nuclides.push_back(j); + result.atoms.push_back(mean); + result.uncertainty.push_back(stdev); + } else { + result.nuclides.push_back(j); + result.atoms.push_back(0.0); + result.uncertainty.push_back(0.0); } } } - } else { - int q = master_indices[i_domain].size(); - int buffer[2*q]; - for (int k = 0; k < q; ++k) { - buffer[2*k] = master_indices[i_domain][k]; - buffer[2*k + 1] = master_hits[i_domain][k]; - } - - MPI_Send(&q, 1, MPI_INTEGER, 0, 0, mpi::intracomm); - MPI_Send(&buffer[0], 2*q, MPI_INTEGER, 0, 1, mpi::intracomm); } -#endif - if (mpi::master) { - int total_hits = 0; - for (int j = 0; j < master_indices[i_domain].size(); ++j) { - total_hits += master_hits[i_domain][j]; - double f = static_cast(master_hits[i_domain][j]) / n_samples_; - double var_f = f*(1.0 - f) / n_samples_; + // return results of the calculation + if (trigger_type_ == ThresholdType::NONE || max_vol_err < threshold_) { + return results; + } - int i_material = master_indices[i_domain][j]; - if (i_material == MATERIAL_VOID) continue; - - const auto& mat = model::materials[i_material]; - for (int k = 0; k < mat->nuclide_.size(); ++k) { - // Accumulate nuclide density - int i_nuclide = mat->nuclide_[k]; - atoms(i_nuclide, 0) += mat->atom_density_[k] * f; - atoms(i_nuclide, 1) += std::pow(mat->atom_density_[k], 2) * var_f; - } - } - - // Determine volume - result.volume[0] = static_cast(total_hits) / n_samples_ * volume_sample; - result.volume[1] = std::sqrt(result.volume[0] - * (volume_sample - result.volume[0]) / n_samples_); - result.num_samples = n_samples_; - - for (int j = 0; j < n_nuc; ++j) { - // Determine total number of atoms. At this point, we have values in - // atoms/b-cm. To get to atoms we multiply by 10^24 V. - double mean = 1.0e24 * volume_sample * atoms(j, 0); - double stdev = 1.0e24 * volume_sample * std::sqrt(atoms(j, 1)); - - // Convert full arrays to vectors - if (mean > 0.0) { - result.nuclides.push_back(j); - result.atoms.push_back(mean); - result.uncertainty.push_back(stdev); - } else { - result.nuclides.push_back(j); - result.atoms.push_back(0.0); - result.uncertainty.push_back(0.0); - } - } - } - } - - return results; + } // end while } void VolumeCalculation::to_hdf5(const std::string& filename, From 4c344e962617b193130ddc39260c6e2757ca67fd Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 8 Oct 2019 09:46:52 -0500 Subject: [PATCH 130/158] Removing unused code. --- include/openmc/volume_calc.h | 52 ++---------------------------------- src/volume_calc.cpp | 51 ++--------------------------------- 2 files changed, 4 insertions(+), 99 deletions(-) diff --git a/include/openmc/volume_calc.h b/include/openmc/volume_calc.h index 982e6a9e75..74331de877 100644 --- a/include/openmc/volume_calc.h +++ b/include/openmc/volume_calc.h @@ -31,48 +31,8 @@ public: std::array volume; //!< Mean/standard deviation of volume std::vector nuclides; //!< Index of nuclides std::vector atoms; //!< Number of atoms for each nuclide - std::vector uncertainty; //!< Uncertainty on number of atoms - size_t num_samples; - - Result& operator +=( const Result& other) { - Expects(volume.size() == other.volume.size()); - Expects(atoms.size() == atoms.size()); - - auto& a_samples = num_samples; - auto& b_samples = other.num_samples; - - size_t total_samples = num_samples + other.num_samples; - - for (int i = 0; i < volume.size(); i++) { - // calculate weighted average of volume results - auto& a_vol = volume[0]; - auto& b_vol = other.volume[0]; - volume[0] = (a_samples * a_vol + b_samples * b_vol) / total_samples; - - // propagate error - auto& a_err = volume[1]; - auto& b_err = other.volume[1]; - volume[1] = std::sqrt(a_samples * a_err * a_err + b_samples * b_err * b_err) / total_samples; - } - - for (int i = 0; i < atoms.size(); i++) { - // calculate weighted average of atom results - auto& a_atoms = atoms[i]; - auto& b_atoms = other.atoms[i]; - atoms[i] = (a_samples * a_atoms + b_samples * b_atoms) / total_samples; - - // propagate error - auto& a_err = uncertainty[i]; - auto& b_err = other.uncertainty[i]; - uncertainty[i] = std::sqrt(a_samples * a_err * a_err + b_samples * b_err * b_err) / total_samples; - } - - // update number of samples on the returned set of results; - num_samples = total_samples; - - return *this; - } - + std::vector uncertainty; //!< Uncertainty on number of atoms + int iterations; }; // Results for a single domain // Constructors @@ -111,14 +71,6 @@ private: void check_hit(int i_material, std::vector& indices, std::vector& hits) const; - //! \brief Perform calculation for domain volumes and average nuclide density - //! using n_samples_ - // - //! \param[in] seed_offset Seed offset used for independent calculations - //! \return Vector of results for each user-specified domain - std::vector _execute() const; - - }; //============================================================================== diff --git a/src/volume_calc.cpp b/src/volume_calc.cpp index f2cb7a7d18..21d9c3cb08 100644 --- a/src/volume_calc.cpp +++ b/src/volume_calc.cpp @@ -103,54 +103,7 @@ VolumeCalculation::VolumeCalculation(pugi::xml_node node) } -std::vector VolumeCalculation::execute() const { - - // execute the calculation once - std::vector results = _execute(); - return results; - - // if no std. dev. threshold is set, return these resuls - if (threshold_ == -1.0) { return results; } - - size_t offset = n_samples_; - double max_val; - - while (true) { - // check maximum error value for all domains - max_val = -INFTY; - for (const auto& result : results) { - double val; - switch (trigger_type_) { - case ThresholdType::STD_DEV: - val = result.volume[1]; - break; - case ThresholdType::REL_ERR: - val = result.volume[1] / result.volume[0]; - break; - case ThresholdType::VARIANCE: - val = result.volume[1] * result.volume[1]; - break; - } - // update max if entry is valid - if (val > 0.0) { max_val = std::max(max_val, val); } - - } - - // exit once we're below our error limit - if (max_val <= threshold_) { break; } - - // perform the calculation - std::vector tmp = _execute(); - offset += n_samples_; - - // update current results - for (int i = 0; i < results.size(); i++) { results[i] += tmp[i]; } - } - - return results; -} - -std::vector VolumeCalculation::_execute() const +std::vector VolumeCalculation::execute() const { // Shared data that is collected from all threads int n = domain_ids_.size(); @@ -336,7 +289,7 @@ std::vector VolumeCalculation::_execute() const result.volume[0] = static_cast(total_hits) / total_samples * volume_sample; result.volume[1] = std::sqrt(result.volume[0] * (volume_sample - result.volume[0]) / total_samples); - result.num_samples = total_samples; + result.iterations = iterations; // update threshold value if needed if (trigger_type_ != ThresholdType::NONE) { From 24bfa0051a09ef06286b36b61537c3be921100ac Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 8 Oct 2019 10:55:04 -0500 Subject: [PATCH 131/158] Updating default trigger value and how trigger value is written to file --- include/openmc/volume_calc.h | 2 +- src/volume_calc.cpp | 18 +++---- .../volume_calc/results_true.dat | 54 ++++--------------- 3 files changed, 20 insertions(+), 54 deletions(-) diff --git a/include/openmc/volume_calc.h b/include/openmc/volume_calc.h index 74331de877..33d10209ef 100644 --- a/include/openmc/volume_calc.h +++ b/include/openmc/volume_calc.h @@ -56,7 +56,7 @@ public: int domain_type_; //!< Type of domain (cell, material, etc.) size_t n_samples_; //!< Number of samples to use double threshold_ {-1.0}; //!< Error threshold for domain volumes - ThresholdType trigger_type_; + ThresholdType trigger_type_ {ThresholdType::NONE}; Position lower_left_; //!< Lower-left position of bounding box Position upper_right_; //!< Upper-right position of bounding box std::vector domain_ids_; //!< IDs of domains to find volumes of diff --git a/src/volume_calc.cpp b/src/volume_calc.cpp index 21d9c3cb08..23bb08f822 100644 --- a/src/volume_calc.cpp +++ b/src/volume_calc.cpp @@ -320,10 +320,6 @@ std::vector VolumeCalculation::execute() const result.nuclides.push_back(j); result.atoms.push_back(mean); result.uncertainty.push_back(stdev); - } else { - result.nuclides.push_back(j); - result.atoms.push_back(0.0); - result.uncertainty.push_back(0.0); } } } @@ -358,21 +354,23 @@ void VolumeCalculation::to_hdf5(const std::string& filename, write_attribute(file_id, "samples", n_samples_); write_attribute(file_id, "lower_left", lower_left_); write_attribute(file_id, "upper_right", upper_right_); - if (threshold_ != -1.0) { + // Write trigger info + if (trigger_type_ != ThresholdType::NONE) { + write_attribute(file_id, "iterations", results[0].iterations); write_attribute(file_id, "threshold", threshold_); - + std::string trigger_str; switch(trigger_type_) { case ThresholdType::VARIANCE: - write_attribute(file_id, "trigger_type", "variance"); + trigger_str = "variance"; break; case ThresholdType::STD_DEV: - write_attribute(file_id, "trigger_type", "std_dev"); + trigger_str = "std_dev"; break; case ThresholdType::REL_ERR: - write_attribute(file_id, "trigger_type", "rel_err"); + trigger_str = "rel_err"; break; } - + write_attribute(file_id, "trigger_type", trigger_str); } if (domain_type_ == FILTER_CELL) { diff --git a/tests/regression_tests/volume_calc/results_true.dat b/tests/regression_tests/volume_calc/results_true.dat index 4fe3b4b6ef..466139cd68 100644 --- a/tests/regression_tests/volume_calc/results_true.dat +++ b/tests/regression_tests/volume_calc/results_true.dat @@ -2,22 +2,15 @@ Volume calculation 0 Domain 1: 31.47+/-0.07 cm^3 Domain 2: 2.093+/-0.031 cm^3 Domain 3: 2.049+/-0.031 cm^3 - Cell Nuclide Atoms -0 1 H1 0.0+/-0 -1 1 O16 0.0+/-0 -2 1 B10 0.0+/-0 -3 1 U235 (3.482+/-0.008)e+23 -4 1 Mo99 (3.482+/-0.008)e+22 -5 2 H1 (1.400+/-0.021)e+23 -6 2 O16 (7.00+/-0.10)e+22 -7 2 B10 (7.00+/-0.10)e+18 -8 2 U235 0.0+/-0 -9 2 Mo99 0.0+/-0 -10 3 H1 (1.370+/-0.021)e+23 -11 3 O16 (6.85+/-0.10)e+22 -12 3 B10 (6.85+/-0.10)e+18 -13 3 U235 0.0+/-0 -14 3 Mo99 0.0+/-0 + Cell Nuclide Atoms +0 1 U235 (3.482+/-0.008)e+23 +1 1 Mo99 (3.482+/-0.008)e+22 +2 2 H1 (1.400+/-0.021)e+23 +3 2 O16 (7.00+/-0.10)e+22 +4 2 B10 (7.00+/-0.10)e+18 +5 3 H1 (1.370+/-0.021)e+23 +6 3 O16 (6.85+/-0.10)e+22 +7 3 B10 (6.85+/-0.10)e+18 Volume calculation 1 Domain 1: 4.14+/-0.04 cm^3 Domain 2: 31.47+/-0.07 cm^3 @@ -25,13 +18,8 @@ Domain 2: 31.47+/-0.07 cm^3 0 1 H1 (2.770+/-0.029)e+23 1 1 O16 (1.385+/-0.014)e+23 2 1 B10 (1.385+/-0.014)e+19 -3 1 U235 0.0+/-0 -4 1 Mo99 0.0+/-0 -5 2 H1 0.0+/-0 -6 2 O16 0.0+/-0 -7 2 B10 0.0+/-0 -8 2 U235 (3.482+/-0.008)e+23 -9 2 Mo99 (3.482+/-0.008)e+22 +3 2 U235 (3.482+/-0.008)e+23 +4 2 Mo99 (3.482+/-0.008)e+22 Volume calculation 2 Domain 0: 35.61+/-0.07 cm^3 Universe Nuclide Atoms @@ -40,23 +28,3 @@ Domain 0: 35.61+/-0.07 cm^3 2 0 B10 (1.385+/-0.014)e+19 3 0 U235 (3.482+/-0.008)e+23 4 0 Mo99 (3.482+/-0.008)e+22 -Volume calculation 3 -Domain 1: 31.35476+/-0.00010 cm^3 -Domain 2: 2.09147+/-0.00005 cm^3 -Domain 3: 2.110402+/-0.000033 cm^3 - Cell Nuclide Atoms -0 1 H1 0.0+/-0 -1 1 O16 0.0+/-0 -2 1 B10 0.0+/-0 -3 1 U235 (3.474110+/-0.000011)e+23 -4 1 Mo99 (3.474110+/-0.000011)e+22 -5 2 H1 (1.398833+/-0.000031)e+23 -6 2 O16 (6.99416+/-0.00015)e+22 -7 2 B10 (6.99416+/-0.00015)e+18 -8 2 U235 0.0+/-0 -9 2 Mo99 0.0+/-0 -10 3 H1 (1.401947+/-0.000022)e+23 -11 3 O16 (7.00974+/-0.00011)e+22 -12 3 B10 (7.00974+/-0.00011)e+18 -13 3 U235 0.0+/-0 -14 3 Mo99 0.0+/-0 From 884a083a50eaf28ac86a3ba35ad96572534724c0 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 8 Oct 2019 10:55:45 -0500 Subject: [PATCH 132/158] Adding iteration property to volume calculation in Python API. --- openmc/volume.py | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/openmc/volume.py b/openmc/volume.py index 3996ce9dd8..99eadb57d4 100644 --- a/openmc/volume.py +++ b/openmc/volume.py @@ -58,7 +58,9 @@ class VolumeCalculation(object): volumes : dict Dictionary mapping unique IDs of domains to estimated volumes in cm^3. threshold : float - Threshold for the maxmimum standard deviation of volumes + Threshold for the maxmimum standard deviation of volumes. + iterations : int + Number of iterations over samples (for calculations with a trigger). trigger_type : {'variance', 'std_dev', 'rel_err'} Value type used to halt volume calculation @@ -68,6 +70,7 @@ class VolumeCalculation(object): self._volumes = {} self._threshold = None self._trigger_type = None + self._iterations = None cv.check_type('domains', domains, Iterable, (openmc.Cell, openmc.Material, openmc.Universe)) @@ -139,6 +142,10 @@ class VolumeCalculation(object): def trigger_type(self): return self._trigger_type + @property + def iterations(self): + return self._iterations + @property def domain_type(self): return self._domain_type @@ -189,10 +196,8 @@ class VolumeCalculation(object): @threshold.setter def threshold(self, threshold): name = 'volume std. dev. threshold' - cv.check_type(name, threshold, Real) - if (threshold <= 0.0): - raise ValueError("Invalid value '{}' (<= 0.0) provided for volume " - "calculation threshold.".format(threshold)) + cv.check_type(name, threshold, Real) + cv.check_greater_than(name, threshold, 0.0) self._threshold = threshold @trigger_type.setter @@ -201,6 +206,13 @@ class VolumeCalculation(object): ['variance', 'std_dev', 'rel_err']) self._trigger_type = trigger_type + @iterations.setter + def iterations(self, iterations): + name = 'volume calculation iterations' + cv.check_type(name, iterations, Integral) + cv.check_greater_than(name, iterations, 0) + self._iterations = iterations + @volumes.setter def volumes(self, volumes): cv.check_type('volumes', volumes, Mapping) @@ -247,15 +259,9 @@ class VolumeCalculation(object): lower_left = f.attrs['lower_left'] upper_right = f.attrs['upper_right'] - try: - threshold = f.attrs['threshold'] - except KeyError: - threshold = None - - try: - trigger_type = f.attrs['trigger_type'].decode() - except KeyError: - trigger_type = None + threshold = f.attrs.get('threshold') + trigger_type = f.attrs.get('trigger_type') + iterations = f.attrs.get('iterations', 1) volumes = {} atoms = {} @@ -288,9 +294,10 @@ class VolumeCalculation(object): # Instantiate the class and assign results vol = cls(domains, samples, lower_left, upper_right) - if threshold is not None: - vol.set_trigger(threshold, trigger_type) + if trigger_type is not None: + vol.set_trigger(threshold, trigger_type.decode()) + vol.iterations = iterations vol.volumes = volumes vol.atoms = atoms return vol From 17ebd34b452b94c8d9431bdae3a3d99af835e878 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 8 Oct 2019 10:55:54 -0500 Subject: [PATCH 133/158] Updating tests. --- .../volume_calc/inputs_true.dat | 18 ++++++- .../volume_calc/results_true.dat | 35 ++++++++++++++ tests/regression_tests/volume_calc/test.py | 48 +++++++++++++++++-- 3 files changed, 95 insertions(+), 6 deletions(-) diff --git a/tests/regression_tests/volume_calc/inputs_true.dat b/tests/regression_tests/volume_calc/inputs_true.dat index 1af3454053..aaf6d8b005 100644 --- a/tests/regression_tests/volume_calc/inputs_true.dat +++ b/tests/regression_tests/volume_calc/inputs_true.dat @@ -54,6 +54,22 @@ 100 -1.0 -1.0 -6.0 1.0 1.0 6.0 - + + + + material + 1 2 + 100 + -1.0 -1.0 -6.0 + 1.0 1.0 6.0 + + + + cell + 1 2 3 + 100 + -1.0 -1.0 -6.0 + 1.0 1.0 6.0 + diff --git a/tests/regression_tests/volume_calc/results_true.dat b/tests/regression_tests/volume_calc/results_true.dat index 466139cd68..a31360abca 100644 --- a/tests/regression_tests/volume_calc/results_true.dat +++ b/tests/regression_tests/volume_calc/results_true.dat @@ -28,3 +28,38 @@ Domain 0: 35.61+/-0.07 cm^3 2 0 B10 (1.385+/-0.014)e+19 3 0 U235 (3.482+/-0.008)e+23 4 0 Mo99 (3.482+/-0.008)e+22 +Volume calculation 3 +Domain 1: 31.47+/-0.10 cm^3 +Domain 2: 2.10+/-0.04 cm^3 +Domain 3: 2.11+/-0.04 cm^3 + Cell Nuclide Atoms +0 1 U235 (3.481+/-0.011)e+23 +1 1 Mo99 (3.481+/-0.011)e+22 +2 2 H1 (1.403+/-0.029)e+23 +3 2 O16 (7.01+/-0.14)e+22 +4 2 B10 (7.01+/-0.14)e+18 +5 3 H1 (1.412+/-0.029)e+23 +6 3 O16 (7.06+/-0.14)e+22 +7 3 B10 (7.06+/-0.14)e+18 +Volume calculation 4 +Domain 1: 4.5+/-0.4 cm^3 +Domain 2: 30.5+/-0.7 cm^3 + Material Nuclide Atoms +0 1 H1 (3.02+/-0.30)e+23 +1 1 O16 (1.51+/-0.15)e+23 +2 1 B10 (1.51+/-0.15)e+19 +3 2 U235 (3.38+/-0.08)e+23 +4 2 Mo99 (3.38+/-0.08)e+22 +Volume calculation 5 +Domain 1: 31.51+/-0.22 cm^3 +Domain 2: 2.13+/-0.10 cm^3 +Domain 3: 2.11+/-0.10 cm^3 + Cell Nuclide Atoms +0 1 U235 (3.486+/-0.025)e+23 +1 1 Mo99 (3.486+/-0.025)e+22 +2 2 H1 (1.42+/-0.06)e+23 +3 2 O16 (7.11+/-0.32)e+22 +4 2 B10 (7.11+/-0.32)e+18 +5 3 H1 (1.41+/-0.06)e+23 +6 3 O16 (7.05+/-0.32)e+22 +7 3 B10 (7.05+/-0.32)e+18 diff --git a/tests/regression_tests/volume_calc/test.py b/tests/regression_tests/volume_calc/test.py index 6bcedd397a..8f003aa68a 100644 --- a/tests/regression_tests/volume_calc/test.py +++ b/tests/regression_tests/volume_calc/test.py @@ -8,6 +8,19 @@ from tests.testing_harness import PyAPITestHarness class VolumeTest(PyAPITestHarness): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.exp_std_dev = 1e-01 + self.std_dev_iters = 521 + + self.exp_rel_err = 1e-01 + self.rel_err_iters = 10 + + self.exp_variance = 5e-02 + self.variance_iters = 105 + def _build_inputs(self): # Define materials water = openmc.Material(1) @@ -46,10 +59,17 @@ class VolumeTest(PyAPITestHarness): openmc.VolumeCalculation(list(root.cells.values()), 100000), openmc.VolumeCalculation([water, fuel], 100000, ll, ur), openmc.VolumeCalculation([root], 100000, ll, ur), + openmc.VolumeCalculation(list(root.cells.values()), 100), + openmc.VolumeCalculation([water, fuel], 100, ll, ur), openmc.VolumeCalculation(list(root.cells.values()), 100) ] - vol_calcs[-1].set_trigger(1e-04, 'std_dev') + + vol_calcs[3].set_trigger(self.exp_std_dev, 'std_dev') + + vol_calcs[4].set_trigger(self.exp_rel_err, 'rel_err') + + vol_calcs[5].set_trigger(self.exp_variance, 'variance') # Define settings settings = openmc.Settings() @@ -65,10 +85,28 @@ class VolumeTest(PyAPITestHarness): # Read volume calculation results volume_calc = openmc.VolumeCalculation.from_hdf5(filename) - if volume_calc.samples == 100: + if i == 3: assert(volume_calc.trigger_type == 'std_dev') - assert(volume_calc.threshold == 1e-04) - + assert(volume_calc.threshold == self.exp_std_dev) + assert(volume_calc.iterations == self.std_dev_iters) + for vol in volume_calc.volumes.values(): + assert(vol.std_dev <= self.exp_std_dev) + elif i == 4: + assert(volume_calc.trigger_type == 'rel_err') + assert(volume_calc.threshold == self.exp_rel_err) + assert(volume_calc.iterations == self.rel_err_iters) + for vol in volume_calc.volumes.values(): + assert(vol.std_dev/vol.nominal_value <= self.exp_rel_err) + elif i == 5: + assert(volume_calc.trigger_type == 'variance') + assert(volume_calc.threshold == self.exp_variance) + assert(volume_calc.iterations == self.variance_iters) + for vol in volume_calc.volumes.values(): + assert(vol.std_dev * vol.std_dev <= self.exp_variance) + else: + assert(volume_calc.trigger_type == None) + assert(volume_calc.threshold == None) + assert(volume_calc.iterations == 1) # Write cell volumes and total # of atoms for each nuclide for uid, volume in sorted(volume_calc.volumes.items()): @@ -82,4 +120,4 @@ class VolumeTest(PyAPITestHarness): def test_volume_calc(): harness = VolumeTest('') - harness.main() + harness.main() \ No newline at end of file From b2c064cc357415b4699302dabf7e0ea6d1f40d9d Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 8 Oct 2019 11:34:26 -0500 Subject: [PATCH 134/158] Updates for MPI runs. --- src/volume_calc.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/volume_calc.cpp b/src/volume_calc.cpp index 23bb08f822..13cfcf569e 100644 --- a/src/volume_calc.cpp +++ b/src/volume_calc.cpp @@ -325,11 +325,30 @@ std::vector VolumeCalculation::execute() const } } +#ifdef OPENMC_MPI + // update maximum error value on all processes + if (mpi::master) { + for (int i = 1; i < mpi::n_procs; i++) { + MPI_Send(&max_vol_err, 1, MPI_DOUBLE, i, 0, mpi::intracomm); + } + } else { + MPI_Recv(&max_vol_err, 1, MPI_DOUBLE, 0, 0, mpi::intracomm, MPI_STATUS_IGNORE); + } +#endif + // return results of the calculation if (trigger_type_ == ThresholdType::NONE || max_vol_err < threshold_) { return results; } +#ifdef OPENMC_MPI + // if iterating in MPI, need to zero indices and hits to they aren't counted twice + if (!mpi::master) { + for (auto& v : master_indices) { std::fill(v.begin(), v.end(), 0.0); } + for (auto& v : master_hits) { std::fill(v.begin(), v.end(), 0.0); } + } +#endif + } // end while } From eeb8b3cbb337635387de225c0d4c31819ac4e0df Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 8 Oct 2019 13:15:30 -0500 Subject: [PATCH 135/158] Updating name VolumeCalculation attribute. --- include/openmc/volume_calc.h | 4 ++-- src/volume_calc.cpp | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/include/openmc/volume_calc.h b/include/openmc/volume_calc.h index 33d10209ef..dade67f5af 100644 --- a/include/openmc/volume_calc.h +++ b/include/openmc/volume_calc.h @@ -12,7 +12,7 @@ namespace openmc { -enum class ThresholdType { +enum class VolumeTriggerMetric { NONE = 0, VARIANCE = 1, STD_DEV = 2, @@ -56,7 +56,7 @@ public: int domain_type_; //!< Type of domain (cell, material, etc.) size_t n_samples_; //!< Number of samples to use double threshold_ {-1.0}; //!< Error threshold for domain volumes - ThresholdType trigger_type_ {ThresholdType::NONE}; + VolumeTriggerMetric trigger_type_ {VolumeTriggerMetric::NONE}; //!< Trigger metric for the volume calculation Position lower_left_; //!< Lower-left position of bounding box Position upper_right_; //!< Upper-right position of bounding box std::vector domain_ids_; //!< IDs of domains to find volumes of diff --git a/src/volume_calc.cpp b/src/volume_calc.cpp index 13cfcf569e..0b5c0204c0 100644 --- a/src/volume_calc.cpp +++ b/src/volume_calc.cpp @@ -80,11 +80,11 @@ VolumeCalculation::VolumeCalculation(pugi::xml_node node) std::string tmp = get_node_value(threshold_node, "type"); if (tmp == "variance") { - trigger_type_ = ThresholdType::VARIANCE; + trigger_type_ = VolumeTriggerMetric::VARIANCE; } else if (tmp == "std_dev") { - trigger_type_ = ThresholdType::STD_DEV; + trigger_type_ = VolumeTriggerMetric::STD_DEV; } else if ( tmp == "rel_err") { - trigger_type_ = ThresholdType::REL_ERR; + trigger_type_ = VolumeTriggerMetric::REL_ERR; } else { std::stringstream msg; msg << "Invalid volume calculation trigger type '" << tmp << "' provided."; @@ -292,16 +292,16 @@ std::vector VolumeCalculation::execute() const result.iterations = iterations; // update threshold value if needed - if (trigger_type_ != ThresholdType::NONE) { + if (trigger_type_ != VolumeTriggerMetric::NONE) { double val = 0.0; switch (trigger_type_) { - case ThresholdType::STD_DEV: + case VolumeTriggerMetric::STD_DEV: val = result.volume[1]; break; - case ThresholdType::REL_ERR: + case VolumeTriggerMetric::REL_ERR: val = result.volume[1] / result.volume[0]; break; - case ThresholdType::VARIANCE: + case VolumeTriggerMetric::VARIANCE: val = result.volume[1] * result.volume[1]; break; } @@ -337,7 +337,7 @@ std::vector VolumeCalculation::execute() const #endif // return results of the calculation - if (trigger_type_ == ThresholdType::NONE || max_vol_err < threshold_) { + if (trigger_type_ == VolumeTriggerMetric::NONE || max_vol_err < threshold_) { return results; } @@ -374,18 +374,18 @@ void VolumeCalculation::to_hdf5(const std::string& filename, write_attribute(file_id, "lower_left", lower_left_); write_attribute(file_id, "upper_right", upper_right_); // Write trigger info - if (trigger_type_ != ThresholdType::NONE) { + if (trigger_type_ != VolumeTriggerMetric::NONE) { write_attribute(file_id, "iterations", results[0].iterations); write_attribute(file_id, "threshold", threshold_); std::string trigger_str; switch(trigger_type_) { - case ThresholdType::VARIANCE: + case VolumeTriggerMetric::VARIANCE: trigger_str = "variance"; break; - case ThresholdType::STD_DEV: + case VolumeTriggerMetric::STD_DEV: trigger_str = "std_dev"; break; - case ThresholdType::REL_ERR: + case VolumeTriggerMetric::REL_ERR: trigger_str = "rel_err"; break; } From f251822b9f16af12f18f08ec7fa94c1fae1be59e Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 8 Oct 2019 13:31:37 -0500 Subject: [PATCH 136/158] Removing trailing whitespace in doc file. --- docs/source/usersguide/volume.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/usersguide/volume.rst b/docs/source/usersguide/volume.rst index d35386b381..b946858ea4 100644 --- a/docs/source/usersguide/volume.rst +++ b/docs/source/usersguide/volume.rst @@ -42,8 +42,8 @@ or relative error of volume estimates using ::attr::`openmc.VolumeCalculation.se vol_calc.set_trigger(1e-05, 'std_dev') -If a threshold is provided, calculations will be performed iteratively using the -number of samples specified on the calculation until all volume uncertainties are below +If a threshold is provided, calculations will be performed iteratively using the +number of samples specified on the calculation until all volume uncertainties are below the threshold value. If no threshold is provided, the calculation will run the number of samples specified once and return the result. From 919c19c9094add032881fe07ad7d12d1091d6424 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 8 Oct 2019 13:53:22 -0500 Subject: [PATCH 137/158] Cleanup from self-review. --- include/openmc/volume_calc.h | 4 +- openmc/volume.py | 13 +++---- src/volume_calc.cpp | 43 ++++++++++++---------- tests/regression_tests/volume_calc/test.py | 6 +-- 4 files changed, 35 insertions(+), 31 deletions(-) diff --git a/include/openmc/volume_calc.h b/include/openmc/volume_calc.h index dade67f5af..bf85a435db 100644 --- a/include/openmc/volume_calc.h +++ b/include/openmc/volume_calc.h @@ -31,8 +31,8 @@ public: std::array volume; //!< Mean/standard deviation of volume std::vector nuclides; //!< Index of nuclides std::vector atoms; //!< Number of atoms for each nuclide - std::vector uncertainty; //!< Uncertainty on number of atoms - int iterations; + std::vector uncertainty; //!< Uncertainty on number of atoms + int iterations; //!< Number of iterations needed to obtain the results }; // Results for a single domain // Constructors diff --git a/openmc/volume.py b/openmc/volume.py index 99eadb57d4..a59fb2acf6 100644 --- a/openmc/volume.py +++ b/openmc/volume.py @@ -32,7 +32,6 @@ class VolumeCalculation(object): Upper-right coordinates of bounding box used to sample points. If this argument is not supplied, an attempt is made to automatically determine a bounding box. - Attributes ---------- @@ -59,10 +58,10 @@ class VolumeCalculation(object): Dictionary mapping unique IDs of domains to estimated volumes in cm^3. threshold : float Threshold for the maxmimum standard deviation of volumes. - iterations : int - Number of iterations over samples (for calculations with a trigger). trigger_type : {'variance', 'std_dev', 'rel_err'} Value type used to halt volume calculation + iterations : int + Number of iterations over samples (for calculations with a trigger). """ def __init__(self, domains, samples, lower_left=None, upper_right=None): @@ -83,7 +82,7 @@ class VolumeCalculation(object): self.ids = [d.id for d in domains] self.samples = samples - + if lower_left is not None: if upper_right is None: raise ValueError('Both lower-left and upper-right coordinates ' @@ -193,7 +192,7 @@ class VolumeCalculation(object): cv.check_length(name, upper_right, 3) self._upper_right = upper_right - @threshold.setter + @threshold.setter def threshold(self, threshold): name = 'volume std. dev. threshold' cv.check_type(name, threshold, Real) @@ -203,7 +202,7 @@ class VolumeCalculation(object): @trigger_type.setter def trigger_type(self, trigger_type): cv.check_value('tally trigger type', trigger_type, - ['variance', 'std_dev', 'rel_err']) + ('variance', 'std_dev', 'rel_err')) self._trigger_type = trigger_type @iterations.setter @@ -293,7 +292,7 @@ class VolumeCalculation(object): # Instantiate the class and assign results vol = cls(domains, samples, lower_left, upper_right) - + if trigger_type is not None: vol.set_trigger(threshold, trigger_type.decode()) diff --git a/src/volume_calc.cpp b/src/volume_calc.cpp index 0b5c0204c0..32b6f03e2e 100644 --- a/src/volume_calc.cpp +++ b/src/volume_calc.cpp @@ -64,13 +64,13 @@ VolumeCalculation::VolumeCalculation(pugi::xml_node node) if (size_t_stream.fail()) { std::stringstream msg; msg << "Could not read number of samples (" - << size_t_stream.str() << ")\n"; + << size_t_stream.str() << ")"; fatal_error(msg); } if (check_for_node(node, "threshold")) { pugi::xml_node threshold_node = node.child("threshold"); - + threshold_ = std::stod(get_node_value(threshold_node, "threshold")); if (threshold_ <= 0.0) { std::stringstream msg; @@ -124,6 +124,7 @@ std::vector VolumeCalculation::execute() const } while (true) { + #pragma omp parallel { // Variables that are private to each thread @@ -223,7 +224,8 @@ std::vector VolumeCalculation::execute() const iterations++; size_t total_samples = iterations * n_samples_; - double max_vol_err = -INFTY; + // reset + double trigger_val = -INFTY; // Set size for members of the Result struct std::vector results(n); @@ -237,7 +239,7 @@ std::vector VolumeCalculation::execute() const auto n_nuc = data::nuclides.size(); xt::xtensor atoms({n_nuc, 2}, 0.0); - #ifdef OPENMC_MPI +#ifdef OPENMC_MPI if (mpi::master) { for (int j = 1; j < mpi::n_procs; j++) { int q; @@ -264,7 +266,7 @@ std::vector VolumeCalculation::execute() const MPI_Send(&q, 1, MPI_INTEGER, 0, 0, mpi::intracomm); MPI_Send(&buffer[0], 2*q, MPI_INTEGER, 0, 1, mpi::intracomm); } - #endif +#endif if (mpi::master) { int total_hits = 0; @@ -299,14 +301,14 @@ std::vector VolumeCalculation::execute() const val = result.volume[1]; break; case VolumeTriggerMetric::REL_ERR: - val = result.volume[1] / result.volume[0]; + val = result.volume[0] == 0.0 ? 0.0 : result.volume[1] / result.volume[0]; break; case VolumeTriggerMetric::VARIANCE: val = result.volume[1] * result.volume[1]; break; } // update max if entry is valid - if (val > 0.0) { max_vol_err = std::max(max_vol_err, val); } + if (val > 0.0) { trigger_val = std::max(trigger_val, val); } } for (int j = 0; j < n_nuc; ++j) { @@ -323,29 +325,30 @@ std::vector VolumeCalculation::execute() const } } } - } + } // end domain loop + + // if no trigger is applied, we're done + if (trigger_type_ == VolumeTriggerMetric::NONE) { return results; } #ifdef OPENMC_MPI // update maximum error value on all processes if (mpi::master) { for (int i = 1; i < mpi::n_procs; i++) { - MPI_Send(&max_vol_err, 1, MPI_DOUBLE, i, 0, mpi::intracomm); + MPI_Send(&trigger_val, 1, MPI_DOUBLE, i, 0, mpi::intracomm); } } else { - MPI_Recv(&max_vol_err, 1, MPI_DOUBLE, 0, 0, mpi::intracomm, MPI_STATUS_IGNORE); - } + MPI_Recv(&trigger_val, 1, MPI_DOUBLE, 0, 0, mpi::intracomm, MPI_STATUS_IGNORE); + } #endif - + // return results of the calculation - if (trigger_type_ == VolumeTriggerMetric::NONE || max_vol_err < threshold_) { - return results; - } + if (trigger_val < threshold_) { return results; } #ifdef OPENMC_MPI - // if iterating in MPI, need to zero indices and hits to they aren't counted twice + // if iterating in an MPI run, need to zero indices and hits so they aren't counted twice if (!mpi::master) { - for (auto& v : master_indices) { std::fill(v.begin(), v.end(), 0.0); } - for (auto& v : master_hits) { std::fill(v.begin(), v.end(), 0.0); } + for (auto& v : master_indices) { std::fill(v.begin(), v.end(), 0); } + for (auto& v : master_hits) { std::fill(v.begin(), v.end(), 0); } } #endif @@ -373,7 +376,7 @@ void VolumeCalculation::to_hdf5(const std::string& filename, write_attribute(file_id, "samples", n_samples_); write_attribute(file_id, "lower_left", lower_left_); write_attribute(file_id, "upper_right", upper_right_); - // Write trigger info + // Write trigger info if (trigger_type_ != VolumeTriggerMetric::NONE) { write_attribute(file_id, "iterations", results[0].iterations); write_attribute(file_id, "threshold", threshold_); @@ -390,6 +393,8 @@ void VolumeCalculation::to_hdf5(const std::string& filename, break; } write_attribute(file_id, "trigger_type", trigger_str); + } else { + write_attribute(file_id, "iterations", 1); } if (domain_type_ == FILTER_CELL) { diff --git a/tests/regression_tests/volume_calc/test.py b/tests/regression_tests/volume_calc/test.py index 8f003aa68a..96acf59b95 100644 --- a/tests/regression_tests/volume_calc/test.py +++ b/tests/regression_tests/volume_calc/test.py @@ -64,7 +64,7 @@ class VolumeTest(PyAPITestHarness): openmc.VolumeCalculation(list(root.cells.values()), 100) ] - + vol_calcs[3].set_trigger(self.exp_std_dev, 'std_dev') vol_calcs[4].set_trigger(self.exp_rel_err, 'rel_err') @@ -102,7 +102,7 @@ class VolumeTest(PyAPITestHarness): assert(volume_calc.threshold == self.exp_variance) assert(volume_calc.iterations == self.variance_iters) for vol in volume_calc.volumes.values(): - assert(vol.std_dev * vol.std_dev <= self.exp_variance) + assert(vol.std_dev * vol.std_dev <= self.exp_variance) else: assert(volume_calc.trigger_type == None) assert(volume_calc.threshold == None) @@ -120,4 +120,4 @@ class VolumeTest(PyAPITestHarness): def test_volume_calc(): harness = VolumeTest('') - harness.main() \ No newline at end of file + harness.main() From 9a00357393258e362737258697becfae682572c1 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 8 Oct 2019 14:45:36 -0500 Subject: [PATCH 138/158] Updating name of material in DAGMC unit test. --- tests/unit_tests/dagmc/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit_tests/dagmc/test.py b/tests/unit_tests/dagmc/test.py index f7b2844f6a..e5d7725551 100644 --- a/tests/unit_tests/dagmc/test.py +++ b/tests/unit_tests/dagmc/test.py @@ -38,7 +38,7 @@ def dagmc_model(request): model.tallies = [tally] # materials - u235 = openmc.Material(name="fuel") + u235 = openmc.Material(name="no-void fuel") u235.add_nuclide('U235', 1.0, 'ao') u235.set_density('g/cc', 11) u235.id = 40 From 711819ef62e884353783e5f1fb476085265943c5 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 9 Oct 2019 09:33:17 -0500 Subject: [PATCH 139/158] Move version number to 0.12.0-dev --- docs/source/conf.py | 4 ++-- include/openmc/constants.h | 4 ++-- openmc/__init__.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index f54a8df0e7..68caff888e 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -79,9 +79,9 @@ copyright = '2011-2019, Massachusetts Institute of Technology and OpenMC contrib # built documents. # # The short X.Y version. -version = "0.11" +version = "0.12" # The full version, including alpha/beta/rc tags. -release = "0.11.0" +release = "0.12.0-dev" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/include/openmc/constants.h b/include/openmc/constants.h index 046a7abefd..d557441179 100644 --- a/include/openmc/constants.h +++ b/include/openmc/constants.h @@ -20,9 +20,9 @@ using double_4dvec = std::vector>>>; // OpenMC major, minor, and release numbers constexpr int VERSION_MAJOR {0}; -constexpr int VERSION_MINOR {11}; +constexpr int VERSION_MINOR {12}; constexpr int VERSION_RELEASE {0}; -constexpr bool VERSION_DEV {false}; +constexpr bool VERSION_DEV {true}; constexpr std::array VERSION {VERSION_MAJOR, VERSION_MINOR, VERSION_RELEASE}; // HDF5 data format diff --git a/openmc/__init__.py b/openmc/__init__.py index f1703dfea0..6af6294227 100644 --- a/openmc/__init__.py +++ b/openmc/__init__.py @@ -34,4 +34,4 @@ from . import examples # Import a few convencience functions that used to be here from openmc.model import rectangular_prism, hexagonal_prism -__version__ = '0.11.0' +__version__ = '0.12.0-dev' From f3be90ee6ca629d86d334cc99ed25830405548be Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 9 Oct 2019 10:04:37 -0500 Subject: [PATCH 140/158] Update year in LICENSE file --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index cfa34033c2..328f3ee469 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2011-2018 Massachusetts Institute of Technology and OpenMC contributors +Copyright (c) 2011-2019 Massachusetts Institute of Technology and OpenMC contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in From 61ec96a934aba7c5e672c6b671356902e4370daa Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 9 Oct 2019 13:11:46 -0500 Subject: [PATCH 141/158] Add CODEOWNERS file --- CODEOWNERS | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 CODEOWNERS diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000000..a061b7b6ea --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,48 @@ +# Data interface +openmc/data/ @paulromano + +# Python bindings to C/C++ API +openmc/lib/ @paulromano + +# Depletion +openmc/deplete/ @drewejohnson +tests/regression_tests/deplete/ @drewejohnson +tests/unit_tests/test_deplete_*.py @drewejohnson + +# MG-related functionality +openmc/mgxs_library.py @nelsonag +src/mgxs.cpp @nelsonag +src/mgxs_interface.cpp @nelsonag +src/physics_mg.cpp @nelsonag +src/scattdata.cpp @nelsonag +src/xsdata.cpp @nelsonag + +# CMFD +openmc/cmfd.py @shikhar413 +src/cmfd_solver.cpp @shikhar413 + +# DAGMC +src/dagmc.cpp @pshriwise +tests/regression_tests/dagmc/ @pshriwise +tests/unit_tests/dagmc/ @pshriwise + +# Photon transport +openmc/data/BREMX.DAT @amandalund +openmc/data/compton_profiles.h5 @amandalund +openmc/data/photon.py @amandalund +src/photon.cpp @amandalund +src/bremsstrahlung.cpp @amandalund +tests/regression_tests/photon_production/ @amandalund +tests/regression_tests/photon_source/ @amandalund + +# RCP and TRISOs +openmc/model/triso.py @amandalund +tests/regression_tests/triso/ @amandalund +tests/unit_tests/test_model_triso.py @amandalund + +# Geometry plotting +src/plot.cpp @pshriwise +openmc/lib/plot.py @pshriwise + +# Resonance covariance +openmc/data/resonance_covariance.py @icmeyer From 4fc6319f03299d8192471f2bfc677909919b570f Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Thu, 10 Oct 2019 10:06:33 -0500 Subject: [PATCH 142/158] Updating legacy test as requested in PR by @paulromano. --- tests/regression_tests/dagmc/legacy/test.py | 66 ++++++++++----------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/tests/regression_tests/dagmc/legacy/test.py b/tests/regression_tests/dagmc/legacy/test.py index 624a7f97b5..5af18e5c8c 100644 --- a/tests/regression_tests/dagmc/legacy/test.py +++ b/tests/regression_tests/dagmc/legacy/test.py @@ -8,48 +8,48 @@ pytestmark = pytest.mark.skipif( not openmc.lib._dagmc_enabled(), reason="DAGMC CAD geometry is not enabled.") -class DAGMCLegacyTest(PyAPITestHarness): +@pytest.fixture +def model(): - def _build_inputs(self): - model = openmc.model.Model() + model = openmc.model.Model() - # settings - model.settings.batches = 5 - model.settings.inactive = 0 - model.settings.particles = 100 + # settings + model.settings.batches = 5 + model.settings.inactive = 0 + model.settings.particles = 100 - source_box = openmc.stats.Box([-4, -4, -4], - [ 4, 4, 4]) - source = openmc.Source(space=source_box) + source_box = openmc.stats.Box([-4, -4, -4], + [ 4, 4, 4]) + source = openmc.Source(space=source_box) - model.settings.source = source + model.settings.source = source - model.settings.dagmc = True + model.settings.dagmc = True - # tally - tally = openmc.Tally() - tally.scores = ['total'] - tally.filters = [openmc.CellFilter(1)] - model.tallies = [tally] + # tally + tally = openmc.Tally() + tally.scores = ['total'] + tally.filters = [openmc.CellFilter(1)] + model.tallies = [tally] - # materials - u235 = openmc.Material(name="no-void fuel") - u235.add_nuclide('U235', 1.0, 'ao') - u235.set_density('g/cc', 11) - u235.id = 40 + # materials + u235 = openmc.Material(name="no-void fuel") + u235.add_nuclide('U235', 1.0, 'ao') + u235.set_density('g/cc', 11) + u235.id = 40 - water = openmc.Material(name="water") - water.add_nuclide('H1', 2.0, 'ao') - water.add_nuclide('O16', 1.0, 'ao') - water.set_density('g/cc', 1.0) - water.add_s_alpha_beta('c_H_in_H2O') - water.id = 41 + water = openmc.Material(name="water") + water.add_nuclide('H1', 2.0, 'ao') + water.add_nuclide('O16', 1.0, 'ao') + water.set_density('g/cc', 1.0) + water.add_s_alpha_beta('c_H_in_H2O') + water.id = 41 - mats = openmc.Materials([u235, water]) - model.materials = mats + mats = openmc.Materials([u235, water]) + model.materials = mats - model.export_to_xml() + return model -def test_dagmc(): - harness = DAGMCLegacyTest('statepoint.5.h5') +def test_dagmc(model): + harness = PyAPITestHarness('statepoint.5.h5', model) harness.main() From a4f98661c1bab079323f58ebc0fa6aa54906b703 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Mon, 14 Oct 2019 18:45:30 -0500 Subject: [PATCH 143/158] Update docs/source/usersguide/volume.rst Co-Authored-By: Paul Romano --- docs/source/usersguide/volume.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/usersguide/volume.rst b/docs/source/usersguide/volume.rst index b946858ea4..c040eb2d7f 100644 --- a/docs/source/usersguide/volume.rst +++ b/docs/source/usersguide/volume.rst @@ -38,7 +38,7 @@ Of course, the volumes that you *need* this capability for are often the ones with complex definitions. A threshold can be applied for the calculation's variance, standard deviation, -or relative error of volume estimates using ::attr::`openmc.VolumeCalculation.set_trigger`:: +or relative error of volume estimates using :meth:`openmc.VolumeCalculation.set_trigger`:: vol_calc.set_trigger(1e-05, 'std_dev') @@ -76,4 +76,4 @@ After the results are loaded, volume estimates will be stored in :attr:`VolumeCalculation.volumes`. There is also a :attr:`VolumeCalculation.atoms_dataframe` attribute that shows stochastic estimates of the number of atoms of each type of nuclide within the specified -domains along with their uncertainties. \ No newline at end of file +domains along with their uncertainties. From 143fe6405757b15f34b1396ee63e3be2dfb668a6 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Mon, 14 Oct 2019 20:26:23 -0500 Subject: [PATCH 144/158] Using trigger enum --- include/openmc/volume_calc.h | 10 ++-------- src/volume_calc.cpp | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/include/openmc/volume_calc.h b/include/openmc/volume_calc.h index bf85a435db..1b49da814d 100644 --- a/include/openmc/volume_calc.h +++ b/include/openmc/volume_calc.h @@ -2,6 +2,7 @@ #define OPENMC_VOLUME_CALC_H #include "openmc/position.h" +#include "openmc/tallies/trigger.h" #include "pugixml.hpp" #include "xtensor/xtensor.hpp" @@ -12,13 +13,6 @@ namespace openmc { -enum class VolumeTriggerMetric { - NONE = 0, - VARIANCE = 1, - STD_DEV = 2, - REL_ERR = 3 -}; - //============================================================================== // Volume calculation class //============================================================================== @@ -56,7 +50,7 @@ public: int domain_type_; //!< Type of domain (cell, material, etc.) size_t n_samples_; //!< Number of samples to use double threshold_ {-1.0}; //!< Error threshold for domain volumes - VolumeTriggerMetric trigger_type_ {VolumeTriggerMetric::NONE}; //!< Trigger metric for the volume calculation + TriggerMetric trigger_type_ {TriggerMetric::not_active}; //!< Trigger metric for the volume calculation Position lower_left_; //!< Lower-left position of bounding box Position upper_right_; //!< Upper-right position of bounding box std::vector domain_ids_; //!< IDs of domains to find volumes of diff --git a/src/volume_calc.cpp b/src/volume_calc.cpp index 32b6f03e2e..b354b1db67 100644 --- a/src/volume_calc.cpp +++ b/src/volume_calc.cpp @@ -80,11 +80,11 @@ VolumeCalculation::VolumeCalculation(pugi::xml_node node) std::string tmp = get_node_value(threshold_node, "type"); if (tmp == "variance") { - trigger_type_ = VolumeTriggerMetric::VARIANCE; + trigger_type_ = TriggerMetric::variance; } else if (tmp == "std_dev") { - trigger_type_ = VolumeTriggerMetric::STD_DEV; + trigger_type_ = TriggerMetric::standard_deviation; } else if ( tmp == "rel_err") { - trigger_type_ = VolumeTriggerMetric::REL_ERR; + trigger_type_ = TriggerMetric::relative_error; } else { std::stringstream msg; msg << "Invalid volume calculation trigger type '" << tmp << "' provided."; @@ -294,16 +294,16 @@ std::vector VolumeCalculation::execute() const result.iterations = iterations; // update threshold value if needed - if (trigger_type_ != VolumeTriggerMetric::NONE) { + if (trigger_type_ != TriggerMetric::not_active) { double val = 0.0; switch (trigger_type_) { - case VolumeTriggerMetric::STD_DEV: + case TriggerMetric::standard_deviation: val = result.volume[1]; break; - case VolumeTriggerMetric::REL_ERR: + case TriggerMetric::relative_error: val = result.volume[0] == 0.0 ? 0.0 : result.volume[1] / result.volume[0]; break; - case VolumeTriggerMetric::VARIANCE: + case TriggerMetric::variance: val = result.volume[1] * result.volume[1]; break; } @@ -328,7 +328,7 @@ std::vector VolumeCalculation::execute() const } // end domain loop // if no trigger is applied, we're done - if (trigger_type_ == VolumeTriggerMetric::NONE) { return results; } + if (trigger_type_ == TriggerMetric::not_active) { return results; } #ifdef OPENMC_MPI // update maximum error value on all processes @@ -377,18 +377,18 @@ void VolumeCalculation::to_hdf5(const std::string& filename, write_attribute(file_id, "lower_left", lower_left_); write_attribute(file_id, "upper_right", upper_right_); // Write trigger info - if (trigger_type_ != VolumeTriggerMetric::NONE) { + if (trigger_type_ != TriggerMetric::not_active) { write_attribute(file_id, "iterations", results[0].iterations); write_attribute(file_id, "threshold", threshold_); std::string trigger_str; switch(trigger_type_) { - case VolumeTriggerMetric::VARIANCE: + case TriggerMetric::variance: trigger_str = "variance"; break; - case VolumeTriggerMetric::STD_DEV: + case TriggerMetric::standard_deviation: trigger_str = "std_dev"; break; - case VolumeTriggerMetric::REL_ERR: + case TriggerMetric::relative_error: trigger_str = "rel_err"; break; } From 5c723bd360f8b8a73ac7c39b9a2887bdb353ad4f Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Mon, 14 Oct 2019 20:27:18 -0500 Subject: [PATCH 145/158] Keeping Python API checks, but writing number of iterations to results file. --- .../volume_calc/results_true.dat | 18 +++++++++++++ tests/regression_tests/volume_calc/test.py | 27 +++++++++---------- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/tests/regression_tests/volume_calc/results_true.dat b/tests/regression_tests/volume_calc/results_true.dat index a31360abca..800356e35b 100644 --- a/tests/regression_tests/volume_calc/results_true.dat +++ b/tests/regression_tests/volume_calc/results_true.dat @@ -1,4 +1,7 @@ Volume calculation 0 +Trigger Type: None +Trigger threshold: None +Iterations: 1 Domain 1: 31.47+/-0.07 cm^3 Domain 2: 2.093+/-0.031 cm^3 Domain 3: 2.049+/-0.031 cm^3 @@ -12,6 +15,9 @@ Domain 3: 2.049+/-0.031 cm^3 6 3 O16 (6.85+/-0.10)e+22 7 3 B10 (6.85+/-0.10)e+18 Volume calculation 1 +Trigger Type: None +Trigger threshold: None +Iterations: 1 Domain 1: 4.14+/-0.04 cm^3 Domain 2: 31.47+/-0.07 cm^3 Material Nuclide Atoms @@ -21,6 +27,9 @@ Domain 2: 31.47+/-0.07 cm^3 3 2 U235 (3.482+/-0.008)e+23 4 2 Mo99 (3.482+/-0.008)e+22 Volume calculation 2 +Trigger Type: None +Trigger threshold: None +Iterations: 1 Domain 0: 35.61+/-0.07 cm^3 Universe Nuclide Atoms 0 0 H1 (2.770+/-0.029)e+23 @@ -29,6 +38,9 @@ Domain 0: 35.61+/-0.07 cm^3 3 0 U235 (3.482+/-0.008)e+23 4 0 Mo99 (3.482+/-0.008)e+22 Volume calculation 3 +Trigger Type: std_dev +Trigger threshold: 0.1 +Iterations: 521 Domain 1: 31.47+/-0.10 cm^3 Domain 2: 2.10+/-0.04 cm^3 Domain 3: 2.11+/-0.04 cm^3 @@ -42,6 +54,9 @@ Domain 3: 2.11+/-0.04 cm^3 6 3 O16 (7.06+/-0.14)e+22 7 3 B10 (7.06+/-0.14)e+18 Volume calculation 4 +Trigger Type: rel_err +Trigger threshold: 0.1 +Iterations: 10 Domain 1: 4.5+/-0.4 cm^3 Domain 2: 30.5+/-0.7 cm^3 Material Nuclide Atoms @@ -51,6 +66,9 @@ Domain 2: 30.5+/-0.7 cm^3 3 2 U235 (3.38+/-0.08)e+23 4 2 Mo99 (3.38+/-0.08)e+22 Volume calculation 5 +Trigger Type: variance +Trigger threshold: 0.05 +Iterations: 105 Domain 1: 31.51+/-0.22 cm^3 Domain 2: 2.13+/-0.10 cm^3 Domain 3: 2.11+/-0.10 cm^3 diff --git a/tests/regression_tests/volume_calc/test.py b/tests/regression_tests/volume_calc/test.py index 96acf59b95..ae4987f1cc 100644 --- a/tests/regression_tests/volume_calc/test.py +++ b/tests/regression_tests/volume_calc/test.py @@ -13,13 +13,8 @@ class VolumeTest(PyAPITestHarness): super().__init__(*args, **kwargs) self.exp_std_dev = 1e-01 - self.std_dev_iters = 521 - self.exp_rel_err = 1e-01 - self.rel_err_iters = 10 - self.exp_variance = 5e-02 - self.variance_iters = 105 def _build_inputs(self): # Define materials @@ -85,29 +80,33 @@ class VolumeTest(PyAPITestHarness): # Read volume calculation results volume_calc = openmc.VolumeCalculation.from_hdf5(filename) + outstr += 'Trigger Type: {}\n'.format(volume_calc.trigger_type) + outstr += 'Trigger threshold: {}\n'.format(volume_calc.threshold) + outstr += 'Iterations: {}\n'.format(volume_calc.iterations) + if i == 3: assert(volume_calc.trigger_type == 'std_dev') assert(volume_calc.threshold == self.exp_std_dev) - assert(volume_calc.iterations == self.std_dev_iters) - for vol in volume_calc.volumes.values(): - assert(vol.std_dev <= self.exp_std_dev) elif i == 4: assert(volume_calc.trigger_type == 'rel_err') assert(volume_calc.threshold == self.exp_rel_err) - assert(volume_calc.iterations == self.rel_err_iters) - for vol in volume_calc.volumes.values(): - assert(vol.std_dev/vol.nominal_value <= self.exp_rel_err) elif i == 5: assert(volume_calc.trigger_type == 'variance') assert(volume_calc.threshold == self.exp_variance) - assert(volume_calc.iterations == self.variance_iters) - for vol in volume_calc.volumes.values(): - assert(vol.std_dev * vol.std_dev <= self.exp_variance) else: assert(volume_calc.trigger_type == None) assert(volume_calc.threshold == None) assert(volume_calc.iterations == 1) + # if a trigger is applied, make sure the calculation satisfies the trigger + for vol in volume_calc.volumes.values(): + if volume_calc.trigger_type == 'std_dev': + assert(vol.std_dev <= self.exp_std_dev) + if volume_calc.trigger_type == 'rel_err': + assert(vol.std_dev/vol.nominal_value <= self.exp_rel_err) + if volume_calc.trigger_type == 'variance': + assert(vol.std_dev * vol.std_dev <= self.exp_variance) + # Write cell volumes and total # of atoms for each nuclide for uid, volume in sorted(volume_calc.volumes.items()): outstr += 'Domain {}: {} cm^3\n'.format(uid, volume) From e91a21add239108c9f950125661b0d6fc626eb8d Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Mon, 14 Oct 2019 20:30:25 -0500 Subject: [PATCH 146/158] Simplyfing MPI send to broadcast. --- src/volume_calc.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/volume_calc.cpp b/src/volume_calc.cpp index b354b1db67..a89c673687 100644 --- a/src/volume_calc.cpp +++ b/src/volume_calc.cpp @@ -333,9 +333,7 @@ std::vector VolumeCalculation::execute() const #ifdef OPENMC_MPI // update maximum error value on all processes if (mpi::master) { - for (int i = 1; i < mpi::n_procs; i++) { - MPI_Send(&trigger_val, 1, MPI_DOUBLE, i, 0, mpi::intracomm); - } + MPI_Bcast(&trigger_val, 1, MPI_DOUBLE, mpi:intracomm); } else { MPI_Recv(&trigger_val, 1, MPI_DOUBLE, 0, 0, mpi::intracomm, MPI_STATUS_IGNORE); } From 6c387b66c56620bf5fa506d7b325e0297368370b Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Mon, 14 Oct 2019 20:39:52 -0500 Subject: [PATCH 147/158] Setting volume variance to INF if the domain is unsampled to avoid early trigger exit. --- src/volume_calc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/volume_calc.cpp b/src/volume_calc.cpp index a89c673687..2c4aa82878 100644 --- a/src/volume_calc.cpp +++ b/src/volume_calc.cpp @@ -301,7 +301,7 @@ std::vector VolumeCalculation::execute() const val = result.volume[1]; break; case TriggerMetric::relative_error: - val = result.volume[0] == 0.0 ? 0.0 : result.volume[1] / result.volume[0]; + val = result.volume[0] == 0.0 ? INFTY : result.volume[1] / result.volume[0]; break; case TriggerMetric::variance: val = result.volume[1] * result.volume[1]; From f47e35da75e6c6a8522e1234a4f57669680777ea Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Mon, 14 Oct 2019 20:53:04 -0500 Subject: [PATCH 148/158] Removing blank line. --- tests/regression_tests/volume_calc/test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/regression_tests/volume_calc/test.py b/tests/regression_tests/volume_calc/test.py index ae4987f1cc..f7b9a27a01 100644 --- a/tests/regression_tests/volume_calc/test.py +++ b/tests/regression_tests/volume_calc/test.py @@ -59,7 +59,6 @@ class VolumeTest(PyAPITestHarness): openmc.VolumeCalculation(list(root.cells.values()), 100) ] - vol_calcs[3].set_trigger(self.exp_std_dev, 'std_dev') vol_calcs[4].set_trigger(self.exp_rel_err, 'rel_err') From d213373a4ba18d4bea3a13b8d576b823d758d9d9 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Mon, 14 Oct 2019 20:55:52 -0500 Subject: [PATCH 149/158] Converting to unsigned long long for number of samples in volume calc. --- src/volume_calc.cpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/volume_calc.cpp b/src/volume_calc.cpp index 2c4aa82878..aa685e3533 100644 --- a/src/volume_calc.cpp +++ b/src/volume_calc.cpp @@ -59,14 +59,7 @@ VolumeCalculation::VolumeCalculation(pugi::xml_node node) domain_ids_ = get_node_array(node, "domain_ids"); lower_left_ = get_node_array(node, "lower_left"); upper_right_ = get_node_array(node, "upper_right"); - std::stringstream size_t_stream(get_node_value(node, "samples")); - size_t_stream >> n_samples_; - if (size_t_stream.fail()) { - std::stringstream msg; - msg << "Could not read number of samples (" - << size_t_stream.str() << ")"; - fatal_error(msg); - } + n_samples_ = std::stoull(get_node_value(node, "samples")); if (check_for_node(node, "threshold")) { pugi::xml_node threshold_node = node.child("threshold"); From dc5fe42af3deda3f43263203e9e7aca9fc158bd1 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 15 Oct 2019 00:40:58 -0500 Subject: [PATCH 150/158] Update src/volume_calc.cpp Co-Authored-By: Paul Romano --- src/volume_calc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/volume_calc.cpp b/src/volume_calc.cpp index aa685e3533..0c91c463e9 100644 --- a/src/volume_calc.cpp +++ b/src/volume_calc.cpp @@ -326,7 +326,7 @@ std::vector VolumeCalculation::execute() const #ifdef OPENMC_MPI // update maximum error value on all processes if (mpi::master) { - MPI_Bcast(&trigger_val, 1, MPI_DOUBLE, mpi:intracomm); + MPI_Bcast(&trigger_val, 1, MPI_DOUBLE, 0, mpi::intracomm); } else { MPI_Recv(&trigger_val, 1, MPI_DOUBLE, 0, 0, mpi::intracomm, MPI_STATUS_IGNORE); } From 67907e53d9279eefb9a138e82e449bcb965550c9 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 15 Oct 2019 00:43:21 -0500 Subject: [PATCH 151/158] Removing if block in MPI ifdef. --- src/volume_calc.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/volume_calc.cpp b/src/volume_calc.cpp index 0c91c463e9..419bf0ea59 100644 --- a/src/volume_calc.cpp +++ b/src/volume_calc.cpp @@ -325,11 +325,7 @@ std::vector VolumeCalculation::execute() const #ifdef OPENMC_MPI // update maximum error value on all processes - if (mpi::master) { - MPI_Bcast(&trigger_val, 1, MPI_DOUBLE, 0, mpi::intracomm); - } else { - MPI_Recv(&trigger_val, 1, MPI_DOUBLE, 0, 0, mpi::intracomm, MPI_STATUS_IGNORE); - } + MPI_Bcast(&trigger_val, 1, MPI_DOUBLE, 0, mpi::intracomm); #endif // return results of the calculation From 4950392fd09c47de7c88a4e6c8098f1ffe62877d Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 16 Oct 2019 10:57:45 +0700 Subject: [PATCH 152/158] Support for setting rotation matrix directly --- include/openmc/cell.h | 8 ++--- openmc/cell.py | 27 ++++++++------ openmc/summary.py | 5 +-- src/cell.cpp | 60 ++++++++++++++++++------------- src/position.cpp | 4 +-- tests/unit_tests/test_geometry.py | 21 +++++++++++ 6 files changed, 82 insertions(+), 43 deletions(-) diff --git a/include/openmc/cell.h b/include/openmc/cell.h index 7206938c40..8a330060c3 100644 --- a/include/openmc/cell.h +++ b/include/openmc/cell.h @@ -179,10 +179,10 @@ public: //! \brief Rotational tranfsormation of the filled universe. // - //! The vector is empty if there is no rotation. Otherwise, the first three - //! values are the rotation angles respectively about the x-, y-, and z-, axes - //! in degrees. The next 9 values give the rotation matrix in row-major - //! order. + //! The vector is empty if there is no rotation. Otherwise, the first 9 values + //! give the rotation matrix in row-major order. When the user specifies + //! rotation angles about the x-, y- and z- axes in degrees, these values are + //! also present at the end of the vector, making it of length 12. std::vector rotation_; std::vector offset_; //!< Distribcell offset table diff --git a/openmc/cell.py b/openmc/cell.py index 3fd70b3455..58e3d48891 100644 --- a/openmc/cell.py +++ b/openmc/cell.py @@ -63,6 +63,10 @@ class Cell(IDManagerMixin): \sin\phi \sin\theta \sin\psi & -\sin\phi \cos\psi + \cos\phi \sin\theta \sin\psi \\ -\sin\theta & \sin\phi \cos\theta & \cos\phi \cos\theta \end{array} \right ] + + A rotation matrix can also be specified directly by setting this + attribute to a nested list (or 2D numpy array) that specifies each + element of the matrix. rotation_matrix : numpy.ndarray The rotation matrix defined by the angles specified in the :attr:`Cell.rotation` property. @@ -227,21 +231,24 @@ class Cell(IDManagerMixin): @rotation.setter def rotation(self, rotation): - cv.check_type('cell rotation', rotation, Iterable, Real) cv.check_length('cell rotation', rotation, 3) self._rotation = np.asarray(rotation) # Save rotation matrix -- the reason we do this instead of having it be # automatically calculated when the rotation_matrix property is accessed # is so that plotting on a rotated geometry can be done faster. - phi, theta, psi = self.rotation*(-pi/180.) - c3, s3 = cos(phi), sin(phi) - c2, s2 = cos(theta), sin(theta) - c1, s1 = cos(psi), sin(psi) - self._rotation_matrix = np.array([ - [c1*c2, c1*s2*s3 - c3*s1, s1*s3 + c1*c3*s2], - [c2*s1, c1*c3 + s1*s2*s3, c3*s1*s2 - c1*s3], - [-s2, c2*s3, c2*c3]]) + if self._rotation.ndim == 2: + # User specified rotation matrix directly + self._rotation_matrix = self._rotation + else: + phi, theta, psi = self.rotation*(-pi/180.) + c3, s3 = cos(phi), sin(phi) + c2, s2 = cos(theta), sin(theta) + c1, s1 = cos(psi), sin(psi) + self._rotation_matrix = np.array([ + [c1*c2, c1*s2*s3 - c3*s1, s1*s3 + c1*c3*s2], + [c2*s1, c1*c3 + s1*s2*s3, c3*s1*s2 - c1*s3], + [-s2, c2*s3, c2*c3]]) @translation.setter def translation(self, translation): @@ -516,7 +523,7 @@ class Cell(IDManagerMixin): element.set("translation", ' '.join(map(str, self.translation))) if self.rotation is not None: - element.set("rotation", ' '.join(map(str, self.rotation))) + element.set("rotation", ' '.join(map(str, self.rotation.ravel()))) return element diff --git a/openmc/summary.py b/openmc/summary.py index 6b8471977e..bcb3ba33b6 100644 --- a/openmc/summary.py +++ b/openmc/summary.py @@ -154,8 +154,9 @@ class Summary(object): if 'rotation' in group: rotation = group['rotation'][()] - rotation = np.asarray(rotation, dtype=np.int) - cell._rotation = rotation + if rotation.size == 9: + rotation.shape = (3, 3) + cell.rotation = rotation elif fill_type == 'material': cell.temperature = group['temperature'][()] diff --git a/src/cell.cpp b/src/cell.cpp index 3a7a2598fc..3a7d0cdd46 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -1,8 +1,10 @@ #include "openmc/cell.h" +#include #include #include +#include #include #include #include @@ -438,35 +440,39 @@ CSGCell::CSGCell(pugi::xml_node cell_node) } auto rot {get_node_array(cell_node, "rotation")}; - if (rot.size() != 3) { + if (rot.size() != 3 && rot.size() != 9) { std::stringstream err_msg; err_msg << "Non-3D rotation vector applied to cell " << id_; fatal_error(err_msg); } - // Store the rotation angles. - rotation_.reserve(12); - rotation_.push_back(rot[0]); - rotation_.push_back(rot[1]); - rotation_.push_back(rot[2]); - // Compute and store the rotation matrix. - auto phi = -rot[0] * PI / 180.0; - auto theta = -rot[1] * PI / 180.0; - auto psi = -rot[2] * PI / 180.0; - rotation_.push_back(std::cos(theta) * std::cos(psi)); - rotation_.push_back(-std::cos(phi) * std::sin(psi) - + std::sin(phi) * std::sin(theta) * std::cos(psi)); - rotation_.push_back(std::sin(phi) * std::sin(psi) - + std::cos(phi) * std::sin(theta) * std::cos(psi)); - rotation_.push_back(std::cos(theta) * std::sin(psi)); - rotation_.push_back(std::cos(phi) * std::cos(psi) - + std::sin(phi) * std::sin(theta) * std::sin(psi)); - rotation_.push_back(-std::sin(phi) * std::cos(psi) - + std::cos(phi) * std::sin(theta) * std::sin(psi)); - rotation_.push_back(-std::sin(theta)); - rotation_.push_back(std::sin(phi) * std::cos(theta)); - rotation_.push_back(std::cos(phi) * std::cos(theta)); + rotation_.reserve(rot.size() == 9 ? 9 : 12); + if (rot.size() == 3) { + double phi = -rot[0] * PI / 180.0; + double theta = -rot[1] * PI / 180.0; + double psi = -rot[2] * PI / 180.0; + rotation_.push_back(std::cos(theta) * std::cos(psi)); + rotation_.push_back(-std::cos(phi) * std::sin(psi) + + std::sin(phi) * std::sin(theta) * std::cos(psi)); + rotation_.push_back(std::sin(phi) * std::sin(psi) + + std::cos(phi) * std::sin(theta) * std::cos(psi)); + rotation_.push_back(std::cos(theta) * std::sin(psi)); + rotation_.push_back(std::cos(phi) * std::cos(psi) + + std::sin(phi) * std::sin(theta) * std::sin(psi)); + rotation_.push_back(-std::sin(phi) * std::cos(psi) + + std::cos(phi) * std::sin(theta) * std::sin(psi)); + rotation_.push_back(-std::sin(theta)); + rotation_.push_back(std::sin(phi) * std::cos(theta)); + rotation_.push_back(std::cos(phi) * std::cos(theta)); + + // When user specifies angles, write them at end of vector + rotation_.push_back(rot[0]); + rotation_.push_back(rot[1]); + rotation_.push_back(rot[2]); + } else { + std::copy(rot.begin(), rot.end(), std::back_inserter(rotation_)); + } } } @@ -578,8 +584,12 @@ CSGCell::to_hdf5(hid_t cell_group) const write_dataset(group, "translation", translation_); } if (!rotation_.empty()) { - std::array rot {rotation_[0], rotation_[1], rotation_[2]}; - write_dataset(group, "rotation", rot); + if (rotation_.size() == 12) { + std::array rot {rotation_[9], rotation_[10], rotation_[11]}; + write_dataset(group, "rotation", rot); + } else { + write_dataset(group, "rotation", rotation_); + } } } else if (type_ == FILL_LATTICE) { diff --git a/src/position.cpp b/src/position.cpp index 1215bfb12e..d29e8d81ca 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -88,9 +88,9 @@ Position Position::rotate(const std::vector& rotation) const { return { + x*rotation[0] + y*rotation[1] + z*rotation[2], x*rotation[3] + y*rotation[4] + z*rotation[5], - x*rotation[6] + y*rotation[7] + z*rotation[8], - x*rotation[9] + y*rotation[10] + z*rotation[11] + x*rotation[6] + y*rotation[7] + z*rotation[8] }; } diff --git a/tests/unit_tests/test_geometry.py b/tests/unit_tests/test_geometry.py index 69211f72e2..70d90b6157 100644 --- a/tests/unit_tests/test_geometry.py +++ b/tests/unit_tests/test_geometry.py @@ -280,3 +280,24 @@ def test_from_xml(run_in_tmpdir, mixed_lattice_model): ll, ur = geom.bounding_box assert ll == pytest.approx((-6.0, -6.0, -np.inf)) assert ur == pytest.approx((6.0, 6.0, np.inf)) + + +def test_rotation_matrix(): + """Test ability to set a rotation matrix directly""" + y = openmc.YPlane() + cyl1 = openmc.ZCylinder(r=1.0) + cyl2 = openmc.ZCylinder(r=2.0, boundary_type='vacuum') + + # Create a universe and then reflect in the y-direction + c1 = openmc.Cell(region=-cyl1 & +y) + c2 = openmc.Cell(region=+cyl1 & +y) + c3 = openmc.Cell(region=-y) + univ = openmc.Universe(cells=[c1, c2, c3]) + c = openmc.Cell(fill=univ, region=-cyl2) + c.rotation = [[1, 0, 0], [0, -1, 0], [0, 0, 1]] + assert np.all(c.rotation_matrix == c.rotation) + geom = openmc.Geometry([c]) + + assert geom.find((0.0, 0.5, 0.0))[-1] == c3 + assert geom.find((0.0, -0.5, 0.0))[-1] == c1 + assert geom.find((0.0, -1.5, 0.0))[-1] == c2 From bb7b9e570bd1e06b2ece8835e4bf83cdcf42ca1a Mon Sep 17 00:00:00 2001 From: Jingang Liang Date: Sun, 20 Oct 2019 14:11:21 +0800 Subject: [PATCH 153/158] create redundant xs upon requested --- openmc/data/neutron.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/openmc/data/neutron.py b/openmc/data/neutron.py index ea14be7844..7420387da1 100644 --- a/openmc/data/neutron.py +++ b/openmc/data/neutron.py @@ -118,7 +118,12 @@ class IncidentNeutron(EqualityMixin): if mt in self.reactions: return self.reactions[mt] else: - raise KeyError('No reaction with MT={}.'.format(mt)) + # Try to create a redundant cross section + mts = self.get_reaction_components(mt) + if len(mts) > 0: + return self._get_redundant_reaction(mt, mts) + else: + raise KeyError('No reaction with MT={}.'.format(mt)) def __repr__(self): return "".format(self.name) @@ -911,16 +916,17 @@ class IncidentNeutron(EqualityMixin): Redundant reaction """ - # Get energy grid - strT = self.temperatures[0] - energy = self.energy[strT] rx = Reaction(mt) - xss = [self.reactions[mt_i].xs[strT] for mt_i in mts] - idx = min([xs._threshold_idx if hasattr(xs, '_threshold_idx') - else 0 for xs in xss]) - rx.xs[strT] = Tabulated1D(energy[idx:], Sum(xss)(energy[idx:])) - rx.xs[strT]._threshold_idx = idx + # Get energy grid + for strT in self.temperatures: + energy = self.energy[strT] + xss = [self.reactions[mt_i].xs[strT] for mt_i in mts] + idx = min([xs._threshold_idx if hasattr(xs, '_threshold_idx') + else 0 for xs in xss]) + rx.xs[strT] = Tabulated1D(energy[idx:], Sum(xss)(energy[idx:])) + rx.xs[strT]._threshold_idx = idx + rx.redundant = True return rx From a3bb85c35896866b9ace72f8b0bc97fea1e10143 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Wed, 23 Oct 2019 08:55:34 -0500 Subject: [PATCH 154/158] Updating Python mesh class string representations. --- openmc/mesh.py | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/openmc/mesh.py b/openmc/mesh.py index 0a8b51c5f6..e6bd967541 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -53,6 +53,12 @@ class MeshBase(IDManagerMixin, metaclass=ABCMeta): else: self._name = '' + def __repr__(self): + string = type(self).__name__ + '\n' + string += '{0: <16}{1}{2}\n'.format('\tID', '=\t', self._id) + string += '{0: <16}{1}{2}\n'.format('\tName', '=\t', self._name) + return string + @classmethod def from_hdf5(cls, group): """Create mesh from HDF5 group @@ -126,7 +132,10 @@ class RegularMesh(MeshBase): @property def n_dimension(self): - return len(self._dimension) + if self._dimension is not None: + return len(self._dimension) + else: + return None @property def lower_left(self): @@ -187,11 +196,9 @@ class RegularMesh(MeshBase): self._width = width def __repr__(self): - string = 'RegularMesh\n' - string += '{0: <16}{1}{2}\n'.format('\tID', '=\t', self._id) - string += '{0: <16}{1}{2}\n'.format('\tName', '=\t', self._name) - string += '{0: <16}{1}{2}\n'.format('\tType', '=\t', self._type) - string += '{0: <16}{1}{2}\n'.format('\tBasis', '=\t', self._dimension) + string = super().__repr__() + string += '{0: <16}{1}{2}\n'.format('\tDimensions', '=\t', self.n_dimension) + string += '{0: <16}{1}{2}\n'.format('\tMesh Cells', '=\t', self._dimension) string += '{0: <16}{1}{2}\n'.format('\tWidth', '=\t', self._lower_left) string += '{0: <16}{1}{2}\n'.format('\tOrigin', '=\t', self._upper_right) string += '{0: <16}{1}{2}\n'.format('\tPixels', '=\t', self._width) @@ -522,6 +529,17 @@ class RectilinearMesh(MeshBase): cv.check_type('mesh z_grid', grid, Iterable, Real) self._z_grid = grid + def __repr__(self): + string = super().__repr__() + string += '{0: <16}{1}{2}\n'.format('\tDimensions', '=\t', self.n_dimension) + x_grid_str = str(self._x_grid) if not self._x_grid else len(self._x_grid) + y_grid_str = str(self._y_grid) if not self._y_grid else len(self._y_grid) + z_grid_str = str(self._z_grid) if not self._z_grid else len(self._z_grid) + string += '{0: <16}{1}{2}\n'.format('\tx pnts:', '=\t', x_grid_str) + string += '{0: <16}{1}{2}\n'.format('\ty pnts:', '=\t', y_grid_str) + string += '{0: <16}{1}{2}\n'.format('\tz pnts:', '=\t', z_grid_str) + return string + @classmethod def from_hdf5(cls, group): mesh_id = int(group.name.split('/')[-1].lstrip('mesh ')) From 392f4461445b16cdb961449494ee1dc4575cef4c Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Wed, 23 Oct 2019 10:33:26 -0500 Subject: [PATCH 155/158] More accurate description of rectilinear mesh attribute. --- openmc/mesh.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/openmc/mesh.py b/openmc/mesh.py index e6bd967541..6ef8776fe8 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -530,14 +530,24 @@ class RectilinearMesh(MeshBase): self._z_grid = grid def __repr__(self): + fmt = '{0: <16}{1}{2}\n' string = super().__repr__() - string += '{0: <16}{1}{2}\n'.format('\tDimensions', '=\t', self.n_dimension) + string += fmt.format('\tDimensions', '=\t', self.n_dimension) x_grid_str = str(self._x_grid) if not self._x_grid else len(self._x_grid) + string += fmt.format('\tN X pnts:', '=\t', x_grid_str) + if self._x_grid: + string += fmt.format('\tX Min:', '=\t', self._x_grid[0]) + string += fmt.format('\tX Max:', '=\t', self._x_grid[-1]) y_grid_str = str(self._y_grid) if not self._y_grid else len(self._y_grid) + string += fmt.format('\tN Y pnts:', '=\t', y_grid_str) + if self._y_grid: + string += fmt.format('\tY Min:', '=\t', self._y_grid[0]) + string += fmt.format('\tY Max:', '=\t', self._y_grid[-1]) z_grid_str = str(self._z_grid) if not self._z_grid else len(self._z_grid) - string += '{0: <16}{1}{2}\n'.format('\tx pnts:', '=\t', x_grid_str) - string += '{0: <16}{1}{2}\n'.format('\ty pnts:', '=\t', y_grid_str) - string += '{0: <16}{1}{2}\n'.format('\tz pnts:', '=\t', z_grid_str) + string += fmt.format('\tN Z pnts:', '=\t', z_grid_str) + if self._z_grid: + string += fmt.format('\tZ Min:', '=\t', self._z_grid[0]) + string += fmt.format('\tZ Max:', '=\t', self._z_grid[-1]) return string @classmethod From 521964149f39d66643d40a546eb254a52f4403c1 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Mon, 28 Oct 2019 11:15:53 -0400 Subject: [PATCH 156/158] Fix tally mesh bug for very short tracks --- src/mesh.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/mesh.cpp b/src/mesh.cpp index ac02b679e8..d6da64bb76 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -523,6 +523,14 @@ void RegularMesh::bins_crossed(const Particle* p, std::vector& bins, } r1 = r; + // The TINY_BIT offsets above mean that the preceding logic cannot always find + // the correct ijk0 and ijk1 indices. For tracks shorter than 2*TINY_BIT, just + // assume the track lies in only one mesh bin. These tracks are very short so + // any error caused by this assumption will be small. + if (total_distance < 2*TINY_BIT) { + for (int i = 0; i < n; ++i) ijk0[i] = ijk1[i]; + } + // ======================================================================== // Find which mesh cells are traversed and the length of each traversal. @@ -898,6 +906,14 @@ void RectilinearMesh::bins_crossed(const Particle* p, std::vector& bins, } r1 = r; + // The TINY_BIT offsets above mean that the preceding logic cannot always find + // the correct ijk0 and ijk1 indices. For tracks shorter than 2*TINY_BIT, just + // assume the track lies in only one mesh bin. These tracks are very short so + // any error caused by this assumption will be small. + if (total_distance < 2*TINY_BIT) { + for (int i = 0; i < 3; ++i) ijk0[i] = ijk1[i]; + } + // ======================================================================== // Find which mesh cells are traversed and the length of each traversal. From 4d4c174a1bee3c36ad5b43fdcef9da884b462d23 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 28 Oct 2019 21:43:26 -0500 Subject: [PATCH 157/158] Allow voxel plots to be colored by material --- src/plot.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/plot.cpp b/src/plot.cpp index 98a3645c76..ba63da16b1 100644 --- a/src/plot.cpp +++ b/src/plot.cpp @@ -908,11 +908,13 @@ void create_voxel(Plot pl) // generate ids using plotbase IdData ids = pltbase.get_map(); - // select only cell ID data and flip the y-axis - xt::xtensor data1 = xt::flip(xt::view(ids.data_, xt::all(), xt::all(), 0), 0); + // select only cell/material ID data and flip the y-axis + int idx = pl.color_by_ == PlotColorBy::cells ? 0 : 1; + xt::xtensor data_slice = xt::view(ids.data_, xt::all(), xt::all(), idx); + xt::xtensor data_flipped = xt::flip(data_slice, 0); // Write to HDF5 dataset - voxel_write_slice(z, dspace, dset, memspace, &(data1(0,0))); + voxel_write_slice(z, dspace, dset, memspace, data_flipped.data()); } voxel_finalize(dspace, dset, memspace); From 90288f2a62d39ca62799f19c042b7c81a8a40912 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 30 Oct 2019 16:01:31 -0500 Subject: [PATCH 158/158] Export targets from CMakeLists.txt --- CMakeLists.txt | 58 +++++++++++++++++++++++++++++++--------- cmake/OpenMCConfig.cmake | 8 ++++++ 2 files changed, 53 insertions(+), 13 deletions(-) create mode 100644 cmake/OpenMCConfig.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 626aadae82..6acfcf2c8b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -114,7 +114,11 @@ endif() #=============================================================================== add_library(pugixml vendor/pugixml/pugixml.cpp) -target_include_directories(pugixml PUBLIC vendor/pugixml/) +target_include_directories(pugixml + PUBLIC + $ + $ +) #=============================================================================== # xtensor header-only library @@ -134,7 +138,11 @@ target_link_libraries(xtensor INTERFACE xtl) #=============================================================================== add_library(gsl INTERFACE) -target_include_directories(gsl INTERFACE vendor/gsl/include) +target_include_directories(gsl + INTERFACE + $ + $ +) # Make sure contract violations throw exceptions target_compile_definitions(gsl INTERFACE GSL_THROW_ON_CONTRACT_VIOLATION) @@ -172,7 +180,11 @@ endif() #=============================================================================== add_library(faddeeva STATIC vendor/faddeeva/Faddeeva.cc) -target_include_directories(faddeeva PUBLIC vendor/faddeeva/) +target_include_directories(faddeeva + PUBLIC + $ + $ +) target_compile_options(faddeeva PRIVATE ${cxxflags}) #=============================================================================== @@ -283,7 +295,11 @@ set_target_properties(libopenmc PROPERTIES OUTPUT_NAME openmc) target_include_directories(libopenmc - PUBLIC include ${HDF5_INCLUDE_DIRS}) + PUBLIC + $ + $ + ${HDF5_INCLUDE_DIRS} +) # Set compile flags target_compile_options(libopenmc PRIVATE ${cxxflags}) @@ -343,12 +359,28 @@ add_custom_command(TARGET libopenmc POST_BUILD # Install executable, scripts, manpage, license #=============================================================================== -install(TARGETS openmc libopenmc - RUNTIME DESTINATION bin - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib - ) -install(DIRECTORY src/relaxng DESTINATION share/openmc) -install(FILES man/man1/openmc.1 DESTINATION share/man/man1) -install(FILES LICENSE DESTINATION "share/doc/openmc" RENAME copyright) -install(DIRECTORY include/ DESTINATION include) +include(GNUInstallDirs) +set(INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/OpenMC) +install(TARGETS openmc libopenmc pugixml faddeeva gsl + EXPORT openmc-targets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +) +install(EXPORT openmc-targets + FILE OpenMCTargets.cmake + NAMESPACE OpenMC:: + DESTINATION ${INSTALL_CONFIGDIR}) + +install(DIRECTORY src/relaxng DESTINATION ${CMAKE_INSTALL_DATADIR}/openmc) +install(FILES cmake/OpenMCConfig.cmake DESTINATION ${INSTALL_CONFIGDIR}) +install(FILES man/man1/openmc.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) +install(FILES LICENSE DESTINATION "${CMAKE_INSTALL_DOCDIR}" RENAME copyright) +install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + +# Copy headers for vendored dependencies (note that xtensor/xtl are handled +# separately since they are managed by CMake) +install(DIRECTORY vendor/pugixml DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + FILES_MATCHING PATTERN "*.hpp") +install(DIRECTORY vendor/gsl DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) +install(DIRECTORY vendor/faddeeva DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/cmake/OpenMCConfig.cmake b/cmake/OpenMCConfig.cmake new file mode 100644 index 0000000000..0bc86fa71c --- /dev/null +++ b/cmake/OpenMCConfig.cmake @@ -0,0 +1,8 @@ +get_filename_component(OpenMC_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY) + +find_package(xtl REQUIRED HINTS ${OpenMC_CMAKE_DIR}/../xtl) +find_package(xtensor REQUIRED HINTS ${OpenMC_CMAKE_DIR}/../xtensor) + +if(NOT TARGET OpenMC::libopenmc) + include("${OpenMC_CMAKE_DIR}/OpenMCTargets.cmake") +endif()