Leaky and Albedo Boundary Conditions Implementation (#2724)

Co-authored-by: Paul Romano <paul.k.romano@gmail.com>
This commit is contained in:
Yuvraj Jain 2023-10-30 20:51:31 +00:00 committed by GitHub
parent 2c1e304892
commit 693314d097
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 376 additions and 82 deletions

View file

@ -60,9 +60,11 @@ The current version of the summary file format is 6.0.
- **coefficients** (*double[]*) -- Array of coefficients that define
the surface. See :ref:`surface_element` for what coefficients are
defined for each surface type.
- **boundary_condition** (*char[]*) -- Boundary condition applied to
the surface. Can be 'transmission', 'vacuum', 'reflective', or
'periodic'.
- **boundary_type** (*char[]*) -- Boundary condition applied to
the surface. Can be 'transmission', 'vacuum', 'reflective',
'periodic', or 'white'.
- **albedo** (*double*) -- Boundary albedo as a positive multiplier
of particle weight. If absent, it is assumed to be 1.0.
- **geom_type** (*char[]*) -- Type of geometry used to create the cell.
Either 'csg' or 'dagmc'.

View file

@ -172,13 +172,17 @@ surface. To specify a vacuum boundary condition, simply change the
outer_surface = openmc.Sphere(r=100.0)
outer_surface.boundary_type = 'vacuum'
Reflective and periodic boundary conditions can be set with the strings
'reflective' and 'periodic'. Vacuum and reflective boundary conditions can be
applied to any type of surface. Periodic boundary conditions can be applied to
pairs of planar surfaces. If there are only two periodic surfaces they will be
matched automatically. Otherwise it is necessary to specify pairs explicitly
using the :attr:`Surface.periodic_surface` attribute as in the following
example::
Reflective, periodic, and white boundary conditions can be set with the
strings 'reflective', 'periodic', and 'white' respectively.
Vacuum, reflective and white boundary conditions can be applied to any
type of surface. The 'white' boundary condition supports diffuse particle
reflection in contrast to specular reflection provided by the 'reflective'
boundary condition.
Periodic boundary conditions can be applied to pairs of planar surfaces.
If there are only two periodic surfaces they will be matched automatically.
Otherwise it is necessary to specify pairs explicitly using the
:attr:`Surface.periodic_surface` attribute as in the following example::
p1 = openmc.Plane(a=0.3, b=5.0, d=1.0, boundary_type='periodic')
p2 = openmc.Plane(a=0.3, b=5.0, d=-1.0, boundary_type='periodic')
@ -196,6 +200,20 @@ lies in the first quadrant of the Cartesian grid. If the geometry instead lies
in the fourth quadrant, the :class:`YPlane` must be replaced by a
:class:`Plane` with the normal vector pointing in the :math:`-y` direction.
Additionally, 'reflective', 'periodic', and 'white' boundary conditions have
an albedo parameter that can be used to modify the importance of particles
that encounter the boundary. The albedo value specifies the ratio between
the particle's importance after interaction with the boundary to its initial
importance. The following example creates a reflective planar surface which
reduces the reflected particles' importance by 33.3%::
x1 = openmc.XPlane(1.0, boundary_type='reflective', albedo=0.667)
# This is equivalent
x1 = openmc.XPlane(1.0)
x1.boundary_type = 'reflective'
x1.albedo = 0.667
.. _usersguide_cells:
-----

View file

@ -1,7 +1,10 @@
#ifndef OPENMC_BOUNDARY_CONDITION_H
#define OPENMC_BOUNDARY_CONDITION_H
#include "openmc/hdf5_interface.h"
#include "openmc/particle.h"
#include "openmc/position.h"
#include <fmt/core.h>
namespace openmc {
@ -22,8 +25,44 @@ public:
//! \param surf The specific surface on the boundary the particle struck.
virtual void handle_particle(Particle& p, const Surface& surf) const = 0;
//! Modify the incident particle's weight according to the boundary's albedo.
//! \param p The particle that struck the boundary. This function calculates
//! the reduction in the incident particle's weight as it interacts
//! with a boundary. The lost weight is tallied before the remaining weight
//! is reassigned to the incident particle. Implementations of the
//! handle_particle function typically call this method in its body.
//! \param surf The specific surface on the boundary the particle struck.
void handle_albedo(Particle& p, const Surface& surf) const
{
if (!has_albedo())
return;
double initial_wgt = p.wgt();
// Treat the lost weight fraction as leakage, similar to VacuumBC.
// This ensures the lost weight is tallied properly.
p.wgt() *= (1.0 - albedo_);
p.cross_vacuum_bc(surf);
p.wgt() = initial_wgt * albedo_;
};
//! Return a string classification of this BC.
virtual std::string type() const = 0;
//! Write albedo data of this BC to hdf5.
void to_hdf5(hid_t surf_group) const
{
if (has_albedo()) {
write_string(surf_group, "albedo", fmt::format("{}", albedo_), false);
}
};
//! Set albedo of this BC.
void set_albedo(double albedo) { albedo_ = albedo; }
//! Return if this BC has an albedo.
bool has_albedo() const { return (albedo_ > 0.0); }
private:
double albedo_ = -1.0;
};
//==============================================================================

View file

@ -83,10 +83,10 @@ struct BoundingBox {
class Surface {
public:
int id_; //!< Unique ID
std::string name_; //!< User-defined name
std::shared_ptr<BoundaryCondition> bc_ {nullptr}; //!< Boundary condition
GeometryType geom_type_; //!< Geometry type indicator (CSG or DAGMC)
int id_; //!< Unique ID
std::string name_; //!< User-defined name
unique_ptr<BoundaryCondition> bc_; //!< Boundary condition
GeometryType geom_type_; //!< Geometry type indicator (CSG or DAGMC)
bool surf_source_ {false}; //!< Activate source banking for the surface?
explicit Surface(pugi::xml_node surf_node);

View file

@ -109,14 +109,16 @@ def borated_water(boron_ppm, temperature=293., pressure=0.1013, temp_unit='K',
# Define function to create a plane on given axis
def _plane(axis, name, value, boundary_type='transmission'):
def _plane(axis, name, value, boundary_type='transmission', albedo=1.):
cls = getattr(openmc, f'{axis.upper()}Plane')
return cls(value, name=f'{name} {axis}',
boundary_type=boundary_type)
boundary_type=boundary_type,
albedo=albedo)
def rectangular_prism(width, height, axis='z', origin=(0., 0.),
boundary_type='transmission', corner_radius=0.):
boundary_type='transmission', albedo=1.,
corner_radius=0.):
"""Get an infinite rectangular prism from four planar surfaces.
.. versionchanged:: 0.11
@ -138,9 +140,14 @@ def rectangular_prism(width, height, axis='z', origin=(0., 0.),
Origin of the prism. The two floats correspond to (y,z), (x,z) or
(x,y) for prisms parallel to the x, y or z axis, respectively.
Defaults to (0., 0.).
boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'}
boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}
Boundary condition that defines the behavior for particles hitting the
surfaces comprising the rectangular prism (default is 'transmission').
albedo : float, optional
Albedo of the prism's surfaces as a ratio of particle weight after
interaction with the surface to the initial weight. Values must be
positive. Only applicable if the boundary type is 'reflective',
'periodic', or 'white'.
corner_radius: float
Prism corner radius in units of cm. Defaults to 0.
@ -153,6 +160,7 @@ def rectangular_prism(width, height, axis='z', origin=(0., 0.),
check_type('width', width, Real)
check_type('height', height, Real)
check_type('albedo', albedo, Real)
check_type('corner_radius', corner_radius, Real)
check_value('axis', axis, ['x', 'y', 'z'])
check_type('origin', origin, Iterable, Real)
@ -167,15 +175,14 @@ def rectangular_prism(width, height, axis='z', origin=(0., 0.),
# Get cylinder class corresponding to given axis
cyl = getattr(openmc, f'{axis.upper()}Cylinder')
# Create container for boundary arguments
bc_args = {'boundary_type': boundary_type, 'albedo': albedo}
# Create rectangular region
min_x1 = _plane(x1, 'minimum', -width/2 + origin[0],
boundary_type=boundary_type)
max_x1 = _plane(x1, 'maximum', width/2 + origin[0],
boundary_type=boundary_type)
min_x2 = _plane(x2, 'minimum', -height/2 + origin[1],
boundary_type=boundary_type)
max_x2 = _plane(x2, 'maximum', height/2 + origin[1],
boundary_type=boundary_type)
min_x1 = _plane(x1, 'minimum', -width/2 + origin[0], **bc_args)
max_x1 = _plane(x1, 'maximum', width/2 + origin[0], **bc_args)
min_x2 = _plane(x2, 'minimum', -height/2 + origin[1], **bc_args)
max_x2 = _plane(x2, 'maximum', height/2 + origin[1], **bc_args)
if boundary_type == 'periodic':
min_x1.periodic_surface = max_x1
min_x2.periodic_surface = max_x2
@ -187,7 +194,7 @@ def rectangular_prism(width, height, axis='z', origin=(0., 0.),
raise ValueError('Periodic boundary conditions not permitted when '
'rounded corners are used.')
args = {'r': corner_radius, 'boundary_type': boundary_type}
args = {'r': corner_radius, 'boundary_type': boundary_type, 'albedo' : albedo}
args[x1 + '0'] = origin[0] - width/2 + corner_radius
args[x2 + '0'] = origin[1] - height/2 + corner_radius
@ -210,13 +217,13 @@ def rectangular_prism(width, height, axis='z', origin=(0., 0.),
x1_max_x2_max = cyl(name='{} max {} max'.format(x1, x2), **args)
x1_min = _plane(x1, 'min', -width/2 + origin[0] + corner_radius,
boundary_type=boundary_type)
**bc_args)
x1_max = _plane(x1, 'max', width/2 + origin[0] - corner_radius,
boundary_type=boundary_type)
**bc_args)
x2_min = _plane(x2, 'min', -height/2 + origin[1] + corner_radius,
boundary_type=boundary_type)
**bc_args)
x2_max = _plane(x2, 'max', height/2 + origin[1] - corner_radius,
boundary_type=boundary_type)
**bc_args)
corners = (+x1_min_x2_min & -x1_min & -x2_min) | \
(+x1_min_x2_max & -x1_min & +x2_max) | \
@ -236,7 +243,7 @@ def get_rectangular_prism(*args, **kwargs):
def hexagonal_prism(edge_length=1., orientation='y', origin=(0., 0.),
boundary_type='transmission', corner_radius=0.):
boundary_type='transmission', albedo=1., corner_radius=0.):
"""Create a hexagon region from six surface planes.
.. versionchanged:: 0.11
@ -253,9 +260,14 @@ def hexagonal_prism(edge_length=1., orientation='y', origin=(0., 0.),
parallel to the y-axis.
origin: Iterable of two floats
Origin of the prism. Defaults to (0., 0.).
boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'}
boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}
Boundary condition that defines the behavior for particles hitting the
surfaces comprising the hexagonal prism (default is 'transmission').
albedo : float, optional
Albedo of the prism's surfaces as a ratio of particle weight after
interaction with the surface to the initial weight. Values must be
positive. Only applicable if the boundary type is 'reflective',
'periodic', or 'white'.
corner_radius: float
Prism corner radius in units of cm. Defaults to 0.
@ -266,25 +278,35 @@ def hexagonal_prism(edge_length=1., orientation='y', origin=(0., 0.),
"""
check_type('edge_length', edge_length, Real)
check_type('albedo', albedo, Real)
check_type('corner_radius', corner_radius, Real)
check_value('orientation', orientation, ['x', 'y'])
check_type('origin', origin, Iterable, Real)
l = edge_length
x, y = origin
# Create container for boundary arguments
bc_args = {'boundary_type': boundary_type, 'albedo' : albedo}
if orientation == 'y':
right = openmc.XPlane(x + sqrt(3.)/2*l, boundary_type=boundary_type)
left = openmc.XPlane(x - sqrt(3.)/2*l, boundary_type=boundary_type)
right = openmc.XPlane(x + sqrt(3.)/2*l, **bc_args)
left = openmc.XPlane(x - sqrt(3.)/2*l, **bc_args)
c = sqrt(3.)/3.
# y = -x/sqrt(3) + a
upper_right = Plane(a=c, b=1., d=l+x*c+y, boundary_type=boundary_type)
upper_right = Plane(a=c, b=1., d=l+x*c+y, **bc_args)
# y = x/sqrt(3) + a
upper_left = Plane(a=-c, b=1., d=l-x*c+y, boundary_type=boundary_type)
upper_left = Plane(a=-c, b=1., d=l-x*c+y, **bc_args)
# y = x/sqrt(3) - a
lower_right = Plane(a=-c, b=1., d=-l-x*c+y, boundary_type=boundary_type)
lower_right = Plane(a=-c, b=1., d=-l-x*c+y, **bc_args)
# y = -x/sqrt(3) - a
lower_left = Plane(a=c, b=1., d=-l+x*c+y, boundary_type=boundary_type)
lower_left = Plane(a=c, b=1., d=-l+x*c+y, **bc_args)
prism = -right & +left & -upper_right & -upper_left & \
+lower_right & +lower_left
@ -295,22 +317,21 @@ def hexagonal_prism(edge_length=1., orientation='y', origin=(0., 0.),
lower_right.periodic_surface = upper_left
elif orientation == 'x':
top = openmc.YPlane(y0=y + sqrt(3.)/2*l, boundary_type=boundary_type)
bottom = openmc.YPlane(y0=y - sqrt(3.)/2*l, boundary_type=boundary_type)
top = openmc.YPlane(y0=y + sqrt(3.)/2*l, **bc_args)
bottom = openmc.YPlane(y0=y - sqrt(3.)/2*l, **bc_args)
c = sqrt(3.)
# y = -sqrt(3)*(x - a)
upper_right = Plane(a=c, b=1., d=c*l+x*c+y, boundary_type=boundary_type)
upper_right = Plane(a=c, b=1., d=c*l+x*c+y, **bc_args)
# y = sqrt(3)*(x + a)
lower_right = Plane(a=-c, b=1., d=-c*l-x*c+y,
boundary_type=boundary_type)
lower_right = Plane(a=-c, b=1., d=-c*l-x*c+y, **bc_args)
# y = -sqrt(3)*(x + a)
lower_left = Plane(a=c, b=1., d=-c*l+x*c+y, boundary_type=boundary_type)
lower_left = Plane(a=c, b=1., d=-c*l+x*c+y, **bc_args)
# y = sqrt(3)*(x + a)
upper_left = Plane(a=-c, b=1., d=c*l-x*c+y, boundary_type=boundary_type)
upper_left = Plane(a=-c, b=1., d=c*l-x*c+y, **bc_args)
prism = -top & +bottom & -upper_right & +lower_right & \
+lower_left & -upper_left
@ -329,11 +350,9 @@ def hexagonal_prism(edge_length=1., orientation='y', origin=(0., 0.),
c = sqrt(3.)/2
t = l - corner_radius/c
# Cylinder with corner radius and boundary type pre-applied
cyl1 = partial(openmc.ZCylinder, r=corner_radius,
boundary_type=boundary_type)
cyl2 = partial(openmc.ZCylinder, r=corner_radius/(2*c),
boundary_type=boundary_type)
# Cylinder with corner radius and boundary conditions pre-applied
cyl1 = partial(openmc.ZCylinder, r=corner_radius, **bc_args)
cyl2 = partial(openmc.ZCylinder, r=corner_radius/(2*c), **bc_args)
if orientation == 'x':
x_min_y_min_in = cyl1(name='x min y min in', x0=x-t/2, y0=y-c*t)

View file

@ -8,13 +8,14 @@ from warnings import warn, catch_warnings, simplefilter
import numpy as np
from .checkvalue import check_type, check_value, check_length
from .checkvalue import check_type, check_value, check_length, check_greater_than
from .mixin import IDManagerMixin, IDWarning
from .region import Region, Intersection, Union
from .bounding_box import BoundingBox
_BOUNDARY_TYPES = ['transmission', 'vacuum', 'reflective', 'periodic', 'white']
_ALBEDO_BOUNDARIES = ['reflective', 'periodic', 'white']
_WARNING_UPPER = """\
"{}(...) accepts an argument named '{}', not '{}'. Future versions of OpenMC \
@ -123,6 +124,10 @@ class Surface(IDManagerMixin, ABC):
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.
albedo : float, optional
Albedo of the surfaces as a ratio of particle weight after interaction
with the surface to the initial weight. Values must be positive. Only
applicable if the boundary type is 'reflective', 'periodic', or 'white'.
name : str, optional
Name of the surface. If not specified, the name will be the empty
string.
@ -132,6 +137,8 @@ class Surface(IDManagerMixin, ABC):
boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}
Boundary condition that defines the behavior for particles hitting the
surface.
albedo : float
Boundary albedo as a positive multiplier of particle weight
coefficients : dict
Dictionary of surface coefficients
id : int
@ -147,10 +154,12 @@ class Surface(IDManagerMixin, ABC):
used_ids = set()
_atol = 1.e-12
def __init__(self, surface_id=None, boundary_type='transmission', name=''):
def __init__(self, surface_id=None, boundary_type='transmission',
albedo=1., name=''):
self.id = surface_id
self.name = name
self.boundary_type = boundary_type
self.albedo = albedo
# A dictionary of the quadratic surface coefficients
# Key - coefficient name
@ -165,15 +174,20 @@ class Surface(IDManagerMixin, ABC):
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)
string += '{0: <20}{1}{2}\n'.format('\tID', '=\t', self._id)
string += '{0: <20}{1}{2}\n'.format('\tName', '=\t', self._name)
string += '{0: <20}{1}{2}\n'.format('\tType', '=\t', self._type)
string += '{0: <20}{1}{2}\n'.format('\tBoundary', '=\t',
self._boundary_type)
if (self._boundary_type in _ALBEDO_BOUNDARIES and
not math.isclose(self._albedo, 1.0)):
string += '{0: <20}{1}{2}\n'.format('\tBoundary Albedo', '=\t',
self._albedo)
coefficients = '{0: <16}'.format('\tCoefficients') + '\n'
coefficients = '{0: <20}'.format('\tCoefficients') + '\n'
for coeff in self._coefficients:
coefficients += '{0: <16}{1}{2}\n'.format(
coefficients += '{0: <20}{1}{2}\n'.format(
coeff, '=\t', self._coefficients[coeff])
string += coefficients
@ -206,6 +220,16 @@ class Surface(IDManagerMixin, ABC):
check_value('boundary type', boundary_type, _BOUNDARY_TYPES)
self._boundary_type = boundary_type
@property
def albedo(self):
return self._albedo
@albedo.setter
def albedo(self, albedo):
check_type('albedo', albedo, Real)
check_greater_than('albedo', albedo, 0.0)
self._albedo = float(albedo)
@property
def coefficients(self):
return self._coefficients
@ -402,6 +426,9 @@ class Surface(IDManagerMixin, ABC):
element.set("type", self._type)
if self.boundary_type != 'transmission':
element.set("boundary", self.boundary_type)
if (self.boundary_type in _ALBEDO_BOUNDARIES and
not math.isclose(self.albedo, 1.0)):
element.set("albedo", str(self.albedo))
element.set("coeffs", ' '.join([str(self._coefficients.setdefault(key, 0.0))
for key in self._coeff_keys]))
@ -427,10 +454,12 @@ class Surface(IDManagerMixin, ABC):
surf_type = elem.get('type')
cls = _SURFACE_CLASSES[surf_type]
# Determine ID, boundary type, coefficients
# Determine ID, boundary type, boundary albedo, coefficients
kwargs = {}
kwargs['surface_id'] = int(elem.get('id'))
kwargs['boundary_type'] = elem.get('boundary', 'transmission')
if kwargs['boundary_type'] in _ALBEDO_BOUNDARIES:
kwargs['albedo'] = float(elem.get('albedo', 1.0))
kwargs['name'] = elem.get('name')
coeffs = [float(x) for x in elem.get('coeffs').split()]
kwargs.update(dict(zip(cls._coeff_keys, coeffs)))
@ -462,8 +491,13 @@ class Surface(IDManagerMixin, ABC):
name = group['name'][()].decode() if 'name' in group else ''
bc = group['boundary_type'][()].decode()
if 'albedo' in group:
bc_alb = float(group['albedo'][()].decode())
else:
bc_alb = 1.0
coeffs = group['coefficients'][...]
kwargs = {'boundary_type': bc, 'name': name, 'surface_id': surface_id}
kwargs = {'boundary_type': bc, 'albedo': bc_alb, 'name': name,
'surface_id': surface_id}
surf_type = group['type'][()].decode()
cls = _SURFACE_CLASSES[surf_type]
@ -607,7 +641,9 @@ class PlaneMixin:
# Compute new rotated coefficients a, b, c
a, b, c = Rmat @ [a, b, c]
kwargs = {'boundary_type': surf.boundary_type, 'name': surf.name}
kwargs = {'boundary_type': surf.boundary_type,
'albedo': surf.albedo,
'name': surf.name}
if inplace:
kwargs['surface_id'] = surf.id
@ -651,6 +687,10 @@ class Plane(PlaneMixin, Surface):
Boundary condition that defines the behavior for particles hitting the
surface. Defaults to transmissive boundary condition where particles
freely pass through the surface.
albedo : float, optional
Albedo of the surfaces as a ratio of particle weight after interaction
with the surface to the initial weight. Values must be positive. Only
applicable if the boundary type is 'reflective', 'periodic', or 'white'.
name : str, optional
Name of the plane. If not specified, the name will be the empty string.
surface_id : int, optional
@ -670,6 +710,8 @@ class Plane(PlaneMixin, Surface):
boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}
Boundary condition that defines the behavior for particles hitting the
surface.
albedo : float
Boundary albedo as a positive multiplier of particle weight
periodic_surface : openmc.Surface
If a periodic boundary condition is used, the surface with which this
one is periodic with
@ -774,6 +816,10 @@ class XPlane(PlaneMixin, Surface):
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.
albedo : float, optional
Albedo of the surfaces as a ratio of particle weight after interaction
with the surface to the initial weight. Values must be positive. Only
applicable if the boundary type is 'reflective', 'periodic', or 'white'.
name : str, optional
Name of the plane. If not specified, the name will be the empty string.
surface_id : int, optional
@ -787,6 +833,8 @@ class XPlane(PlaneMixin, Surface):
boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}
Boundary condition that defines the behavior for particles hitting the
surface.
albedo : float
Boundary albedo as a positive multiplier of particle weight
periodic_surface : openmc.Surface
If a periodic boundary condition is used, the surface with which this
one is periodic with
@ -832,7 +880,11 @@ class YPlane(PlaneMixin, Surface):
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.
supported, i.e., y-planes can only be paired with y-planes.
albedo : float, optional
Albedo of the surfaces as a ratio of particle weight after interaction
with the surface to the initial weight. Values must be positive. Only
applicable if the boundary type is 'reflective', 'periodic', or 'white'.
name : str, optional
Name of the plane. If not specified, the name will be the empty string.
surface_id : int, optional
@ -846,6 +898,8 @@ class YPlane(PlaneMixin, Surface):
boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}
Boundary condition that defines the behavior for particles hitting the
surface.
albedo : float
Boundary albedo as a positive multiplier of particle weight
periodic_surface : openmc.Surface
If a periodic boundary condition is used, the surface with which this
one is periodic with
@ -891,7 +945,11 @@ class ZPlane(PlaneMixin, Surface):
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.
supported, i.e., z-planes can only be paired with z-planes.
albedo : float, optional
Albedo of the surfaces as a ratio of particle weight after interaction
with the surface to the initial weight. Values must be positive. Only
applicable if the boundary type is 'reflective', 'periodic', or 'white'.
name : str, optional
Name of the plane. If not specified, the name will be the empty string.
surface_id : int, optional
@ -905,6 +963,8 @@ class ZPlane(PlaneMixin, Surface):
boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}
Boundary condition that defines the behavior for particles hitting the
surface.
albedo : float
Boundary albedo as a positive multiplier of particle weight
periodic_surface : openmc.Surface
If a periodic boundary condition is used, the surface with which this
one is periodic with
@ -1076,7 +1136,8 @@ class QuadricMixin:
else:
base_cls = type(tsurf)._virtual_base
# Copy necessary surface attributes to new kwargs dictionary
kwargs = {'boundary_type': tsurf.boundary_type, 'name': tsurf.name}
kwargs = {'boundary_type': tsurf.boundary_type,
'albedo': tsurf.albedo, 'name': tsurf.name}
if inplace:
kwargs['surface_id'] = tsurf.id
kwargs.update({k: getattr(tsurf, k) for k in base_cls._coeff_keys})
@ -1133,6 +1194,10 @@ class Cylinder(QuadricMixin, Surface):
Boundary condition that defines the behavior for particles hitting the
surface. Defaults to transmissive boundary condition where particles
freely pass through the surface.
albedo : float, optional
Albedo of the surfaces as a ratio of particle weight after interaction
with the surface to the initial weight. Values must be positive. Only
applicable if the boundary type is 'reflective', 'periodic', or 'white'.
name : str, optional
Name of the cylinder. If not specified, the name will be the empty
string.
@ -1159,6 +1224,8 @@ class Cylinder(QuadricMixin, Surface):
boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}
Boundary condition that defines the behavior for particles hitting the
surface.
albedo : float
Boundary albedo as a positive multiplier of particle weight
coefficients : dict
Dictionary of surface coefficients
id : int
@ -1280,8 +1347,8 @@ class Cylinder(QuadricMixin, Surface):
# since the C++ layer doesn't support Cylinders right now
with catch_warnings():
simplefilter('ignore', IDWarning)
kwargs = {'boundary_type': self.boundary_type, 'name': self.name,
'surface_id': self.id}
kwargs = {'boundary_type': self.boundary_type, 'albedo': self.albedo,
'name': self.name, 'surface_id': self.id}
quad_rep = Quadric(*self._get_base_coeffs(), **kwargs)
return quad_rep.to_xml_element()
@ -1302,6 +1369,10 @@ class XCylinder(QuadricMixin, Surface):
Boundary condition that defines the behavior for particles hitting the
surface. Defaults to transmissive boundary condition where particles
freely pass through the surface.
albedo : float, optional
Albedo of the surfaces as a ratio of particle weight after interaction
with the surface to the initial weight. Values must be positive. Only
applicable if the boundary type is 'reflective', 'periodic', or 'white'.
name : str, optional
Name of the cylinder. If not specified, the name will be the empty
string.
@ -1320,6 +1391,8 @@ class XCylinder(QuadricMixin, Surface):
boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}
Boundary condition that defines the behavior for particles hitting the
surface.
albedo : float
Boundary albedo as a positive multiplier of particle weight
coefficients : dict
Dictionary of surface coefficients
id : int
@ -1394,6 +1467,10 @@ class YCylinder(QuadricMixin, Surface):
Boundary condition that defines the behavior for particles hitting the
surface. Defaults to transmissive boundary condition where particles
freely pass through the surface.
albedo : float, optional
Albedo of the surfaces as a ratio of particle weight after interaction
with the surface to the initial weight. Values must be positive. Only
applicable if the boundary type is 'reflective', 'periodic', or 'white'.
name : str, optional
Name of the cylinder. If not specified, the name will be the empty
string.
@ -1412,6 +1489,8 @@ class YCylinder(QuadricMixin, Surface):
boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}
Boundary condition that defines the behavior for particles hitting the
surface.
albedo : float
Boundary albedo as a positive multiplier of particle weight
coefficients : dict
Dictionary of surface coefficients
id : int
@ -1486,6 +1565,10 @@ class ZCylinder(QuadricMixin, Surface):
Boundary condition that defines the behavior for particles hitting the
surface. Defaults to transmissive boundary condition where particles
freely pass through the surface.
albedo : float, optional
Albedo of the surfaces as a ratio of particle weight after interaction
with the surface to the initial weight. Values must be positive. Only
applicable if the boundary type is 'reflective', 'periodic', or 'white'.
name : str, optional
Name of the cylinder. If not specified, the name will be the empty
string.
@ -1504,6 +1587,8 @@ class ZCylinder(QuadricMixin, Surface):
boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}
Boundary condition that defines the behavior for particles hitting the
surface.
albedo : float
Boundary albedo as a positive multiplier of particle weight
coefficients : dict
Dictionary of surface coefficients
id : int
@ -1579,6 +1664,10 @@ class Sphere(QuadricMixin, Surface):
Boundary condition that defines the behavior for particles hitting the
surface. Defaults to transmissive boundary condition where particles
freely pass through the surface.
albedo : float, optional
Albedo of the surfaces as a ratio of particle weight after interaction
with the surface to the initial weight. Values must be positive. Only
applicable if the boundary type is 'reflective', 'periodic', or 'white'.
name : str, optional
Name of the sphere. If not specified, the name will be the empty string.
surface_id : int, optional
@ -1598,6 +1687,8 @@ class Sphere(QuadricMixin, Surface):
boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}
Boundary condition that defines the behavior for particles hitting the
surface.
albedo : float
Boundary albedo as a positive multiplier of particle weight
coefficients : dict
Dictionary of surface coefficients
id : int
@ -1683,6 +1774,11 @@ class Cone(QuadricMixin, Surface):
Boundary condition that defines the behavior for particles hitting the
surface. Defaults to transmissive boundary condition where particles
freely pass through the surface.
albedo : float, optional
Albedo of the surfaces as a ratio of particle weight after interaction
with the surface to the initial weight. Values must be positive. Only
applicable if the boundary type is 'reflective', 'periodic', or 'white'.
name : str
Name of the cone. If not specified, the name will be the empty string.
@ -1705,6 +1801,8 @@ class Cone(QuadricMixin, Surface):
boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}
Boundary condition that defines the behavior for particles hitting the
surface.
albedo : float
Boundary albedo as a positive multiplier of particle weight
coefficients : dict
Dictionary of surface coefficients
id : int
@ -1790,7 +1888,9 @@ class Cone(QuadricMixin, Surface):
# since the C++ layer doesn't support Cones right now
with catch_warnings():
simplefilter('ignore', IDWarning)
kwargs = {'boundary_type': self.boundary_type, 'name': self.name,
kwargs = {'boundary_type': self.boundary_type,
'albedo': self.albedo,
'name': self.name,
'surface_id': self.id}
quad_rep = Quadric(*self._get_base_coeffs(), **kwargs)
return quad_rep.to_xml_element()
@ -1814,6 +1914,10 @@ class XCone(QuadricMixin, Surface):
Boundary condition that defines the behavior for particles hitting the
surface. Defaults to transmissive boundary condition where particles
freely pass through the surface.
albedo : float, optional
Albedo of the surfaces as a ratio of particle weight after interaction
with the surface to the initial weight. Values must be positive. Only
applicable if the boundary type is 'reflective', 'periodic', or 'white'.
name : str, optional
Name of the cone. If not specified, the name will be the empty string.
surface_id : int, optional
@ -1833,6 +1937,8 @@ class XCone(QuadricMixin, Surface):
boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}
Boundary condition that defines the behavior for particles hitting the
surface.
albedo : float
Boundary albedo as a positive multiplier of particle weight
coefficients : dict
Dictionary of surface coefficients
id : int
@ -1903,6 +2009,10 @@ class YCone(QuadricMixin, Surface):
Boundary condition that defines the behavior for particles hitting the
surface. Defaults to transmissive boundary condition where particles
freely pass through the surface.
albedo : float, optional
Albedo of the surfaces as a ratio of particle weight after interaction
with the surface to the initial weight. Values must be positive. Only
applicable if the boundary type is 'reflective', 'periodic', or 'white'.
name : str, optional
Name of the cone. If not specified, the name will be the empty string.
surface_id : int, optional
@ -1922,6 +2032,8 @@ class YCone(QuadricMixin, Surface):
boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}
Boundary condition that defines the behavior for particles hitting the
surface.
albedo : float
Boundary albedo as a positive multiplier of particle weight
coefficients : dict
Dictionary of surface coefficients
id : int
@ -1992,6 +2104,10 @@ class ZCone(QuadricMixin, Surface):
Boundary condition that defines the behavior for particles hitting the
surface. Defaults to transmissive boundary condition where particles
freely pass through the surface.
albedo : float, optional
Albedo of the surfaces as a ratio of particle weight after interaction
with the surface to the initial weight. Values must be positive. Only
applicable if the boundary type is 'reflective', 'periodic', or 'white'.
name : str, optional
Name of the cone. If not specified, the name will be the empty string.
surface_id : int, optional
@ -2011,6 +2127,8 @@ class ZCone(QuadricMixin, Surface):
boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}
Boundary condition that defines the behavior for particles hitting the
surface.
albedo : float
Boundary albedo as a positive multiplier of particle weight
coefficients : dict
Dictionary of surface coefficients
id : int
@ -2075,6 +2193,10 @@ class Quadric(QuadricMixin, Surface):
Boundary condition that defines the behavior for particles hitting the
surface. Defaults to transmissive boundary condition where particles
freely pass through the surface.
albedo : float, optional
Albedo of the surfaces as a ratio of particle weight after interaction
with the surface to the initial weight. Values must be positive. Only
applicable if the boundary type is 'reflective', 'periodic', or 'white'.
name : str, optional
Name of the surface. If not specified, the name will be the empty string.
surface_id : int, optional
@ -2088,6 +2210,8 @@ class Quadric(QuadricMixin, Surface):
boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}
Boundary condition that defines the behavior for particles hitting the
surface.
albedo : float
Boundary albedo as a positive multiplier of particle weight
coefficients : dict
Dictionary of surface coefficients
id : int
@ -2183,7 +2307,9 @@ class TorusMixin:
# Create rotated torus
kwargs = {
'boundary_type': surf.boundary_type, 'name': surf.name,
'boundary_type': surf.boundary_type,
'albedo': surf.albedo,
'name': surf.name,
'a': surf.a, 'b': surf.b, 'c': surf.c
}
if inplace:
@ -2236,6 +2362,8 @@ class XTorus(TorusMixin, Surface):
boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}
Boundary condition that defines the behavior for particles hitting the
surface.
albedo : float
Boundary albedo as a positive multiplier of particle weight
coefficients : dict
Dictionary of surface coefficients
id : int
@ -2309,6 +2437,8 @@ class YTorus(TorusMixin, Surface):
boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}
Boundary condition that defines the behavior for particles hitting the
surface.
albedo : float
Boundary albedo as a positive multiplier of particle weight
coefficients : dict
Dictionary of surface coefficients
id : int
@ -2382,6 +2512,8 @@ class ZTorus(TorusMixin, Surface):
boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}
Boundary condition that defines the behavior for particles hitting the
surface.
albedo : float
Boundary albedo as a positive multiplier of particle weight
coefficients : dict
Dictionary of surface coefficients
id : int

