This commit is contained in:
GuySten 2026-07-17 08:35:16 +03:00 committed by GitHub
commit 696b5d4bcf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 1181 additions and 188 deletions

View file

@ -362,7 +362,7 @@ attributes/sub-elements:
:type:
The type of mesh. This can be either "regular", "rectilinear",
"cylindrical", "spherical", or "unstructured".
"cylindrical", "spherical", "hexagonal", or "unstructured".
:dimension:
The number of mesh cells in each direction. (For regular mesh only.)
@ -379,6 +379,9 @@ attributes/sub-elements:
The upper-right corner of the structured mesh. If only two coordinates are
given, it is assumed that the mesh is an x-y mesh. (For regular mesh only.)
:pitch:
The mesh radial pitch. (For hexagonal mesh only.)
:width:
The width of mesh cells in each direction. (For regular mesh only.)
@ -389,7 +392,7 @@ attributes/sub-elements:
The mesh divisions along the y-axis. (For rectilinear mesh only.)
:z_grid:
The mesh divisions along the z-axis. (For rectilinear and cylindrical meshes only.)
The mesh divisions along the z-axis. (For rectilinear, cylindrical and hexagonal meshes only.)
:r_grid:
The mesh divisions along the r-axis. (For cylindrical and spherical meshes only.)
@ -400,8 +403,14 @@ attributes/sub-elements:
:theta_grid:
The mesh divisions along the theta-axis. (For spherical mesh only.)
:num_rings:
The number of hexagonal rings. (For hexagonal mesh only.)
:orientation:
The orientation of the hexagonal mesh, either "x" or "y". (For hexagonal mesh only.)
:origin:
The origin in cartesian coordinates. (For cylindrical and spherical meshes only.)
The origin in cartesian coordinates. (For cylindrical, spherical and hexagonal meshes only.)
:library:
The mesh library used to represent an unstructured mesh. This can be either

View file

@ -170,6 +170,7 @@ Meshes
openmc.CylindricalMesh
openmc.SphericalMesh
openmc.UnstructuredMesh
openmc.HexagonalMesh
Geometry Plotting
-----------------

View file

@ -302,10 +302,10 @@ public:
struct MeshDistance {
MeshDistance() = default;
MeshDistance(int _index, bool _max_surface, double _distance)
: next_index {_index}, max_surface {_max_surface}, distance {_distance}
MeshDistance(MeshIndex _offset, bool _max_surface, double _distance)
: offset {_offset}, max_surface {_max_surface}, distance {_distance}
{}
int next_index {-1};
MeshIndex offset {0, 0, 0};
bool max_surface {true};
double distance {INFTY};
bool operator<(const MeshDistance& o) const
@ -314,6 +314,8 @@ public:
}
};
virtual void sanitize_index(MeshIndex& idx) const {};
Position sample_element(int32_t bin, uint64_t* seed) const override
{
return sample_element(get_indices_from_bin(bin), seed);
@ -364,6 +366,13 @@ public:
//! \return Array of mesh indices
virtual MeshIndex get_indices(Position r, bool& in_mesh) const;
//! Check if mesh indices are inside mesh
//
//! \param[in] Array of mesh indices
//! \param[in] k Suspect axis
//! \return are indices inside mesh?
virtual bool valid_index(const MeshIndex& ijk, int k) const;
//! Get mesh indices corresponding to a mesh bin
//
//! \param[in] bin Mesh bin
@ -416,9 +425,16 @@ public:
virtual MeshDistance distance_to_grid_boundary(const MeshIndex& ijk, int i,
const Position& r0, const Direction& u, double l) const = 0;
virtual double distance_to_mesh(const MeshIndex& ijk,
const std::array<MeshDistance, 4>& distances, double traveled_distance,
int& k_max) const;
//! Get a label for the mesh bin
std::string bin_label(int bin) const override;
//! Get a label for the surface
virtual std::string surface_label(int surface) const;
//! Get mesh dimensions as a tensor
tensor::Tensor<int> get_shape_tensor() const;
@ -452,6 +468,8 @@ public:
// Data members
std::array<int, 3> shape_; //!< Number of mesh elements in each dimension
std::vector<std::vector<int>> correlated_axes_ = {{0}, {1}, {2}};
std::vector<std::string> axes_labels_ = {"x", "y", "z"};
protected:
};
@ -580,6 +598,11 @@ public:
CylindricalMesh(hid_t group);
// Overridden methods
void sanitize_index(MeshIndex& idx) const override
{
idx[1] = sanitize_phi(idx[1]);
}
virtual MeshIndex get_indices(Position r, bool& in_mesh) const override;
int get_index_in_direction(double r, int i) const override;
@ -645,6 +668,12 @@ public:
SphericalMesh(hid_t group);
// Overridden methods
void sanitize_index(MeshIndex& idx) const override
{
idx[1] = sanitize_theta(idx[1]);
idx[2] = sanitize_phi(idx[2]);
}
virtual MeshIndex get_indices(Position r, bool& in_mesh) const override;
int get_index_in_direction(double r, int i) const override;
@ -706,6 +735,84 @@ private:
}
};
class HexagonalMesh : public PeriodicStructuredMesh {
public:
// Constructors
HexagonalMesh() = default;
HexagonalMesh(pugi::xml_node node);
HexagonalMesh(hid_t group);
// Overridden methods
//! Get a label for the mesh bin
std::string bin_label(int bin) const override;
std::string surface_label(int surface) const override;
int n_bins() const override;
MeshIndex get_indices(Position r, bool& in_mesh) const override;
//! Get mesh indices corresponding to a mesh bin
//
//! \param[in] bin Mesh bin
//! \return ijk Mesh indices
MeshIndex get_indices_from_bin(int bin) const override;
bool valid_index(const MeshIndex& ijk, int k) const override;
int get_bin_from_indices(const MeshIndex& ijk) const override;
std::string get_mesh_type() const override;
static const std::string mesh_type;
Position sample_element(const MeshIndex& ijk, uint64_t* seed) const override;
MeshDistance distance_to_grid_boundary(const MeshIndex& ijk, int i,
const Position& r0, const Direction& u, double l) const override;
double distance_to_mesh(const MeshIndex& ijk,
const std::array<MeshDistance, 4>& distances, double traveled_distance,
int& k_max) const override;
std::pair<vector<double>, vector<double>> plot(
Position plot_ll, Position plot_ur) const override;
void to_hdf5_inner(hid_t group) const override;
int set_grid();
enum class Orientation {
y, //!< Flat side of lattice parallel to y-axis
x //!< Flat side of lattice parallel to x-axis
};
// Data members
int num_rings_;
double pitch_;
vector<double> grid_;
Direction q_;
Direction r_;
Direction q_dual_;
Direction r_dual_;
Orientation orientation_ {Orientation::y};
private:
StructuredMesh::MeshDistance find_z_crossing(
const Position& r, const Direction& u, double l, int shell) const;
double volume(const MeshIndex& ijk) const override;
int get_index_in_direction(double r, int i) const override
{
fatal_error("This function is not implemented");
return -1;
}
int get_index_in_z_direction(double z) const;
};
// Abstract class for unstructured meshes
class UnstructuredMesh : public Mesh {

View file

@ -22,21 +22,6 @@ public:
// Accessors
void set_mesh(int32_t mesh) override;
enum class MeshDir {
OUT_LEFT, // x min
IN_LEFT, // x min
OUT_RIGHT, // x max
IN_RIGHT, // x max
OUT_BACK, // y min
IN_BACK, // y min
OUT_FRONT, // y max
IN_FRONT, // y max
OUT_BOTTOM, // z min
IN_BOTTOM, // z min
OUT_TOP, // z max
IN_TOP // z max
};
};
} // namespace openmc

