mirror of
https://github.com/openmc-dev/openmc.git
synced 2026-07-22 06:55:35 -04:00
372 lines
14 KiB
Python
372 lines
14 KiB
Python
from collections import OrderedDict
|
|
from collections.abc import Iterable
|
|
from math import sqrt
|
|
from numbers import Real
|
|
|
|
from openmc import XPlane, YPlane, Plane, ZCylinder
|
|
from openmc.checkvalue import check_type, check_value
|
|
import openmc.data
|
|
|
|
|
|
def borated_water(boron_ppm, temperature=293., pressure=0.1013, temp_unit='K',
|
|
press_unit='MPa', density=None, **kwargs):
|
|
"""Return a Material with the composition of boron dissolved in water.
|
|
|
|
The water density can be determined from a temperature and pressure, or it
|
|
can be set directly.
|
|
|
|
The concentration of boron has no effect on the stoichiometric ratio of H
|
|
and O---they are fixed at 2-1.
|
|
|
|
Parameters
|
|
----------
|
|
boron_ppm : float
|
|
The weight fraction in parts-per-million of elemental boron in the
|
|
water.
|
|
temperature : float
|
|
Temperature in [K] used to compute water density.
|
|
pressure : float
|
|
Pressure in [MPa] used to compute water density.
|
|
temp_unit : {'K', 'C', 'F'}
|
|
The units used for the `temperature` argument.
|
|
press_unit : {'MPa', 'psi'}
|
|
The units used for the `pressure` argument.
|
|
density : float
|
|
Water density in [g / cm^3]. If specified, this value overrides the
|
|
temperature and pressure arguments.
|
|
**kwargs
|
|
All keyword arguments are passed to the created Material object.
|
|
|
|
Returns
|
|
-------
|
|
openmc.Material
|
|
|
|
"""
|
|
# Perform any necessary unit conversions.
|
|
check_value('temperature unit', temp_unit, ('K', 'C', 'F'))
|
|
if temp_unit == 'K':
|
|
T = temperature
|
|
elif temp_unit == 'C':
|
|
T = temperature + 273.15
|
|
elif temp_unit == 'F':
|
|
T = (temperature + 459.67) * 5.0 / 9.0
|
|
check_value('pressure unit', press_unit, ('MPa', 'psi'))
|
|
if press_unit == 'MPa':
|
|
P = pressure
|
|
elif press_unit == 'psi':
|
|
P = pressure * 0.006895
|
|
|
|
# Set the density of water, either from an explicitly given density or from
|
|
# temperature and pressure.
|
|
if density is not None:
|
|
water_density = density
|
|
else:
|
|
water_density = openmc.data.water_density(T, P)
|
|
|
|
# Compute the density of the solution.
|
|
solution_density = water_density / (1 - boron_ppm * 1e-6)
|
|
|
|
# Compute the molar mass of pure water.
|
|
hydrogen = openmc.Element('H')
|
|
oxygen = openmc.Element('O')
|
|
M_H2O = 0.0
|
|
for iso_name, frac, junk in hydrogen.expand(2.0, 'ao'):
|
|
M_H2O += frac * openmc.data.atomic_mass(iso_name)
|
|
for iso_name, frac, junk in oxygen.expand(1.0, 'ao'):
|
|
M_H2O += frac * openmc.data.atomic_mass(iso_name)
|
|
|
|
# Compute the molar mass of boron.
|
|
boron = openmc.Element('B')
|
|
M_B = 0.0
|
|
for iso_name, frac, junk in boron.expand(1.0, 'ao'):
|
|
M_B += frac * openmc.data.atomic_mass(iso_name)
|
|
|
|
# Compute the number fractions of each element.
|
|
frac_H2O = (1 - boron_ppm * 1e-6) / M_H2O
|
|
frac_H = 2 * frac_H2O
|
|
frac_O = frac_H2O
|
|
frac_B = boron_ppm * 1e-6 / M_B
|
|
|
|
# Build the material.
|
|
if density is None:
|
|
out = openmc.Material(temperature=T, **kwargs)
|
|
else:
|
|
out = openmc.Material(**kwargs)
|
|
out.add_element('H', frac_H, 'ao')
|
|
out.add_element('O', frac_O, 'ao')
|
|
out.add_element('B', frac_B, 'ao')
|
|
out.set_density('g/cc', solution_density)
|
|
out.add_s_alpha_beta('c_H_in_H2O')
|
|
return out
|
|
|
|
|
|
def get_rectangular_prism(width, height, axis='z', origin=(0., 0.),
|
|
boundary_type='transmission', corner_radius=0.):
|
|
"""Get an infinite rectangular prism from four planar surfaces.
|
|
|
|
Parameters
|
|
----------
|
|
width: float
|
|
Prism width in units of cm. The width is aligned with the y, x,
|
|
or x axes for prisms parallel to the x, y, or z axis, respectively.
|
|
height: float
|
|
Prism height in units of cm. The height is aligned with the z, z,
|
|
or y axes for prisms parallel to the x, y, or z axis, respectively.
|
|
axis : {'x', 'y', 'z'}
|
|
Axis with which the infinite length of the prism should be aligned.
|
|
Defaults to 'z'.
|
|
origin: Iterable of two floats
|
|
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 condition that defines the behavior for particles hitting the
|
|
surfaces comprising the rectangular prism (default is 'transmission').
|
|
corner_radius: float
|
|
Prism corner radius in units of cm. Defaults to 0.
|
|
|
|
Returns
|
|
-------
|
|
openmc.Region
|
|
The inside of a rectangular prism
|
|
|
|
"""
|
|
|
|
check_type('width', width, Real)
|
|
check_type('height', height, Real)
|
|
check_type('corner_radius', corner_radius, Real)
|
|
check_value('axis', axis, ['x', 'y', 'z'])
|
|
check_type('origin', origin, Iterable, Real)
|
|
|
|
# Define function to create a plane on given axis
|
|
def plane(axis, name, value):
|
|
cls = globals()['{}Plane'.format(axis.upper())]
|
|
return cls(name='{} {}'.format(name, axis),
|
|
boundary_type=boundary_type,
|
|
**{axis + '0': value})
|
|
|
|
if axis == 'x':
|
|
x1, x2 = 'y', 'z'
|
|
elif axis == 'y':
|
|
x1, x2 = 'x', 'z'
|
|
else:
|
|
x1, x2 = 'x', 'y'
|
|
|
|
# Get cylinder class corresponding to given axis
|
|
cyl = globals()['{}Cylinder'.format(axis.upper())]
|
|
|
|
# Create rectangular region
|
|
min_x1 = plane(x1, 'minimum', -width/2 + origin[0])
|
|
max_x1 = plane(x1, 'maximum', width/2 + origin[0])
|
|
min_x2 = plane(x2, 'minimum', -height/2 + origin[1])
|
|
max_x2 = plane(x2, 'maximum', height/2 + origin[1])
|
|
prism = +min_x1 & -max_x1 & +min_x2 & -max_x2
|
|
|
|
# Handle rounded corners if given
|
|
if corner_radius > 0.:
|
|
args = {'R': corner_radius, 'boundary_type': boundary_type}
|
|
|
|
args[x1 + '0'] = origin[0] - width/2 + corner_radius
|
|
args[x2 + '0'] = origin[1] - height/2 + corner_radius
|
|
x1_min_x2_min = cyl(name='{} min {} min'.format(x1, x2), **args)
|
|
|
|
args[x1 + '0'] = origin[0] - width/2 + corner_radius
|
|
args[x2 + '0'] = origin[1] - height/2 + corner_radius
|
|
x1_min_x2_min = cyl(name='{} min {} min'.format(x1, x2), **args)
|
|
|
|
args[x1 + '0'] = origin[0] - width/2 + corner_radius
|
|
args[x2 + '0'] = origin[1] + height/2 - corner_radius
|
|
x1_min_x2_max = cyl(name='{} min {} max'.format(x1, x2), **args)
|
|
|
|
args[x1 + '0'] = origin[0] + width/2 - corner_radius
|
|
args[x2 + '0'] = origin[1] - height/2 + corner_radius
|
|
x1_max_x2_min = cyl(name='{} max {} min'.format(x1, x2), **args)
|
|
|
|
args[x1 + '0'] = origin[0] + width/2 - corner_radius
|
|
args[x2 + '0'] = origin[1] + height/2 - corner_radius
|
|
x1_max_x2_max = cyl(name='{} max {} max'.format(x1, x2), **args)
|
|
|
|
x1_min = plane(x1, 'min', -width/2 + origin[0] + corner_radius)
|
|
x1_max = plane(x1, 'max', width/2 + origin[0] - corner_radius)
|
|
x2_min = plane(x2, 'min', -height/2 + origin[1] + corner_radius)
|
|
x2_max = plane(x2, 'max', height/2 + origin[1] - corner_radius)
|
|
|
|
corners = (+x1_min_x2_min & -x1_min & -x2_min) | \
|
|
(+x1_min_x2_max & -x1_min & +x2_max) | \
|
|
(+x1_max_x2_min & +x1_max & -x2_min) | \
|
|
(+x1_max_x2_max & +x1_max & +x2_max)
|
|
|
|
prism = prism & ~corners
|
|
|
|
return prism
|
|
|
|
|
|
def get_hexagonal_prism(edge_length=1., orientation='y', origin=(0., 0.),
|
|
boundary_type='transmission', corner_radius=0.):
|
|
"""Create a hexagon region from six surface planes.
|
|
|
|
Parameters
|
|
----------
|
|
edge_length : float
|
|
Length of a side of the hexagon in cm
|
|
orientation : {'x', 'y'}
|
|
An 'x' orientation means that two sides of the hexagon are parallel to
|
|
the x-axis and a 'y' orientation means that two sides of the hexagon are
|
|
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 condition that defines the behavior for particles hitting the
|
|
surfaces comprising the hexagonal prism (default is 'transmission').
|
|
corner_radius: float
|
|
Prism corner radius in units of cm. Defaults to 0.
|
|
|
|
Returns
|
|
-------
|
|
openmc.Region
|
|
The inside of a hexagonal prism
|
|
|
|
"""
|
|
|
|
l = edge_length
|
|
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)
|
|
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)
|
|
|
|
# y = x/sqrt(3) + a
|
|
upper_left = Plane(A=-c, B=1., D=l-x*c+y, boundary_type=boundary_type)
|
|
|
|
# y = x/sqrt(3) - a
|
|
lower_right = Plane(A=-c, B=1., D=-l-x*c+y, boundary_type=boundary_type)
|
|
|
|
# y = -x/sqrt(3) - a
|
|
lower_left = Plane(A=c, B=1., D=-l+x*c+y, boundary_type=boundary_type)
|
|
|
|
prism = -right & +left & -upper_right & -upper_left & \
|
|
+lower_right & +lower_left
|
|
|
|
if boundary_type == 'periodic':
|
|
right.periodic_surface = left
|
|
upper_right.periodic_surface = lower_left
|
|
lower_right.periodic_surface = upper_left
|
|
|
|
elif orientation == 'x':
|
|
top = YPlane(y0=y + sqrt(3.)/2*l, boundary_type=boundary_type)
|
|
bottom = YPlane(y0=y - sqrt(3.)/2*l, boundary_type=boundary_type)
|
|
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)
|
|
|
|
# y = sqrt(3)*(x + a)
|
|
lower_right = Plane(A=-c, B=1., D=-c*l-x*c+y,
|
|
boundary_type=boundary_type)
|
|
|
|
# y = -sqrt(3)*(x + a)
|
|
lower_left = Plane(A=c, B=1., D=-c*l+x*c+y, boundary_type=boundary_type)
|
|
|
|
# y = sqrt(3)*(x + a)
|
|
upper_left = Plane(A=-c, B=1., D=c*l-x*c+y, boundary_type=boundary_type)
|
|
|
|
prism = -top & +bottom & -upper_right & +lower_right & \
|
|
+lower_left & -upper_left
|
|
|
|
if boundary_type == 'periodic':
|
|
top.periodic_surface = bottom
|
|
upper_right.periodic_surface = lower_left
|
|
lower_right.periodic_surface = upper_left
|
|
|
|
# Handle rounded corners if given
|
|
if corner_radius > 0.:
|
|
if boundary_type == 'periodic':
|
|
raise ValueError('Periodic boundary conditions not permitted when '
|
|
'rounded corners are used.')
|
|
|
|
c = sqrt(3.)/2
|
|
t = l - corner_radius/c
|
|
|
|
# Cylinder with corner radius and boundary type pre-applied
|
|
cyl1 = partial(ZCylinder, R=corner_radius, boundary_type=boundary_type)
|
|
cyl2 = partial(ZCylinder, R=corner_radius/(2*c),
|
|
boundary_type=boundary_type)
|
|
|
|
if orientation == 'x':
|
|
x_min_y_min_in = cyl1(name='x min y min in', x0=x-t/2, y0=y-c*t)
|
|
x_min_y_max_in = cyl1(name='x min y max in', x0=x+t/2, y0=y-c*t)
|
|
x_max_y_min_in = cyl1(name='x max y min in', x0=x-t/2, y0=y+c*t)
|
|
x_max_y_max_in = cyl1(name='x max y max in', x0=x+t/2, y0=y+c*t)
|
|
x_min_in = cyl1(name='x min in', x0=x-t, y0=y)
|
|
x_max_in = cyl1(name='x max in', x0=x+t, y0=y)
|
|
|
|
x_min_y_min_out = cyl2(name='x min y min out', x0=x-l/2, y0=y-c*l)
|
|
x_min_y_max_out = cyl2(name='x min y max out', x0=x+l/2, y0=y-c*l)
|
|
x_max_y_min_out = cyl2(name='x max y min out', x0=x-l/2, y0=y+c*l)
|
|
x_max_y_max_out = cyl2(name='x max y max out', x0=x+l/2, y0=y+c*l)
|
|
x_min_out = cyl2(name='x min out', x0=x-l, y0=y)
|
|
x_max_out = cyl2(name='x max out', x0=x+l, y0=y)
|
|
|
|
corners = (+x_min_y_min_in & -x_min_y_min_out |
|
|
+x_min_y_max_in & -x_min_y_max_out |
|
|
+x_max_y_min_in & -x_max_y_min_out |
|
|
+x_max_y_max_in & -x_max_y_max_out |
|
|
+x_min_in & -x_min_out |
|
|
+x_max_in & -x_max_out)
|
|
|
|
elif orientation == 'y':
|
|
x_min_y_min_in = cyl1(name='x min y min in', x0=x-c*t, y0=y-t/2)
|
|
x_min_y_max_in = cyl1(name='x min y max in', x0=x-c*t, y0=y+t/2)
|
|
x_max_y_min_in = cyl1(name='x max y min in', x0=x+c*t, y0=y-t/2)
|
|
x_max_y_max_in = cyl1(name='x max y max in', x0=x+c*t, y0=y+t/2)
|
|
y_min_in = cyl1(name='y min in', x0=x, y0=y-t)
|
|
y_max_in = cyl1(name='y max in', x0=x, y0=y+t)
|
|
|
|
x_min_y_min_out = cyl2(name='x min y min out', x0=x-c*l, y0=y-l/2)
|
|
x_min_y_max_out = cyl2(name='x min y max out', x0=x-c*l, y0=y+l/2)
|
|
x_max_y_min_out = cyl2(name='x max y min out', x0=x+c*l, y0=y-l/2)
|
|
x_max_y_max_out = cyl2(name='x max y max out', x0=x+c*l, y0=y+l/2)
|
|
y_min_out = cyl2(name='y min out', x0=x, y0=y-l)
|
|
y_max_out = cyl2(name='y max out', x0=x, y0=y+l)
|
|
|
|
corners = (+x_min_y_min_in & -x_min_y_min_out |
|
|
+x_min_y_max_in & -x_min_y_max_out |
|
|
+x_max_y_min_in & -x_max_y_min_out |
|
|
+x_max_y_max_in & -x_max_y_max_out |
|
|
+y_min_in & -y_min_out |
|
|
+y_max_in & -y_max_out)
|
|
|
|
prism = prism & ~corners
|
|
|
|
return prism
|
|
|
|
|
|
def subdivide(surfaces):
|
|
"""Create regions separated by a series of surfaces.
|
|
|
|
This function allows regions to be constructed from a set of a surfaces that
|
|
are "in order". For example, if you had four instances of
|
|
:class:`openmc.ZPlane` at z=-10, z=-5, z=5, and z=10, this function would
|
|
return a list of regions corresponding to z < -10, -10 < z < -5, -5 < z < 5,
|
|
5 < z < 10, and 10 < z. That is, for n surfaces, n+1 regions are returned.
|
|
|
|
Parameters
|
|
----------
|
|
surfaces : sequence of openmc.Surface
|
|
Surfaces separating regions
|
|
|
|
Returns
|
|
-------
|
|
list of openmc.Region
|
|
Regions formed by the given surfaces
|
|
|
|
"""
|
|
regions = [-surfaces[0]]
|
|
for s0, s1 in zip(surfaces[:-1], surfaces[1:]):
|
|
regions.append(+s0 & -s1)
|
|
regions.append(+surfaces[-1])
|
|
return regions
|