View file

@ -28,6 +28,9 @@ void ReflectiveBC::handle_particle(Particle& p, const Surface& surf) const
Direction u = surf.reflect(p.r(), p.u(), &p);
u /= u.norm();
// Handle the effects of the surface albedo on the particle's weight.
BoundaryCondition::handle_albedo(p, surf);
p.cross_reflective_bc(surf, u);
}
@ -40,6 +43,9 @@ void WhiteBC::handle_particle(Particle& p, const Surface& surf) const
Direction u = surf.diffuse_reflect(p.r(), p.u(), p.current_seed());
u /= u.norm();
// Handle the effects of the surface albedo on the particle's weight.
BoundaryCondition::handle_albedo(p, surf);
p.cross_reflective_bc(surf, u);
}
@ -130,6 +136,9 @@ void TranslationalPeriodicBC::handle_particle(
"hitting a surface, but that surface is not recognized by the BC.");
}
// Handle the effects of the surface albedo on the particle's weight.
BoundaryCondition::handle_albedo(p, surf);
// Pass the new location and surface to the particle.
p.cross_periodic_bc(surf, new_r, p.u(), new_surface);
}
@ -264,6 +273,9 @@ void RotationalPeriodicBC::handle_particle(
Direction new_u = {
cos_theta * u.x - sin_theta * u.y, sin_theta * u.x + cos_theta * u.y, u.z};
// Handle the effects of the surface albedo on the particle's weight.
BoundaryCondition::handle_albedo(p, surf);
// Pass the new location, direction, and surface to the particle.
p.cross_periodic_bc(surf, new_r, new_u, new_surface);
}