View file

@ -30,13 +30,6 @@ _FILTER_TYPES = (
'weight', 'meshborn', 'meshsurface', 'meshmaterial', 'reaction',
)
_CURRENT_NAMES = (
'x-min out', 'x-min in', 'x-max out', 'x-max in',
'y-min out', 'y-min in', 'y-max out', 'y-max in',
'z-min out', 'z-min in', 'z-max out', 'z-max in'
)
class FilterMeta(ABCMeta):
"""Metaclass for filters that ensures class names are appropriate."""
@ -79,7 +72,7 @@ class FilterMeta(ABCMeta):
def _repeat_and_tile(bins, repeat_factor, data_size):
filter_bins = np.repeat(bins, repeat_factor)
filter_bins = np.repeat(bins, repeat_factor, axis=0)
tile_factor = data_size // len(filter_bins)
return np.tile(filter_bins, tile_factor)
@ -992,16 +985,9 @@ class MeshFilter(Filter):
# Append mesh ID as outermost index of multi-index
mesh_key = f'mesh {self.mesh.id}'
# Determine index base (0-based for unstructured, 1-based otherwise)
idx_start = 0 if isinstance(self.mesh, openmc.UnstructuredMesh) else 1
# Generate a multi-index sub-column for each axis
for label, dim_size in zip(self.mesh._axis_labels, self.mesh.dimension):
filter_dict[mesh_key, label] = _repeat_and_tile(
np.arange(idx_start, idx_start + dim_size), stride, data_size)
stride *= dim_size
return pd.DataFrame(filter_dict)
columns = [(mesh_key, label) for label in self.mesh._axis_labels]
indices = _repeat_and_tile(list(self.mesh.indices), stride, data_size)
return pd.DataFrame(indices, columns=columns)
def to_xml_element(self):
"""Return XML Element representing the Filter.
@ -1282,15 +1268,34 @@ class MeshSurfaceFilter(MeshFilter):
def shape(self):
return (self.num_bins,)
@staticmethod
def _current_names(mesh):
if isinstance(mesh, openmc.HexagonalMesh):
names = []
ax0, ax1, ax2, ax3 = mesh._axis_labels
pairs = [(ax0, ax1), (ax1, ax2), (ax2, ax0)]
for ax0, ax1 in pairs:
for minmax0, minmax1 in [('max','min'),('min','max')]:
for inout in ('out','in'):
names.append(f"{ax0}-{minmax0} {ax1}-{minmax1} {inout}")
for minmax in ('min', 'max'):
for inout in ('out','in'):
names.append(f"{ax3}-{minmax} {inout}")
return names
names = []
for ax in mesh._axis_labels:
for minmax in ('min', 'max'):
for inout in ('out','in'):
names.append(f"{ax}-{minmax} {inout}")
return names
@MeshFilter.mesh.setter
def mesh(self, mesh):
cv.check_type('filter mesh', mesh, openmc.MeshBase)
self._mesh = mesh
# Take the product of mesh indices and current names
n_dim = mesh.n_dimension
self.bins = [mesh_tuple + (surf,) for mesh_tuple, surf in
product(mesh.indices, _CURRENT_NAMES[:4*n_dim])]
product(mesh.indices, self._current_names(mesh))]
def get_pandas_dataframe(self, data_size, stride, **kwargs):
"""Builds a Pandas DataFrame for the Filter's bins.
@ -1320,47 +1325,19 @@ class MeshSurfaceFilter(MeshFilter):
Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe()
"""
# Initialize Pandas DataFrame
df = pd.DataFrame()
# Initialize dictionary to build Pandas Multi-index column
filter_dict = {}
# Append mesh ID as outermost index of multi-index
mesh_key = f'mesh {self.mesh.id}'
# Find mesh dimensions - use 3D indices for simplicity
n_surfs = 4 * len(self.mesh.dimension)
if len(self.mesh.dimension) == 3:
nx, ny, nz = self.mesh.dimension
elif len(self.mesh.dimension) == 2:
nx, ny = self.mesh.dimension
nz = 1
else:
nx = self.mesh.dimension
ny = nz = 1
n_surfs = len(self._current_names(self._mesh))
# Generate multi-index sub-column for x-axis
filter_dict[mesh_key, 'x'] = _repeat_and_tile(
np.arange(1, nx + 1), n_surfs * stride, data_size)
# Generate multi-index sub-column for y-axis
if len(self.mesh.dimension) > 1:
filter_dict[mesh_key, 'y'] = _repeat_and_tile(
np.arange(1, ny + 1), n_surfs * nx * stride, data_size)
# Generate multi-index sub-column for z-axis
if len(self.mesh.dimension) > 2:
filter_dict[mesh_key, 'z'] = _repeat_and_tile(
np.arange(1, nz + 1), n_surfs * nx * ny * stride, data_size)
# Generate multi-index sub-column for surface
filter_dict[mesh_key, 'surf'] = _repeat_and_tile(
_CURRENT_NAMES[:n_surfs], stride, data_size)
# Initialize a Pandas DataFrame from the mesh dictionary
return pd.concat([df, pd.DataFrame(filter_dict)])
columns = [(mesh_key, label) for label in self.mesh._axis_labels]
indices = _repeat_and_tile(list(self.mesh.indices), stride*n_surfs, data_size)
filter_dict = dict(zip(columns,indices.T))
surfs = _repeat_and_tile(self._current_names(self._mesh), stride, data_size)
filter_dict[mesh_key, 'surf'] = surfs
return pd.DataFrame(filter_dict)
class CollisionFilter(Filter):
"""Bins tally events based on the number of collisions.

View file

