mirror of
https://github.com/openmc-dev/openmc.git
synced 2026-07-22 06:55:35 -04:00
376 lines
13 KiB
Python
376 lines
13 KiB
Python
from collections import Iterable
|
|
from numbers import Real, Integral
|
|
from xml.etree import ElementTree as ET
|
|
import sys
|
|
|
|
import numpy as np
|
|
|
|
import openmc.checkvalue as cv
|
|
import openmc
|
|
from openmc.mixin import EqualityMixin, IDManagerMixin
|
|
|
|
|
|
class Mesh(IDManagerMixin):
|
|
"""A structured Cartesian mesh in one, two, or three dimensions
|
|
|
|
Parameters
|
|
----------
|
|
mesh_id : int
|
|
Unique identifier for the mesh
|
|
name : str
|
|
Name of the mesh
|
|
|
|
Attributes
|
|
----------
|
|
id : int
|
|
Unique identifier for the mesh
|
|
name : str
|
|
Name of the mesh
|
|
type : str
|
|
Type of the mesh
|
|
dimension : Iterable of int
|
|
The number of mesh cells in each direction.
|
|
lower_left : Iterable of float
|
|
The lower-left corner of the structured mesh. If only two coordinate are
|
|
given, it is assumed that the mesh is an x-y mesh.
|
|
upper_right : Iterable of float
|
|
The upper-right corner of the structrued mesh. If only two coordinate
|
|
are given, it is assumed that the mesh is an x-y mesh.
|
|
width : Iterable of float
|
|
The width of mesh cells in each direction.
|
|
indices : list of tuple
|
|
A list of mesh indices for each mesh element, e.g. [(1, 1, 1), (2, 1,
|
|
1), ...]
|
|
|
|
"""
|
|
|
|
next_id = 1
|
|
used_ids = set()
|
|
|
|
def __init__(self, mesh_id=None, name=''):
|
|
# Initialize Mesh class attributes
|
|
self.id = mesh_id
|
|
self.name = name
|
|
self._type = 'regular'
|
|
self._dimension = None
|
|
self._lower_left = None
|
|
self._upper_right = None
|
|
self._width = None
|
|
|
|
@property
|
|
def name(self):
|
|
return self._name
|
|
|
|
@property
|
|
def type(self):
|
|
return self._type
|
|
|
|
@property
|
|
def dimension(self):
|
|
return self._dimension
|
|
|
|
@property
|
|
def lower_left(self):
|
|
return self._lower_left
|
|
|
|
@property
|
|
def upper_right(self):
|
|
return self._upper_right
|
|
|
|
@property
|
|
def width(self):
|
|
return self._width
|
|
|
|
@property
|
|
def num_mesh_cells(self):
|
|
return np.prod(self._dimension)
|
|
|
|
@property
|
|
def indices(self):
|
|
ndim = len(self._dimension)
|
|
if ndim == 3:
|
|
nx, ny, nz = self.dimension
|
|
return ((x, y, z)
|
|
for z in range(1, nz + 1)
|
|
for y in range(1, ny + 1)
|
|
for x in range(1, nx + 1))
|
|
elif ndim == 2:
|
|
nx, ny = self.dimension
|
|
return ((x, y)
|
|
for y in range(1, ny + 1)
|
|
for x in range(1, nx + 1))
|
|
else:
|
|
nx, = self.dimension
|
|
return ((x,) for x in range(1, nx + 1))
|
|
|
|
@name.setter
|
|
def name(self, name):
|
|
if name is not None:
|
|
cv.check_type('name for mesh ID="{0}"'.format(self._id),
|
|
name, str)
|
|
self._name = name
|
|
else:
|
|
self._name = ''
|
|
|
|
@type.setter
|
|
def type(self, meshtype):
|
|
cv.check_type('type for mesh ID="{0}"'.format(self._id),
|
|
meshtype, str)
|
|
cv.check_value('type for mesh ID="{0}"'.format(self._id),
|
|
meshtype, ['regular'])
|
|
self._type = meshtype
|
|
|
|
@dimension.setter
|
|
def dimension(self, dimension):
|
|
cv.check_type('mesh dimension', dimension, Iterable, Integral)
|
|
cv.check_length('mesh dimension', dimension, 1, 3)
|
|
self._dimension = dimension
|
|
|
|
@lower_left.setter
|
|
def lower_left(self, lower_left):
|
|
cv.check_type('mesh lower_left', lower_left, Iterable, Real)
|
|
cv.check_length('mesh lower_left', lower_left, 1, 3)
|
|
self._lower_left = lower_left
|
|
|
|
@upper_right.setter
|
|
def upper_right(self, upper_right):
|
|
cv.check_type('mesh upper_right', upper_right, Iterable, Real)
|
|
cv.check_length('mesh upper_right', upper_right, 1, 3)
|
|
self._upper_right = upper_right
|
|
|
|
@width.setter
|
|
def width(self, width):
|
|
cv.check_type('mesh width', width, Iterable, Real)
|
|
cv.check_length('mesh width', width, 1, 3)
|
|
self._width = width
|
|
|
|
def __repr__(self):
|
|
string = 'Mesh\n'
|
|
string += '{0: <16}{1}{2}\n'.format('\tID', '=\t', self._id)
|
|
string += '{0: <16}{1}{2}\n'.format('\tName', '=\t', self._name)
|
|
string += '{0: <16}{1}{2}\n'.format('\tType', '=\t', self._type)
|
|
string += '{0: <16}{1}{2}\n'.format('\tBasis', '=\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)
|
|
return string
|
|
|
|
@classmethod
|
|
def from_hdf5(cls, group):
|
|
"""Create mesh from HDF5 group
|
|
|
|
Parameters
|
|
----------
|
|
group : h5py.Group
|
|
Group in HDF5 file
|
|
|
|
Returns
|
|
-------
|
|
openmc.Mesh
|
|
Mesh instance
|
|
|
|
"""
|
|
mesh_id = int(group.name.split('/')[-1].lstrip('mesh '))
|
|
|
|
# Read and assign mesh properties
|
|
mesh = cls(mesh_id)
|
|
mesh.type = group['type'].value.decode()
|
|
mesh.dimension = group['dimension'].value
|
|
mesh.lower_left = group['lower_left'].value
|
|
mesh.upper_right = group['upper_right'].value
|
|
mesh.width = group['width'].value
|
|
|
|
return mesh
|
|
|
|
@classmethod
|
|
def from_rect_lattice(cls, lattice, division=1, mesh_id=None, name=''):
|
|
"""Create mesh from an existing rectangular lattice
|
|
|
|
Parameters
|
|
----------
|
|
lattice : openmc.RectLattice
|
|
Rectangular lattice used as a template for this mesh
|
|
division : int
|
|
Number of mesh cells per lattice cell.
|
|
If not specified, there will be 1 mesh cell per lattice cell.
|
|
mesh_id : int
|
|
Unique identifier for the mesh
|
|
name : str
|
|
Name of the mesh
|
|
|
|
Returns
|
|
-------
|
|
openmc.Mesh
|
|
Mesh instance
|
|
|
|
"""
|
|
cv.check_type('rectangular lattice', lattice, openmc.RectLattice)
|
|
|
|
shape = np.array(lattice.shape)
|
|
width = lattice.pitch*shape
|
|
|
|
mesh = cls(mesh_id, name)
|
|
mesh.lower_left = lattice.lower_left
|
|
mesh.upper_right = lattice.lower_left + width
|
|
mesh.dimension = shape*division
|
|
|
|
return mesh
|
|
|
|
def to_xml_element(self):
|
|
"""Return XML representation of the mesh
|
|
|
|
Returns
|
|
-------
|
|
element : xml.etree.ElementTree.Element
|
|
XML element containing mesh data
|
|
|
|
"""
|
|
|
|
element = ET.Element("mesh")
|
|
element.set("id", str(self._id))
|
|
element.set("type", self._type)
|
|
|
|
subelement = ET.SubElement(element, "dimension")
|
|
subelement.text = ' '.join(map(str, self._dimension))
|
|
|
|
subelement = ET.SubElement(element, "lower_left")
|
|
subelement.text = ' '.join(map(str, self._lower_left))
|
|
|
|
if self._upper_right is not None:
|
|
subelement = ET.SubElement(element, "upper_right")
|
|
subelement.text = ' '.join(map(str, self._upper_right))
|
|
|
|
if self._width is not None:
|
|
subelement = ET.SubElement(element, "width")
|
|
subelement.text = ' '.join(map(str, self._width))
|
|
|
|
return element
|
|
|
|
def build_cells(self, bc=['reflective'] * 6):
|
|
"""Generates a lattice of universes with the same dimensionality
|
|
as the mesh object. The individual cells/universes produced
|
|
will not have material definitions applied and so downstream code
|
|
will have to apply that information.
|
|
|
|
Parameters
|
|
----------
|
|
bc : iterable of {'reflective', 'periodic', 'transmission', or 'vacuum'}
|
|
Boundary conditions for each of the four faces of a rectangle
|
|
(if aplying to a 2D mesh) or six faces of a parallelepiped
|
|
(if applying to a 3D mesh) provided in the following order:
|
|
[x min, x max, y min, y max, z min, z max]. 2-D cells do not
|
|
contain the z min and z max entries.
|
|
|
|
Returns
|
|
-------
|
|
root_cell : openmc.Cell
|
|
The cell containing the lattice representing the mesh geometry;
|
|
this cell is a single parallelepiped with boundaries matching
|
|
the outermost mesh boundary with the boundary conditions from bc
|
|
applied.
|
|
cells : iterable of openmc.Cell
|
|
The list of cells within each lattice position mimicking the mesh
|
|
geometry.
|
|
|
|
"""
|
|
|
|
cv.check_length('bc', bc, length_min=4, length_max=6)
|
|
for entry in bc:
|
|
cv.check_value('bc', entry, ['transmission', 'vacuum',
|
|
'reflective', 'periodic'])
|
|
|
|
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])]
|
|
if n_dim == 1:
|
|
yplanes = [openmc.YPlane(y0=-1e10, boundary_type='reflective'),
|
|
openmc.YPlane(y0=1e10, boundary_type='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])]
|
|
|
|
if n_dim <= 2:
|
|
# Would prefer to have the z ranges be the max supported float, but
|
|
# these values are apparently different between python and Fortran.
|
|
# Choosing a safe and sane default.
|
|
# Values of +/-1e10 are used here as there seems to be an
|
|
# 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')]
|
|
else:
|
|
zplanes = [openmc.ZPlane(z0=self.lower_left[2],
|
|
boundary_type=bc[4]),
|
|
openmc.ZPlane(z0=self.upper_right[2],
|
|
boundary_type=bc[5])]
|
|
root_cell = openmc.Cell()
|
|
root_cell.region = ((+xplanes[0] & -xplanes[1]) &
|
|
(+yplanes[0] & -yplanes[1]) &
|
|
(+zplanes[0] & -zplanes[1]))
|
|
|
|
# Build the universes which will be used for each of the (i,j,k)
|
|
# locations within the mesh.
|
|
# We will concurrently build cells to assign to these universes
|
|
cells = []
|
|
universes = []
|
|
for index in self.indices:
|
|
cells.append(openmc.Cell())
|
|
universes.append(openmc.Universe())
|
|
universes[-1].add_cell(cells[-1])
|
|
|
|
lattice = openmc.RectLattice()
|
|
lattice.lower_left = self.lower_left
|
|
|
|
# Assign the universe and rotate to match the indexing expected for
|
|
# the lattice
|
|
if n_dim == 1:
|
|
universe_array = np.array([universes])
|
|
elif n_dim == 2:
|
|
universe_array = np.empty(self.dimension[::-1],
|
|
dtype=openmc.Universe)
|
|
i = 0
|
|
for y in range(self.dimension[1] - 1, -1, -1):
|
|
for x in range(self.dimension[0]):
|
|
universe_array[y][x] = universes[i]
|
|
i += 1
|
|
else:
|
|
universe_array = np.empty(self.dimension[::-1],
|
|
dtype=openmc.Universe)
|
|
i = 0
|
|
for z in range(self.dimension[2]):
|
|
for y in range(self.dimension[1] - 1, -1, -1):
|
|
for x in range(self.dimension[0]):
|
|
universe_array[z][y][x] = universes[i]
|
|
i += 1
|
|
lattice.universes = universe_array
|
|
|
|
if self.width is not None:
|
|
lattice.pitch = self.width
|
|
else:
|
|
dx = ((self.upper_right[0] - self.lower_left[0]) /
|
|
self.dimension[0])
|
|
|
|
if n_dim == 1:
|
|
lattice.pitch = [dx]
|
|
elif n_dim == 2:
|
|
dy = ((self.upper_right[1] - self.lower_left[1]) /
|
|
self.dimension[1])
|
|
lattice.pitch = [dx, dy]
|
|
else:
|
|
dy = ((self.upper_right[1] - self.lower_left[1]) /
|
|
self.dimension[1])
|
|
dz = ((self.upper_right[2] - self.lower_left[2]) /
|
|
self.dimension[2])
|
|
lattice.pitch = [dx, dy, dz]
|
|
|
|
# Fill Cell with the Lattice
|
|
root_cell.fill = lattice
|
|
|
|
return root_cell, cells
|