View file

@ -289,10 +289,10 @@ void DAGUniverse::init_geometry()
bc_value == "transmission") {
// set to transmission by default (nullptr)
} else if (bc_value == "vacuum") {
s->bc_ = std::make_shared<VacuumBC>();
s->bc_ = make_unique<VacuumBC>();
} else if (bc_value == "reflective" || bc_value == "reflect" ||
bc_value == "reflecting") {
s->bc_ = std::make_shared<ReflectiveBC>();
s->bc_ = make_unique<ReflectiveBC>();
} else if (bc_value == "periodic") {
fatal_error("Periodic boundary condition not supported in DAGMC.");
} else {
@ -310,7 +310,7 @@ void DAGUniverse::init_geometry()
// if this surface belongs to the graveyard
if (graveyard && parent_vols.find(graveyard) != parent_vols.end()) {
// set graveyard surface BC's to vacuum
s->bc_ = std::make_shared<VacuumBC>();
s->bc_ = make_unique<VacuumBC>();
}
// add to global array and map

View file

@ -80,19 +80,36 @@ Surface::Surface(pugi::xml_node surf_node)
if (surf_bc == "transmission" || surf_bc == "transmit" || surf_bc.empty()) {
// Leave the bc_ a nullptr
} else if (surf_bc == "vacuum") {
bc_ = std::make_shared<VacuumBC>();
bc_ = make_unique<VacuumBC>();
} else if (surf_bc == "reflective" || surf_bc == "reflect" ||
surf_bc == "reflecting") {
bc_ = std::make_shared<ReflectiveBC>();
bc_ = make_unique<ReflectiveBC>();
} else if (surf_bc == "white") {
bc_ = std::make_shared<WhiteBC>();
bc_ = make_unique<WhiteBC>();
} else if (surf_bc == "periodic") {
// periodic BC's are handled separately
// Periodic BCs are handled separately
} else {
fatal_error(fmt::format("Unknown boundary condition \"{}\" specified "
"on surface {}",
surf_bc, id_));
}
if (check_for_node(surf_node, "albedo") && bc_) {
double surf_alb = std::stod(get_node_value(surf_node, "albedo"));
if (surf_alb < 0.0)
fatal_error(fmt::format("Surface {} has an albedo of {}. "
"Albedo values must be positive.",
id_, surf_alb));
if (surf_alb > 1.0)
warning(fmt::format("Surface {} has an albedo of {}. "
"Albedos greater than 1 may cause "
"unphysical behaviour.",
id_, surf_alb));
bc_->set_albedo(surf_alb);
}
}
}
@ -154,6 +171,7 @@ void Surface::to_hdf5(hid_t group_id) const
if (bc_) {
write_string(surf_group, "boundary_type", bc_->type(), false);
bc_->to_hdf5(surf_group);
} else {
write_string(surf_group, "boundary_type", "transmission", false);
}
@ -1156,9 +1174,10 @@ void read_surfaces(pugi::xml_node node)
}
// Loop over XML surface elements and populate the array. Keep track of
// periodic surfaces.
// periodic surfaces and their albedos.
model::surfaces.reserve(n_surfaces);
std::set<std::pair<int, int>> periodic_pairs;
std::unordered_map<int, double> albedo_map;
{
pugi::xml_node surf_node;
int i_surf;
@ -1221,6 +1240,12 @@ void read_surfaces(pugi::xml_node node)
if (check_for_node(surf_node, "boundary")) {
std::string surf_bc = get_node_value(surf_node, "boundary", true, true);
if (surf_bc == "periodic") {
// Check for surface albedo. Skip sanity check as it is already done
// in the Surface class's constructor.
if (check_for_node(surf_node, "albedo")) {
albedo_map[model::surfaces.back()->id_] =
std::stod(get_node_value(surf_node, "albedo"));
}
if (check_for_node(surf_node, "periodic_surface_id")) {
int i_periodic =
std::stoi(get_node_value(surf_node, "periodic_surface_id"));
@ -1284,7 +1309,7 @@ void read_surfaces(pugi::xml_node node)
periodic_pairs.erase(second_unresolved);
}
// Assign the periodic boundary conditions
// Assign the periodic boundary conditions with albedos
for (auto periodic_pair : periodic_pairs) {
int i_surf = model::surface_map[periodic_pair.first];
int j_surf = model::surface_map[periodic_pair.second];
@ -1302,11 +1327,19 @@ void read_surfaces(pugi::xml_node node)
// planes are parallel which indicates a translational periodic boundary
// condition. Otherwise, it is a rotational periodic BC.
if (std::abs(1.0 - dot_prod) < FP_PRECISION) {
surf1.bc_ = std::make_shared<TranslationalPeriodicBC>(i_surf, j_surf);
surf2.bc_ = surf1.bc_;
surf1.bc_ = make_unique<TranslationalPeriodicBC>(i_surf, j_surf);
surf2.bc_ = make_unique<TranslationalPeriodicBC>(i_surf, j_surf);
} else {
surf1.bc_ = std::make_shared<RotationalPeriodicBC>(i_surf, j_surf);
surf2.bc_ = surf1.bc_;
surf1.bc_ = make_unique<RotationalPeriodicBC>(i_surf, j_surf);
surf2.bc_ = make_unique<RotationalPeriodicBC>(i_surf, j_surf);
}
// If albedo data is present in albedo map, set the boundary albedo.
if (albedo_map.count(surf1.id_)) {
surf1.bc_->set_albedo(albedo_map[surf1.id_]);
}
if (albedo_map.count(surf2.id_)) {
surf2.bc_->set_albedo(albedo_map[surf2.id_]);
}
}
}

View file

View file

@ -0,0 +1,13 @@
<?xml version="1.0"?>
<geometry>
<!-- Reflective box with albedo -->
<surface id="1" type="x-plane" coeffs="-5" boundary="reflective" albedo="0.952"/>
<surface id="2" type="x-plane" coeffs="5" boundary="reflective" albedo="0.952"/>
<surface id="3" type="y-plane" coeffs="-5" boundary="reflective" albedo="0.952"/>
<surface id="4" type="y-plane" coeffs="5" boundary="reflective" albedo="0.952"/>
<surface id="5" type="z-plane" coeffs="-5" boundary="reflective" albedo="0.952"/>
<surface id="6" type="z-plane" coeffs="5" boundary="reflective" albedo="0.952"/>
<cell id="1" material="1" region="1 -2 3 -4 5 -6" />
</geometry>

View file

@ -0,0 +1,9 @@
<?xml version="1.0"?>
<materials>
<material id="1">
<density value="4.5" units="g/cc" />
<nuclide name="U235" ao="1.0" />
</material>
</materials>

View file

@ -0,0 +1,2 @@
k-combined:
1.590800E+00 4.251788E-03

View file

@ -0,0 +1,9 @@
<?xml version="1.0"?>
<settings>
<run_mode>eigenvalue</run_mode>
<batches>10</batches>
<inactive>5</inactive>
<particles>1000</particles>
</settings>

View file

@ -0,0 +1,6 @@
from tests.testing_harness import TestHarness
def test_albedo_box():
harness = TestHarness('statepoint.10.h5')
harness.main()