Skip to content
10 changes: 10 additions & 0 deletions include/openmc/mesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,12 @@ class Mesh {
//! \param[in] bin Mesh bin to generate a label for
virtual std::string bin_label(int bin) const = 0;

//! Axis names (per dimension) used when labeling surface tally bins
virtual std::array<const char*, 3> axis_labels() const;

//! Build the surface component of a mesh surface tally bin label
std::string surface_bin_label(int surf_index) const;

//! Get the volume of a mesh bin
//
//! \param[in] bin Bin to return the volume for
Expand Down Expand Up @@ -588,6 +594,8 @@ class CylindricalMesh : public PeriodicStructuredMesh {

static const std::string mesh_type;

std::array<const char*, 3> axis_labels() const override;

Position sample_element(const MeshIndex& ijk, uint64_t* seed) const override;

MeshDistance distance_to_grid_boundary(const MeshIndex& ijk, int i,
Expand Down Expand Up @@ -653,6 +661,8 @@ class SphericalMesh : public PeriodicStructuredMesh {

static const std::string mesh_type;

std::array<const char*, 3> axis_labels() const override;

Position sample_element(const MeshIndex& ijk, uint64_t* seed) const override;

MeshDistance distance_to_grid_boundary(const MeshIndex& ijk, int i,
Expand Down
15 changes: 0 additions & 15 deletions include/openmc/tallies/filter_meshsurface.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,6 @@ class MeshSurfaceFilter : public MeshFilter {
// 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
Expand Down
74 changes: 38 additions & 36 deletions openmc/filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,19 @@
'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'
)
def _mesh_current_names(mesh):
"""Return surface-crossing bin names derived from a mesh's axis labels.

For each axis (in dimension order) the four crossing names are produced as
"<axis>-min out", "<axis>-min in", "<axis>-max out", "<axis>-max in". This
yields x/y/z names for Cartesian meshes, r/phi/z for cylindrical, and
r/theta/phi for spherical.
"""
names = []
for axis in mesh.axis_labels:
names += [f'{axis}-min out', f'{axis}-min in',
f'{axis}-max out', f'{axis}-max in']
return names


class FilterMeta(ABCMeta):
Expand Down Expand Up @@ -996,7 +1003,7 @@ def get_pandas_dataframe(self, data_size, stride, **kwargs):
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):
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
Expand Down Expand Up @@ -1273,7 +1280,8 @@ class MeshSurfaceFilter(MeshFilter):
Unique identifier for the filter
bins : list of tuple
A list of mesh indices / surfaces for each filter bin, e.g. [(1, 1,
'x-min out'), (1, 1, 'x-min in'), ...]
'x-min out'), (1, 1, 'x-min in'), ...]. Surface names use the mesh's
axis labels (e.g. r/phi/z for a cylindrical mesh).
num_bins : Integral
The number of filter bins

Expand All @@ -1287,10 +1295,12 @@ 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
# Take the product of mesh indices and surface-crossing names, using
# the mesh's own axis labels so cylindrical/spherical meshes get the
# correct names.
current_names = _mesh_current_names(mesh)
self.bins = [mesh_tuple + (surf,) for mesh_tuple, surf in
product(mesh.indices, _CURRENT_NAMES[:4*n_dim])]
product(mesh.indices, current_names)]

def get_pandas_dataframe(self, data_size, stride, **kwargs):
"""Builds a Pandas DataFrame for the Filter's bins.
Expand All @@ -1309,9 +1319,11 @@ def get_pandas_dataframe(self, data_size, stride, **kwargs):
Returns
-------
pandas.DataFrame
A Pandas DataFrame with three columns describing the x,y,z mesh
cell indices corresponding to each filter bin. The number of rows
in the DataFrame is the same as the total number of bins in the
A Pandas DataFrame with columns describing the mesh cell indices
corresponding to each filter bin, plus a surface column. Column
names depend on the mesh type (e.g., x/y/z for RegularMesh, r/phi/z
for CylindricalMesh, r/theta/phi for SphericalMesh). The number of
rows in the DataFrame is the same as the total number of bins in the
corresponding tally, with the filter bin appropriately tiled to map
to the corresponding tally bins.

Expand All @@ -1329,34 +1341,24 @@ def get_pandas_dataframe(self, data_size, stride, **kwargs):
# 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

# 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)
# Number of surface-crossing bins per mesh element
dims = self.mesh.dimension
n_surfs = 4 * len(dims)

# 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)
# Surface-crossing names derived from the mesh's axis labels
current_names = _mesh_current_names(self.mesh)

# 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 a multi-index sub-column for each axis, using the mesh's own
# axis labels so curvilinear meshes are labeled correctly.
axis_stride = n_surfs * stride
for label, dim_size in zip(self.mesh.axis_labels, dims):
filter_dict[mesh_key, label] = _repeat_and_tile(
np.arange(1, dim_size + 1), axis_stride, data_size)
axis_stride *= dim_size

# Generate multi-index sub-column for surface
filter_dict[mesh_key, 'surf'] = _repeat_and_tile(
_CURRENT_NAMES[:n_surfs], stride, data_size)
current_names[:n_surfs], stride, data_size)

# Initialize a Pandas DataFrame from the mesh dictionary
return pd.concat([df, pd.DataFrame(filter_dict)])
Expand Down
28 changes: 22 additions & 6 deletions openmc/mesh.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,12 @@ def indices(self):
def n_elements(self):
pass

@property
@abstractmethod
def axis_labels(self):
"""tuple of str : Names of the mesh axes, one per dimension."""
pass

def __repr__(self):
string = type(self).__name__ + '\n'
string += '{0: <16}{1}{2}\n'.format('\tID', '=\t', self._id)
Expand Down Expand Up @@ -538,7 +544,8 @@ def n_dimension(self):

@property
@abstractmethod
def _axis_labels(self):
def axis_labels(self):
"""tuple of str : Names of the mesh axes, one per dimension."""
pass

@property
Expand Down Expand Up @@ -1042,6 +1049,9 @@ class RegularMesh(StructuredMesh):
The number of mesh cells in each direction (x, y, z).
n_dimension : int
Number of mesh dimensions.
axis_labels : tuple of str
Names of the mesh axes ('x', 'y', 'z'), truncated to the mesh
dimensionality.
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.
Expand Down Expand Up @@ -1085,7 +1095,7 @@ def n_dimension(self):
return None

@property
def _axis_labels(self):
def axis_labels(self):
return ('x', 'y', 'z')[:self.n_dimension]

@property
Expand Down Expand Up @@ -1569,6 +1579,8 @@ class RectilinearMesh(StructuredMesh):
The number of mesh cells in each direction (x, y, z).
n_dimension : int
Number of mesh dimensions (always 3 for a RectilinearMesh).
axis_labels : tuple of str
Names of the mesh axes ('x', 'y', 'z').
x_grid : numpy.ndarray
1-D array of mesh boundary points along the x-axis.
y_grid : numpy.ndarray
Expand Down Expand Up @@ -1602,7 +1614,7 @@ def n_dimension(self):
return 3

@property
def _axis_labels(self):
def axis_labels(self):
return ('x', 'y', 'z')

@property
Expand Down Expand Up @@ -1870,6 +1882,8 @@ class CylindricalMesh(StructuredMesh):
The number of mesh cells in each direction (r_grid, phi_grid, z_grid).
n_dimension : int
Number of mesh dimensions (always 3 for a CylindricalMesh).
axis_labels : tuple of str
Names of the mesh axes ('r', 'phi', 'z').
r_grid : numpy.ndarray
1-D array of mesh boundary points along the r-axis.
Requirement is r >= 0.
Expand Down Expand Up @@ -1924,7 +1938,7 @@ def n_dimension(self):
return 3

@property
def _axis_labels(self):
def axis_labels(self):
return ('r', 'phi', 'z')

@property
Expand Down Expand Up @@ -2307,6 +2321,8 @@ class SphericalMesh(StructuredMesh):
theta_grid, phi_grid).
n_dimension : int
Number of mesh dimensions (always 3 for a SphericalMesh).
axis_labels : tuple of str
Names of the mesh axes ('r', 'theta', 'phi').
r_grid : numpy.ndarray
1-D array of mesh boundary points along the r-axis.
Requirement is r >= 0.
Expand Down Expand Up @@ -2361,7 +2377,7 @@ def n_dimension(self):
return 3

@property
def _axis_labels(self):
def axis_labels(self):
return ('r', 'theta', 'phi')

@property
Expand Down Expand Up @@ -2949,7 +2965,7 @@ def n_dimension(self):
return 3

@property
def _axis_labels(self):
def axis_labels(self):
return ('element_index',)

@property
Expand Down
2 changes: 1 addition & 1 deletion openmc/mgxs/mdgxs.py
Original file line number Diff line number Diff line change
Expand Up @@ -877,7 +877,7 @@ def get_pandas_dataframe(self, groups='all', nuclides='all',
# energy groups such that data is from fast to thermal
if self.domain_type == 'mesh':
mesh_str = f'mesh {self.domain.id}'
mesh_cols = [(mesh_str, label) for label in self.domain._axis_labels]
mesh_cols = [(mesh_str, label) for label in self.domain.axis_labels]
df.sort_values(by=mesh_cols + columns, inplace=True)
else:
df.sort_values(by=[self.domain_type] + columns, inplace=True)
Expand Down
2 changes: 1 addition & 1 deletion openmc/mgxs/mgxs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2134,7 +2134,7 @@ def get_pandas_dataframe(self, groups='all', nuclides='all',
# energy groups such that data is from fast to thermal
if self.domain_type == 'mesh':
mesh_str = f'mesh {self.domain.id}'
mesh_cols = [(mesh_str, label) for label in self.domain._axis_labels]
mesh_cols = [(mesh_str, label) for label in self.domain.axis_labels]
df.sort_values(by=mesh_cols + columns, inplace=True)
else:
df.sort_values(by=[self.domain_type] + columns, inplace=True)
Expand Down
29 changes: 29 additions & 0 deletions src/mesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,25 @@ vector<double> Mesh::volumes() const
return volumes;
}

//! Default (Cartesian) axis labels used for surface bin labels.
std::array<const char*, 3> Mesh::axis_labels() const
{
return {"x", "y", "z"};
}

//! Build the surface component of a mesh surface tally bin label.
//! surf_index: 0=out/min, 1=in/min, 2=out/max, 3=in/max
std::string Mesh::surface_bin_label(int surf_index) const
{
auto labels = this->axis_labels();
int dim = surf_index / 4;
int code = surf_index % 4;
bool incoming = (code == 1) || (code == 3);
bool max = (code == 2) || (code == 3);
return fmt::format(" {}, {}-{}", incoming ? "Incoming" : "Outgoing",
labels[dim], max ? "max" : "min");
}

void Mesh::material_volumes(int nx, int ny, int nz, int table_size,
int32_t* materials, double* volumes) const
{
Expand Down Expand Up @@ -1815,6 +1834,11 @@ std::string CylindricalMesh::get_mesh_type() const
return mesh_type;
}

std::array<const char*, 3> CylindricalMesh::axis_labels() const
{
return {"r", "phi", "z"};
}

StructuredMesh::MeshIndex CylindricalMesh::get_indices(
Position r, bool& in_mesh) const
{
Expand Down Expand Up @@ -2108,6 +2132,11 @@ std::string SphericalMesh::get_mesh_type() const
return mesh_type;
}

std::array<const char*, 3> SphericalMesh::axis_labels() const
{
return {"r", "theta", "phi"};
}

StructuredMesh::MeshIndex SphericalMesh::get_indices(
Position r, bool& in_mesh) const
{
Expand Down
Loading
Loading