@ -18,7 +18,7 @@ from ..mesh import MeshMaterialVolumes
__all__ = [
'Mesh', 'RegularMesh', 'RectilinearMesh', 'CylindricalMesh',
'SphericalMesh', 'UnstructuredMesh', 'meshes', 'MeshMaterialVolumes'
'SphericalMesh', 'HexagonalMesh', 'UnstructuredMesh', 'meshes', 'MeshMaterialVolumes'
]
@ -108,6 +108,11 @@ _dll.openmc_spherical_mesh_set_grid.argtypes = [c_int32, POINTER(c_double),
_dll.openmc_spherical_mesh_set_grid.restype = c_int
_dll.openmc_spherical_mesh_set_grid.errcheck = _error_handler
_dll.openmc_hexagonal_mesh_get_grid.argtypes = [c_int32,
POINTER(POINTER(c_double)), POINTER(c_int), POINTER(c_double),
POINTER(POINTER(c_double)), POINTER(c_double), POINTER(c_char_p)]
_dll.openmc_hexagonal_mesh_get_grid.restype = c_int
_dll.openmc_hexagonal_mesh_get_grid.errcheck = _error_handler
class Mesh(_FortranObjectWithID):
"""Base class to represent mesh objects
@ -727,6 +732,91 @@ class SphericalMesh(Mesh):
ntheta, phi_grid, nphi)
class HexagonalMesh(Mesh):
"""HexagonalMesh stored internally.
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.lib.meshes` mapping.
Parameters
----------
index : int
Index in the `meshes` array.
Attributes
----------
id : int
ID of the mesh
z_grid : numpy.ndarray
1-D array of mesh boundary points along the z-axis.
pitch : float
Radial pitch of the hexagonal mesh in cm.
num_rings : int
Number of radial ring positions in the xy-plane
orientation : {'x', 'y'}
The orientation of the lattice. The 'x' orientation means that each
lattice element has two faces that are perpendicular to the x-axis,
while the 'y' orientation means that each lattice element has two faces
that are perpendicular to the y-axis. By default, the orientation is
'y'.
origin : numpy.ndarray
1-D array of length 3 the (x,y,z) origin of the mesh in
cartesian coordinates
n_elements : int
Total number of mesh elements.
volumes : numpy.ndarray
Volume of each mesh element in [cm^3]
bounding_box : openmc.BoundingBox
Axis-aligned bounding box of the mesh
"""
mesh_type = 'hexagonal'
def __init__(self, uid=None, new=True, index=None):
super().__init__(uid, new, index)
@property
def n_elements(self):
z_grid, nr, *_ = self._get_parameters()
return (z_grid.size-1)*(3*nr*(nr-1)+1)
@property
def num_rings(self):
return self._get_parameters()[1]
@property
def pitch(self):
return self._get_parameters()[2]
@property
def orientation(self):
return self._get_parameters()[4]
@property
def origin(self):
return self._get_parameters()[3]
@property
def z_grid(self):
return self._get_parameters()[0]
def _get_parameters(self):
gz = POINTER(c_double)()
nz = c_int()
nr = c_int()
orig = POINTER(c_double)()
orient = c_char()
p = c_double()
# Call C API to get grid parameters
_dll.openmc_hexagonal_mesh_get_grid(self._index, gz, nz, nr, orig, p, orient)
# Convert grid parameters to Numpy arrays
grid_z = as_array(gz, (nz.value,))
origin = as_array(orig, (3,))
return (grid_z, nr, pitch, origin, orientation.decode())
class UnstructuredMesh(Mesh):
pass
@ -736,6 +826,7 @@ _MESH_TYPE_MAP = {
'rectilinear': RectilinearMesh,
'cylindrical': CylindricalMesh,
'spherical': SphericalMesh,
'hexagonal': HexagonalMesh,
'unstructured': UnstructuredMesh
}

View file

@ -299,6 +299,8 @@ class MeshBase(IDManagerMixin, ABC):
return CylindricalMesh.from_hdf5(group, mesh_id, mesh_name)
elif mesh_type == 'spherical':
return SphericalMesh.from_hdf5(group, mesh_id, mesh_name)
elif mesh_type == 'hexagonal':
return HexagonalMesh.from_hdf5(group, mesh_id, mesh_name)
elif mesh_type == 'unstructured':
return UnstructuredMesh.from_hdf5(group, mesh_id, mesh_name)
else:
@ -526,11 +528,6 @@ class StructuredMesh(MeshBase):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@property
@abstractmethod
def dimension(self):
pass
@property
@abstractmethod
def n_dimension(self):
@ -541,11 +538,6 @@ class StructuredMesh(MeshBase):
def _axis_labels(self):
pass
@property
@abstractmethod
def _grids(self):
pass
@abstractmethod
def get_indices_at_coords(self, coords: Sequence[float]) -> tuple:
pass
@ -2720,6 +2712,281 @@ class SphericalMesh(StructuredMesh):
return (r_index, theta_index, phi_index)
class HexagonalMesh(StructuredMesh):
"""A 3D hexagonal mesh
Parameters
----------
z_grid : numpy.ndarray
1-D array of mesh boundary points along the z-axis.
pitch : float
Radial pitch of the hexagonal mesh in cm.
num_rings : int
Number of radial ring positions in the xy-plane
orientation : {'x', 'y'}
The orientation of the lattice. The 'x' orientation means that each
lattice element has two faces that are perpendicular to the x-axis,
while the 'y' orientation means that each lattice element has two faces
that are perpendicular to the y-axis. By default, the orientation is
'y'.
origin : numpy.ndarray
1-D array of length 3 the (x,y,z) origin of the mesh in
cartesian coordinates
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
z_grid : numpy.ndarray
1-D array of mesh boundary points along the z-axis.
pitch : float
Radial pitch of the hexagonal mesh in cm.
num_rings : int
Number of radial ring positions in the xy-plane
orientation : {'x', 'y'}
The orientation of the lattice. The 'x' orientation means that each
lattice element has two faces that are perpendicular to the x-axis,
while the 'y' orientation means that each lattice element has two faces
that are perpendicular to the y-axis. By default, the orientation is
'y'.
origin : numpy.ndarray
1-D array of length 3 the (x,y,z) origin of the mesh in
cartesian coordinates
indices : Iterable of tuple
An iterable of mesh indices for each mesh element, e.g. [(1, 1, 1),
(2, 1, 1), ...]
lower_left : numpy.ndarray
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 : numpy.ndarray
The upper-right corner of the structured mesh. If only two coordinate
are given, it is assumed that the mesh is an x-y mesh.
bounding_box : openmc.BoundingBox
Axis-aligned bounding box of the mesh as defined by the upper-right and
lower-left coordinates.
"""
def __init__(
self,
z_grid: Sequence[float],
pitch: float,
num_rings: int,
orientation: str = 'y',
origin: Sequence[float] = (0., 0., 0.),
mesh_id: int | None = None,
name: str = '',
):
super().__init__(mesh_id, name)
self.z_grid = z_grid
self.pitch = pitch
self.num_rings = num_rings
self.orientation = orientation
self.origin = origin
@property
def _axis_labels(self):
return ('r', 'q', 's', 'z')
@property
def origin(self):
return self._origin
@origin.setter
def origin(self, coords):
cv.check_type('mesh origin', coords, Iterable, Real)
cv.check_length("mesh origin", coords, 3)
self._origin = np.asarray(coords, dtype=float)
@property
def z_grid(self):
return self._z_grid
@z_grid.setter
def z_grid(self, grid):
cv.check_type('mesh z_grid', grid, Iterable, Real)
cv.check_length('mesh z_grid', grid, 2)
cv.check_increasing('mesh z_grid', grid)
self._z_grid = np.asarray(grid, dtype=float)
@property
def pitch(self):
return self._pitch
@pitch.setter
def pitch(self, pitch):
cv.check_type('mesh radial pitch', pitch, Real)
cv.check_greater_than('mesh radial pitch', pitch, 0.0)
self._pitch = pitch
@property
def n_elements(self):
return (self.z_grid.size - 1)*(3*self.num_rings*(self.num_rings - 1) + 1)
@property
def dimension(self):
return (self.n_elements,)
@property
def n_dimension(self):
return 4
@property
def lower_left(self):
p = self.pitch
r = self.pitch/np.sqrt(3.0)
n = self.num_rings
offset = np.array([0,0,self.z_grid[0]])
if self.orientation == 'x':
offset[0] = -p*(n-0.5)
offset[1] = -(1+1.5*(n-1))*r
else:
offset[0] = -(1+1.5*(n-1))*r
offset[1] = -p*(n-0.5)
return self.origin + offset
@property
def upper_right(self):
p = self.pitch
r = self.pitch/np.sqrt(3.0)
n = self.num_rings
offset = np.array([0,0,self.z_grid[-1]])
if self.orientation == 'x':
offset[0] = p*(n-0.5)
offset[1] = (1+1.5*(n-1))*r
else:
offset[0] = (1+1.5*(n-1))*r
offset[1] = p*(n-0.5)
return self.origin + offset
@property
def num_rings(self):
return self._num_rings
@num_rings.setter
def num_rings(self, num_rings):
cv.check_type('mesh num_rings', num_rings, Integral)
cv.check_greater_than('mesh num_rings', num_rings, 0)
self._num_rings = num_rings
@property
def orientation(self):
return self._orientation
@orientation.setter
def orientation(self, orientation):
cv.check_value('orientation', orientation.lower(), ('x', 'y'))
self._orientation = orientation.lower()
@property
def indices(self):
idx = []
for j in range(self.z_grid.size-1):
idx.append((0, 0, 0, j))
for rad in range(1, self.num_rings):
for i in range(rad):
idx.append((i, rad-i, -rad, j))
idx.append((rad, -i, i-rad, j))
idx.append((rad-i, -rad, i, j))
idx.append((-i, i-rad, rad, j))
idx.append((-rad, i, rad-i, j))
idx.append((i-rad, rad, -i, j))
return idx
def __repr__(self):
fmt = '{0: <16}{1}{2}\n'
string = super().__repr__()
string += fmt.format('\tOrigin', '=\t', self.origin)
string += fmt.format('\tPitch', '=\t', self.pitch)
string += fmt.format('\tN rings', '=\t', self.num_rings)
string += fmt.format('\tOrientation', '=\t', self.orientation)
z_grid_str = str(self._z_grid) if self._z_grid is None else len(self._z_grid)
string += fmt.format('\tN Z pnts:', '=\t', z_grid_str)
if self._z_grid is not None:
string += fmt.format('\tZ Min:', '=\t', self._z_grid[0])
string += fmt.format('\tZ Max:', '=\t', self._z_grid[-1])
return string
@classmethod
def from_hdf5(cls, group: h5py.Group, mesh_id: int, name: str):
# Read and assign mesh properties
mesh = cls(
z_grid = group['z_grid'][()],
pitch = group.attrs['pitch'],
num_rings = group.attrs['num_rings'],
mesh_id=mesh_id,
name=name
)
if 'origin' in group:
mesh.origin = group['origin'][()]
if 'orientation' in group.attrs:
mesh.orientation = group.attrs['orientation']
return mesh
def to_xml_element(self):
"""Return XML representation of the mesh
Returns
-------
element : lxml.etree._Element
XML element containing mesh data
"""
element = super().to_xml_element()
element.set("type", "hexagonal")
element.set("pitch", str(self.pitch))
element.set("num_rings", str(self.num_rings))
element.set("orientation", str(self.orientation))
subelement = ET.SubElement(element, "z_grid")
subelement.text = ' '.join(map(str, self.z_grid))
subelement = ET.SubElement(element, "origin")
subelement.text = ' '.join(map(str, self.origin))
return element
@classmethod
def from_xml_element(cls, elem: ET.Element):
"""Generate a spherical mesh from an XML element
Parameters
----------
elem : lxml.etree._Element
XML element
Returns
-------
openmc.SphericalMesh
Spherical mesh object
"""
mesh_id = int(get_text(elem, 'id'))
mesh = cls(
mesh_id=mesh_id,
z_grid = get_elem_list(elem, "z_grid", float),
pitch = float(get_text(elem, "pitch")),
num_rings = int(get_text(elem, "num_rings")),
orientation = get_text(elem, "orientation"),
origin = get_elem_list(elem, "origin", float) or [0., 0., 0.],
)
return mesh
def get_indices_at_coords(self, coords: Sequence[float]) -> tuple:
raise NotImplementedError(
"get_indices_at_coords is not yet implemented for SphericalMesh"
)
def require_statepoint_data(func):
@wraps(func)

View file

@ -343,6 +343,8 @@ const std::unique_ptr<Mesh>& Mesh::create(
model::meshes.push_back(make_unique<CylindricalMesh>(dataset));
} else if (mesh_type == SphericalMesh::mesh_type) {
model::meshes.push_back(make_unique<SphericalMesh>(dataset));
} else if (mesh_type == HexagonalMesh::mesh_type) {
model::meshes.push_back(make_unique<HexagonalMesh>(dataset));
#ifdef OPENMC_DAGMC_ENABLED
} else if (mesh_type == UnstructuredMesh::mesh_type &&
mesh_library == MOABMesh::mesh_lib_type) {
@ -768,6 +770,17 @@ std::string StructuredMesh::bin_label(int bin) const
}
}
std::string StructuredMesh::surface_label(int surface) const
{
int axis = static_cast<int>(std::floor(surface / 4));
std::string label = axes_labels_[axis];
const std::vector<std::string> MINMAX = {"min", "max"};
const std::vector<std::string> OUTIN = {"Outgoing", "Incoming"};
auto minmax = MINMAX[static_cast<int>(std::floor((surface % 4) / 2))];
auto outin = OUTIN[surface % 2];
return fmt::format("{}, {}-{}", outin, minmax, label);
}
tensor::Tensor<int> StructuredMesh::get_shape_tensor() const
{
return tensor::Tensor<int>(shape_.data(), static_cast<size_t>(n_dimension_));
@ -1037,6 +1050,11 @@ StructuredMesh::MeshIndex StructuredMesh::get_indices(
return ijk;
}
bool StructuredMesh::valid_index(const MeshIndex& ijk, int k) const
{
return ((ijk[k] >= 1) && (ijk[k] <= shape_[k]));
}
int StructuredMesh::get_bin_from_indices(const MeshIndex& ijk) const
{
switch (n_dimension_) {
@ -1197,14 +1215,13 @@ void StructuredMesh::raytrace_mesh(
}
// Calculate initial distances to next surfaces in all three dimensions
std::array<MeshDistance, 3> distances;
std::array<MeshDistance, 4> distances;
for (int k = 0; k < n; ++k) {
distances[k] = distance_to_grid_boundary(ijk, k, local_r, u, 0.0);
}
// Loop until r = r1 is eventually reached
while (true) {
if (in_mesh) {
// find surface with minimal distance to current position
@ -1225,14 +1242,17 @@ void StructuredMesh::raytrace_mesh(
// current
tally.surface(ijk, k, distances[k].max_surface, false);
// Update cell and calculate distance to next surface in k-direction.
// The two other directions are still valid!
ijk[k] = distances[k].next_index;
distances[k] =
distance_to_grid_boundary(ijk, k, local_r, u, traveled_distance);
// Update cell and calculate distance to next surface in correlated
// directions.
for (int j = 0; j < 3; ++j)
ijk[j] += distances[k].offset[j];
sanitize_index(ijk);
for (auto j : correlated_axes_[k])
distances[j] =
distance_to_grid_boundary(ijk, j, local_r, u, traveled_distance);
// Check if we have left the interior of the mesh
in_mesh = ((ijk[k] >= 1) && (ijk[k] <= shape_[k]));
in_mesh = valid_index(ijk, k);
// If we are still inside the mesh, tally inward current for the next
// cell
@ -1240,23 +1260,9 @@ void StructuredMesh::raytrace_mesh(
tally.surface(ijk, k, !distances[k].max_surface, true);
} else { // not inside mesh
// For all directions outside the mesh, find the distance that we need
// to travel to reach the next surface. Use the largest distance, as
// only this will cross all outer surfaces.
int k_max {-1};
for (int k = 0; k < n; ++k) {
if ((ijk[k] < 1 || ijk[k] > shape_[k]) &&
(distances[k].distance > traveled_distance)) {
traveled_distance = distances[k].distance;
k_max = k;
}
}
// Assure some distance is traveled
if (k_max == -1) {
traveled_distance += TINY_BIT;
}
int k_max;
traveled_distance =
distance_to_mesh(ijk, distances, traveled_distance, k_max);
// If r1 is not inside the mesh, exit here
if (traveled_distance >= total_distance)
return;
@ -1276,6 +1282,29 @@ void StructuredMesh::raytrace_mesh(
}
}
double StructuredMesh::distance_to_mesh(const MeshIndex& ijk,
const std::array<MeshDistance, 4>& distances, double traveled_distance,
int& k_max) const
{
// For all directions outside the mesh, find the distance that we need
// to travel to reach the next surface. Use the largest distance, as
// only this will cross all outer surfaces.
const int n = n_dimension_;
k_max = -1;
for (int k = 0; k < n; ++k) {
if ((ijk[k] < 1 || ijk[k] > shape_[k]) &&
(distances[k].distance > traveled_distance)) {
traveled_distance = distances[k].distance;
k_max = k;
}
}
// Assure some distance is traveled
if (k_max == -1) {
traveled_distance += TINY_BIT;
}
return traveled_distance;
}
void StructuredMesh::bins_crossed(Position r0, Position r1, const Direction& u,
vector<int>& bins, vector<double>& lengths) const
{
@ -1511,16 +1540,15 @@ StructuredMesh::MeshDistance RegularMesh::distance_to_grid_boundary(
double l) const
{
MeshDistance d;
d.next_index = ijk[i];
if (std::abs(u[i]) < FP_PRECISION)
return d;
d.max_surface = (u[i] > 0);
if (d.max_surface && (ijk[i] <= shape_[i])) {
d.next_index++;
++d.offset[i];
d.distance = (positive_grid_boundary(ijk, i) - r0[i]) / u[i];
} else if (!d.max_surface && (ijk[i] >= 1)) {
d.next_index--;
--d.offset[i];
d.distance = (negative_grid_boundary(ijk, i) - r0[i]) / u[i];
}
@ -1684,16 +1712,15 @@ StructuredMesh::MeshDistance RectilinearMesh::distance_to_grid_boundary(
double l) const
{
MeshDistance d;
d.next_index = ijk[i];
if (std::abs(u[i]) < FP_PRECISION)
return d;
d.max_surface = (u[i] > 0);
if (d.max_surface && (ijk[i] <= shape_[i])) {
d.next_index++;
++d.offset[i];
d.distance = (positive_grid_boundary(ijk, i) - r0[i]) / u[i];
} else if (!d.max_surface && (ijk[i] > 0)) {
d.next_index--;
--d.offset[i];
d.distance = (negative_grid_boundary(ijk, i) - r0[i]) / u[i];
}
return d;
@ -1946,7 +1973,6 @@ StructuredMesh::MeshDistance CylindricalMesh::find_z_crossing(
const Position& r, const Direction& u, double l, int shell) const
{
MeshDistance d;
d.next_index = shell;
// Direction of flight is within xy-plane. Will never intersect z.
if (std::abs(u.z) < FP_PRECISION)
@ -1954,10 +1980,10 @@ StructuredMesh::MeshDistance CylindricalMesh::find_z_crossing(
d.max_surface = (u.z > 0.0);
if (d.max_surface && (shell <= shape_[2])) {
d.next_index += 1;
++d.offset[2];
d.distance = (grid_[2][shell] - r.z) / u.z;
} else if (!d.max_surface && (shell > 0)) {
d.next_index -= 1;
--d.offset[2];
d.distance = (grid_[2][shell - 1] - r.z) / u.z;
}
return d;
@ -1968,17 +1994,15 @@ StructuredMesh::MeshDistance CylindricalMesh::distance_to_grid_boundary(
double l) const
{
if (i == 0) {
return std::min(
MeshDistance(ijk[i] + 1, true, find_r_crossing(r0, u, l, ijk[i])),
MeshDistance(ijk[i] - 1, false, find_r_crossing(r0, u, l, ijk[i] - 1)));
MeshDistance({1, 0, 0}, true, find_r_crossing(r0, u, l, ijk[i])),
MeshDistance({-1, 0, 0}, false, find_r_crossing(r0, u, l, ijk[i] - 1)));
} else if (i == 1) {
return std::min(MeshDistance(sanitize_phi(ijk[i] + 1), true,
find_phi_crossing(r0, u, l, ijk[i])),
MeshDistance(sanitize_phi(ijk[i] - 1), false,
find_phi_crossing(r0, u, l, ijk[i] - 1)));
return std::min(
MeshDistance({0, 1, 0}, true, find_phi_crossing(r0, u, l, ijk[i])),
MeshDistance({0, -1, 0}, false, find_phi_crossing(r0, u, l, ijk[i] - 1)));
} else {
return find_z_crossing(r0, u, l, ijk[i]);
@ -1991,6 +2015,8 @@ int CylindricalMesh::set_grid()
static_cast<int>(grid_[1].size()) - 1,
static_cast<int>(grid_[2].size()) - 1};
axes_labels_ = {"r", "phi", "z"};
for (const auto& g : grid_) {
if (g.size() < 2) {
set_errmsg("r-, phi-, and z- grids for cylindrical meshes "
@ -2076,7 +2102,6 @@ SphericalMesh::SphericalMesh(pugi::xml_node node)
: PeriodicStructuredMesh {node}
{
n_dimension_ = 3;
grid_[0] = get_node_array<double>(node, "r_grid");
grid_[1] = get_node_array<double>(node, "theta_grid");
grid_[2] = get_node_array<double>(node, "phi_grid");
@ -2090,7 +2115,6 @@ SphericalMesh::SphericalMesh(pugi::xml_node node)
SphericalMesh::SphericalMesh(hid_t group) : PeriodicStructuredMesh {group}
{
n_dimension_ = 3;
read_dataset(group, "r_grid", grid_[0]);
read_dataset(group, "theta_grid", grid_[1]);
read_dataset(group, "phi_grid", grid_[2]);
@ -2297,20 +2321,19 @@ StructuredMesh::MeshDistance SphericalMesh::distance_to_grid_boundary(
if (i == 0) {
return std::min(
MeshDistance(ijk[i] + 1, true, find_r_crossing(r0, u, l, ijk[i])),
MeshDistance(ijk[i] - 1, false, find_r_crossing(r0, u, l, ijk[i] - 1)));
MeshDistance({1, 0, 0}, true, find_r_crossing(r0, u, l, ijk[i])),
MeshDistance({-1, 0, 0}, false, find_r_crossing(r0, u, l, ijk[i] - 1)));
} else if (i == 1) {
return std::min(MeshDistance(sanitize_theta(ijk[i] + 1), true,
find_theta_crossing(r0, u, l, ijk[i])),
MeshDistance(sanitize_theta(ijk[i] - 1), false,
find_theta_crossing(r0, u, l, ijk[i] - 1)));
return std::min(
MeshDistance({0, 1, 0}, true, find_theta_crossing(r0, u, l, ijk[i])),
MeshDistance(
{0, -1, 0}, false, find_theta_crossing(r0, u, l, ijk[i] - 1)));
} else {
return std::min(MeshDistance(sanitize_phi(ijk[i] + 1), true,
find_phi_crossing(r0, u, l, ijk[i])),
MeshDistance(sanitize_phi(ijk[i] - 1), false,
find_phi_crossing(r0, u, l, ijk[i] - 1)));
return std::min(
MeshDistance({0, 0, 1}, true, find_phi_crossing(r0, u, l, ijk[i])),
MeshDistance({0, 0, -1}, false, find_phi_crossing(r0, u, l, ijk[i] - 1)));
}
}
@ -2320,9 +2343,11 @@ int SphericalMesh::set_grid()
static_cast<int>(grid_[1].size()) - 1,
static_cast<int>(grid_[2].size()) - 1};
axes_labels_ = {"r", "theta", "phi"};
for (const auto& g : grid_) {
if (g.size() < 2) {
set_errmsg("x-, y-, and z- grids for spherical meshes "
set_errmsg("r-, theta-, and phi- grids for spherical meshes "
"must each have at least 2 points");
return OPENMC_E_INVALID_ARGUMENT;
}
@ -2398,6 +2423,413 @@ double SphericalMesh::volume(const MeshIndex& ijk) const
(std::cos(theta_i) - std::cos(theta_o)) * (phi_o - phi_i);
}
//==============================================================================
// HexagonalMesh implementation
//==============================================================================
HexagonalMesh::HexagonalMesh(pugi::xml_node node)
: PeriodicStructuredMesh {node}
{
grid_ = get_node_array<double>(node, "z_grid");
num_rings_ = std::stoi(get_node_value(node, "num_rings"));
pitch_ = std::stod(get_node_value(node, "pitch"));
origin_ = get_node_position(node, "origin");
// Read the orientation. Default to 'y'.
if (check_for_node(node, "orientation")) {
std::string orientation = get_node_value(node, "orientation");
if (orientation == "x") {
orientation_ = Orientation::x;
} else if (orientation != "y") {
fatal_error("Unrecognized orientation '" + orientation + "'");
}
}
if (int err = set_grid()) {
fatal_error(openmc_err_msg);
}
}
HexagonalMesh::HexagonalMesh(hid_t group) : PeriodicStructuredMesh {group}
{
read_dataset(group, "z_grid", grid_);
read_attribute(group, "num_rings", num_rings_);
read_attribute(group, "pitch", pitch_);
read_dataset(group, "origin", origin_);
if (attribute_exists(group, "orientation")) {
std::string orientation;
read_attribute(group, "orientation", orientation);
if (orientation == "x") {
orientation_ = Orientation::x;
} else if (orientation != "y") {
fatal_error("Unrecognized orientation '" + orientation + "'");
}
}
if (int err = set_grid()) {
fatal_error(openmc_err_msg);
}
}
const std::string HexagonalMesh::mesh_type = "hexagonal";
std::string HexagonalMesh::get_mesh_type() const
{
return mesh_type;
}
std::string HexagonalMesh::bin_label(int bin) const
{
MeshIndex ijk = get_indices_from_bin(bin);
return fmt::format(
"Mesh Index ({}, {}, {}, {})", ijk[0], ijk[1], -ijk[0] - ijk[1], ijk[2]);
}
std::string HexagonalMesh::surface_label(int surface) const
{
int axis = static_cast<int>(std::floor(surface / 4));
int max = static_cast<int>(std::floor((surface % 4) / 2));
const std::vector<std::string> MINMAX = {"min", "max"};
const std::vector<std::string> OUTIN = {"Outgoing", "Incoming"};
auto minmax = MINMAX[max];
auto outin = OUTIN[surface % 2];
std::string label = axes_labels_[axis];
if (axis == 3) {
return fmt::format("{}, {}-{}", outin, label, minmax);
}
auto minmax2 = MINMAX[1 - max];
std::string label2 = axes_labels_[(axis + 1) % 2];
return fmt::format("{}, {}-{} {}-{}", outin, label, minmax, label2, minmax2);
}
int HexagonalMesh::n_bins() const
{
return (1 + 3 * num_rings_ * (num_rings_ - 1)) * (grid_.size() - 1);
}
bool HexagonalMesh::valid_index(const MeshIndex& ijk, int k) const
{
if (k == 3)
return ((ijk[2] >= 1) && (ijk[2] < grid_.size()));
int ring =
std::max({std::abs(ijk[0]), std::abs(ijk[1]), std::abs(ijk[0] + ijk[1])});
return (ring < num_rings_);
}
int HexagonalMesh::get_bin_from_indices(const MeshIndex& ijk) const
{
int q = ijk[0];
int r = ijk[1];
int k = ijk[2];
int hexes = 3 * num_rings_ * (num_rings_ - 1) + 1;
int bin = (k - 1) * (grid_.size() - 1) * hexes;
int rad = std::max({std::abs(r), std::abs(q), std::abs(r + q)});
if (rad == 0)
return bin;
bin += 3 * rad * (rad - 1) + 1;
if ((q >= 0) and (r >= 0)) {
bin += q;
} else if ((r < 0) && ((-r - q) < 0)) {
bin += (rad - r);
} else if ((q >= 0) && ((-q - r) >= 0)) {
bin += (2 * rad - q - r);
} else if ((q < 0) && (r < 0)) {
bin += (3 * rad - q);
} else if ((r >= 0) && ((-q - r) >= 0)) {
bin += (4 * rad + r);
} else {
bin += (5 * rad + q + r);
}
return bin;
}
StructuredMesh::MeshIndex HexagonalMesh::get_indices(
Position r, bool& in_mesh) const
{
r = local_coords(r);
double q = q_dual_.dot(r);
double r0 = r_dual_.dot(r);
double s = -q - r0;
double q_r = std::round(q);
double r_r = std::round(r0);
double s_r = std::round(s);
std::array<double, 3> diff = {
std::abs(q - q_r), std::abs(r0 - r_r), std::abs(s - s_r)};
auto max_it = std::max_element(diff.begin(), diff.end());
int i = static_cast<int>(std::distance(diff.begin(), max_it));
if (i == 0)
q_r = -s_r - r_r;
else if (i == 1)
r_r = -q_r - s_r;
int z_i = get_index_in_z_direction(r.z);
int q_i = static_cast<int>(q_r);
int r_i = static_cast<int>(r_r);
int s_i = -q_i - r_i;
MeshIndex ijk;
in_mesh = true;
ijk[0] = q_i;
ijk[1] = r_i;
ijk[2] = z_i;
if (std::max({std::abs(q_i), std::abs(r_i), std::abs(s_i)}) >= num_rings_)
in_mesh = false;
if (ijk[2] < 1 || ijk[2] > grid_.size() - 1)
in_mesh = false;
return ijk;
}
StructuredMesh::MeshIndex HexagonalMesh::get_indices_from_bin(int bin) const
{
MeshIndex ijk = {0, 0, 0};
int hexes = 3 * num_rings_ * (num_rings_ - 1) + 1;
ijk[2] = static_cast<int>(std::floor(bin / hexes)) + 1;
int sp_idx = bin % hexes;
if (sp_idx == 0) {
return ijk;
}
int rad = static_cast<int>(std::floor((3 + std::sqrt(12 * sp_idx - 3)) / 6));
int offset = sp_idx - (3 * rad * (rad - 1) + 1);
int side = (offset / rad) % 6;
int dist = offset % rad;
switch (side) {
case 0:
ijk[0] = dist;
ijk[1] = rad - dist;
break;
case 1:
ijk[0] = rad;
ijk[1] = -dist;
break;
case 2:
ijk[0] = rad - dist;
ijk[1] = -rad;
break;
case 3:
ijk[0] = -dist;
ijk[1] = dist - rad;
break;
case 4:
ijk[0] = -rad;
ijk[1] = dist;
break;
case 5:
ijk[0] = dist - rad;
ijk[1] = rad;
break;
}
return ijk;
}
Position HexagonalMesh::sample_element(
const MeshIndex& ijk, uint64_t* seed) const
{
double radius = pitch_ / std::sqrt(3.0);
double q0 = ijk[0];
double r0 = ijk[1];
double z = uniform_distribution(grid_[ijk[2] - 1], grid_[ijk[2]], seed);
double q, r, s;
do {
double rad = std::cbrt(prn(seed)) * radius;
double phi = uniform_distribution(0.0, 2 * PI, seed);
double x = rad * std::cos(phi);
double y = rad * std::sin(phi);
q = q_dual_[0] * x + q_dual_[1] * y;
r = r_dual_[0] * x + r_dual_[1] * y;
s = -q - r;
} while (std::max({std::abs(q), std::abs(r), std::abs(s)}) > 1);
return origin_ + radius * ((q + q0) * q_ + (r + r0) * r_) + z;
}
StructuredMesh::MeshDistance HexagonalMesh::find_z_crossing(
const Position& r, const Direction& u, double l, int shell) const
{
MeshDistance d;
// Direction of flight is within xy-plane. Will never intersect z.
if (std::abs(u.z) < FP_PRECISION)
return d;
d.max_surface = (u.z > 0.0);
if (d.max_surface && (shell < grid_.size())) {
++d.offset[2];
d.distance = (grid_[shell] - r.z) / u.z;
} else if (!d.max_surface && (shell > 0)) {
--d.offset[2];
d.distance = (grid_[shell - 1] - r.z) / u.z;
}
return d;
}
StructuredMesh::MeshDistance HexagonalMesh::distance_to_grid_boundary(
const MeshIndex& ijk, int i, const Position& r0, const Direction& u,
double l) const
{
auto r = local_coords(r0);
if (i == 3)
return find_z_crossing(r, u, l, ijk[2]);
r.z = 0.0;
MeshDistance d;
Direction s_ = -q_ - r_;
Direction dir;
MeshIndex idx = {ijk[0], ijk[1], -ijk[0] - ijk[1]};
MeshIndex offset = {0, 0, 0};
double distance;
if (i == 0) {
dir = 0.5 * (q_ - r_);
++offset[0];
--offset[1];
} else if (i == 1) {
dir = 0.5 * (r_ - s_);
++offset[1];
--offset[2];
} else {
dir = 0.5 * (s_ - q_);
--offset[0];
++offset[2];
}
dir /= dir.norm();
if (std::abs(u.dot(dir)) < FP_PRECISION)
return d;
d.max_surface = (u.dot(dir) > 0.0);
if (d.max_surface) {
distance = (idx[0] * q_ + idx[1] * r_ + dir - r).norm() / u.dot(dir);
idx[0] += offset[0];
idx[1] += offset[1];
idx[2] += offset[2];
} else {
distance = -(idx[0] * q_ + idx[1] * r_ - dir - r).norm() / u.dot(dir);
idx[0] -= offset[0];
idx[1] -= offset[1];
idx[2] -= offset[2];
}
int radius = std::max({std::abs(idx[0]), std::abs(idx[1]), std::abs(idx[2])});
if (d.max_surface && (radius < num_rings_)) {
d.offset[0] += offset[0];
d.offset[1] += offset[1];
d.distance = distance;
} else if (!d.max_surface && (radius < num_rings_)) {
d.offset[0] -= offset[0];
d.offset[1] -= offset[1];
d.distance = distance;
}
return d;
}
double HexagonalMesh::distance_to_mesh(const MeshIndex& ijk,
const std::array<MeshDistance, 4>& distances, double traveled_distance,
int& k_max) const
{
// For all directions outside the mesh, find the distance that we need
// to travel to reach the next surface. Use the largest distance, as
// only this will cross all outer surfaces.
const int n = n_dimension_;
k_max = -1;
for (int k = 0; k < n; ++k) {
if (distances[k].distance <= traveled_distance)
continue;
if ((k < 2) && (std::abs(ijk[k]) < num_rings_))
continue;
if ((k == 2) && (std::abs(ijk[0] + ijk[1]) < num_rings_))
continue;
if ((k == 3) && (ijk[2] >= 1) && (ijk[2] < grid_.size()))
continue;
traveled_distance = distances[k].distance;
k_max = k;
}
// Assure some distance is traveled
if (k_max == -1) {
traveled_distance += TINY_BIT;
}
return traveled_distance;
}
int HexagonalMesh::set_grid()
{
n_dimension_ = 4;
correlated_axes_ = {{0, 1, 2}, {0, 1, 2}, {0, 1, 2}, {3}};
axes_labels_ = {"r", "q", "s", "z"};
if (orientation_ == Orientation::x) {
q_ = {std::sqrt(3.0), 0.0, 0.0};
r_ = {0.5 * std::sqrt(3), -1.5, 0.0};
q_dual_ = {std::sqrt(3.0) / 3.0, 1.0 / 3.0, 0.0};
r_dual_ = {0.0, -2.0 / 3.0, 0.0};
} else {
q_ = {1.5, -0.5 * std::sqrt(3.0), 0.0};
r_ = {0.0, -std::sqrt(3.0), 0.0};
q_dual_ = {2.0 / 3.0, 0.0, 0.0};
r_dual_ = {-1.0 / 3.0, -std::sqrt(3.0) / 3.0, 0.0};
}
double size = pitch_ / std::sqrt(3.0);
q_ *= size;
r_ *= size;
q_dual_ /= size;
r_dual_ /= size;
if (grid_.size() < 2) {
set_errmsg("z- grid for hexagonal meshes "
"must have at least 2 points");
return OPENMC_E_INVALID_ARGUMENT;
}
if (std::adjacent_find(grid_.begin(), grid_.end(), std::greater_equal<>()) !=
grid_.end()) {
set_errmsg("Values in z- grid for "
"hexagonal meshes must be sorted and unique.");
return OPENMC_E_INVALID_ARGUMENT;
}
return 0;
}
int HexagonalMesh::get_index_in_z_direction(double z) const
{
return lower_bound_index(grid_.begin(), grid_.end(), z) + 1;
}
std::pair<vector<double>, vector<double>> HexagonalMesh::plot(
Position plot_ll, Position plot_ur) const
{
fatal_error("Plot of hexagonal Mesh not implemented");
// Figure out which axes lie in the plane of the plot.
array<vector<double>, 2> axis_lines;
return {axis_lines[0], axis_lines[1]};
}
void HexagonalMesh::to_hdf5_inner(hid_t mesh_group) const
{
write_dataset(mesh_group, "z_grid", grid_);
if (orientation_ == Orientation::x)
write_attribute(mesh_group, "orientation", "x");
write_attribute(mesh_group, "num_rings", num_rings_);
write_attribute(mesh_group, "pitch", pitch_);
write_dataset(mesh_group, "origin", origin_);
}
double HexagonalMesh::volume(const MeshIndex& ijk) const
{
double f = 1.5 * std::sqrt(3.0);
double size = pitch_ / std::sqrt(3.0);
double z_i = grid_[ijk[2] - 1];
double z_o = grid_[ijk[2]];
return f * (z_o - z_i) * size * size;
}
//==============================================================================
// Helper functions for the C API
//==============================================================================
@ -2464,6 +2896,8 @@ extern "C" int openmc_extend_meshes(
model::meshes.push_back(make_unique<CylindricalMesh>());
} else if (SphericalMesh::mesh_type == type) {
model::meshes.push_back(make_unique<SphericalMesh>());
} else if (HexagonalMesh::mesh_type == type) {
model::meshes.push_back(make_unique<HexagonalMesh>());
} else {
throw std::runtime_error {"Unknown mesh type: " + std::string(type)};
}
@ -2857,6 +3291,28 @@ extern "C" int openmc_spherical_mesh_set_grid(int32_t index,
index, grid_x, nx, grid_y, ny, grid_z, nz);
}
//! Get the hexagonal mesh grid
extern "C" int openmc_hexagonal_mesh_get_grid(int32_t index, double** grid_z,
int* nz, int* nr, double** origin, double* pitch, const char** orient)
{
if (int err = check_mesh_type<HexagonalMesh>(index))
return err;
HexagonalMesh* m = dynamic_cast<HexagonalMesh*>(model::meshes[index].get());
if (m->grid_.empty()) {
set_errmsg("Mesh parameters have not been set.");
return OPENMC_E_ALLOCATE;
}
*grid_z = m->grid_.data();
*nz = m->grid_.size();
*nr = m->num_rings_;
*origin = &m->origin_.x;
*pitch = m->pitch_;
*orient = (m->orientation_ == HexagonalMesh::Orientation::y) ? "y" : "x";
return 0;
}
#ifdef OPENMC_DAGMC_ENABLED
const std::string MOABMesh::mesh_lib_type = "moab";

View file

@ -25,62 +25,24 @@ void MeshSurfaceFilter::get_all_bins(
std::string MeshSurfaceFilter::text_label(int bin) const
{
auto& mesh = *model::meshes[mesh_];
int n_dim = mesh.n_dimension_;
auto mesh = dynamic_cast<StructuredMesh*>(model::meshes[mesh_].get());
int n_dim = mesh->n_dimension_;
// Get flattend mesh index and surface index.
int i_mesh = bin / (4 * n_dim);
MeshDir surf_dir = static_cast<MeshDir>(bin % (4 * n_dim));
int i_surf = bin % (4 * n_dim);
// Get mesh index part of label.
std::string out = MeshFilter::text_label(i_mesh);
// Get surface part of label.
switch (surf_dir) {
case MeshDir::OUT_LEFT:
out += " Outgoing, x-min";
break;
case MeshDir::IN_LEFT:
out += " Incoming, x-min";
break;
case MeshDir::OUT_RIGHT:
out += " Outgoing, x-max";
break;
case MeshDir::IN_RIGHT:
out += " Incoming, x-max";
break;
case MeshDir::OUT_BACK:
out += " Outgoing, y-min";
break;
case MeshDir::IN_BACK:
out += " Incoming, y-min";
break;
case MeshDir::OUT_FRONT:
out += " Outgoing, y-max";
break;
case MeshDir::IN_FRONT:
out += " Incoming, y-max";
break;
case MeshDir::OUT_BOTTOM:
out += " Outgoing, z-min";
break;
case MeshDir::IN_BOTTOM:
out += " Incoming, z-min";
break;
case MeshDir::OUT_TOP:
out += " Outgoing, z-max";
break;
case MeshDir::IN_TOP:
out += " Incoming, z-max";
break;
}
out += " ";
out += mesh->surface_label(i_surf);
return out;
}
void MeshSurfaceFilter::set_mesh(int32_t mesh)
{
mesh_ = mesh;
if (!dynamic_cast<StructuredMesh*>(model::meshes[mesh_].get()))
fatal_error("Only structured mesh is supported in MeshSurfaceFilter.");
n_bins_ = model::meshes[mesh_]->n_surface_bins();
}

View file

@ -0,0 +1,138 @@
from itertools import product, permutations
import openmc
import numpy as np
import pytest
pitch = 1.25
@pytest.fixture(params=['x','y'])
def model(request):
openmc.reset_auto_ids()
orientation = request.param
water = openmc.Material(name='water')
water.add_element('H', 2.0)
water.add_element('O', 1.0)
water.set_density('g/cc', 1.0)
outer = openmc.Cell(fill=water, cell_id=100)
cell10 = openmc.Cell(fill=water, cell_id=10)
cell00 = openmc.Cell(fill=water, cell_id=0)
cell01 = openmc.Cell(fill=water, cell_id=1)
cell02 = openmc.Cell(fill=water, cell_id=2)
cell03 = openmc.Cell(fill=water, cell_id=3)
cell04 = openmc.Cell(fill=water, cell_id=4)
cell05 = openmc.Cell(fill=water, cell_id=5)
univ10 = openmc.Universe(cells=[cell10])
univ00 = openmc.Universe(cells=[cell00])
univ01 = openmc.Universe(cells=[cell01])
univ02 = openmc.Universe(cells=[cell02])
univ03 = openmc.Universe(cells=[cell03])
univ04 = openmc.Universe(cells=[cell04])
univ05 = openmc.Universe(cells=[cell05])
plane1 = openmc.ZPlane(-10.0, boundary_type='vacuum')
plane2 = openmc.ZPlane(10.0, boundary_type='vacuum')
lat = openmc.HexLattice()
lat.center = (0., 0.)
lat.pitch = (pitch,)
lat.universes = [[univ00, univ01, univ02, univ03, univ04, univ05], [univ10]]
lat.outer = openmc.Universe(cells=[outer])
lat.orientation = orientation
hex_prism = openmc.model.HexagonalPrism(
edge_length=2*pitch,
orientation=orientation,
boundary_type='vacuum'
)
cell = openmc.Cell(region=-hex_prism & +plane1 & -plane2, fill=lat)
geom = openmc.Geometry([cell])
source = openmc.IndependentSource()
source.space = openmc.stats.Point()
source.energy = openmc.stats.Discrete([10000], [1.0])
settings = openmc.Settings()
settings.particles = 2000
settings.batches = 10
settings.run_mode = 'fixed source'
# build
mesh = openmc.HexagonalMesh(
z_grid=[-10.0, 10.0],
num_rings = 2,
pitch = pitch,
orientation = orientation,
)
tally = openmc.Tally()
mesh_filter = openmc.MeshFilter(mesh)
cell_filter = openmc.CellFilter([cell10, cell00, cell01, cell02, cell03, cell04, cell05])
tally.filters.append(mesh_filter)
tally.filters.append(cell_filter)
tally.scores.append("total")
tallies = openmc.Tallies([tally])
return openmc.Model(geometry=geom, settings=settings, tallies=tallies)
@pytest.mark.parametrize("estimator", ["collision", "tracklength"])
def test_correct_locations(model, run_in_tmpdir, estimator):
tally, = model.tallies
tally.estimator = estimator
model.run(apply_tally_results=True)
df = tally.get_pandas_dataframe()
df = df[df['mean']>0]
df = df.set_index("cell")["mesh 1"]
assert len(df) == 7
if tally.filters[0].mesh.orientation == "y":
assert tuple(df.loc[10]) == (0,0,0,0)
assert tuple(df.loc[0]) == (0,-1,1,0)
assert tuple(df.loc[1]) == (1,-1,0,0)
assert tuple(df.loc[2]) == (1,0,-1,0)
assert tuple(df.loc[3]) == (0,1,-1,0)
assert tuple(df.loc[4]) == (-1,1,0,0)
assert tuple(df.loc[5]) == (-1,0,1,0)
else:
assert tuple(df.loc[10]) == (0,0,0,0)
assert tuple(df.loc[0]) == (1,0,-1,0)
assert tuple(df.loc[1]) == (0,1,-1,0)
assert tuple(df.loc[2]) == (-1,1,0,0)
assert tuple(df.loc[3]) == (-1,0,1,0)
assert tuple(df.loc[4]) == (0,-1,1,0)
assert tuple(df.loc[5]) == (1,-1,0,0)
def test_meshsurface(model, run_in_tmpdir):
tally, = model.tallies
orientation = tally.filters[0].mesh.orientation
tally = openmc.Tally()
mesh = openmc.HexagonalMesh(
z_grid=[-10.0, 10.0],
num_rings = 2,
pitch = pitch,
orientation = orientation,
)
tally.filters = [openmc.MeshSurfaceFilter(mesh)]
tally.scores = ['current']
model.tallies = [tally]
model.run(apply_tally_results=True)
df = tally.get_pandas_dataframe()
z_max_in = df['mesh 2', 'surf']=='z-max in'
z_min_in = df['mesh 2', 'surf']=='z-min in'
assert np.all(df[z_max_in & z_min_in]['mean']==0.0)