Repository: openmc-dev/openmc

Issue:
[Feature request] Non-uniform cylindrical meshes
Those would be convenient to generate MGXS for 2D-RZ calculations mostly. Those could remain common for cylindrical reactors with highly heterogeneous geometries (TMSR, PB-FHR for example).
Also for tokamaks?


Discussion:
I’d agree with this. Came across the need for this in a particular reactor analysis I’m working on now. The model of interest has R-Z symmetry that such a mesh is best suited for.

From: Giud <notifications@github.com>
Sent: Saturday, September 5, 2020 11:12 AM
To: openmc-dev/openmc <openmc@noreply.github.com>
Cc: Subscribed <subscribed@noreply.github.com>
Subject: [openmc-dev/openmc] [Feature request] Rectilinear cylindrical meshes (#1653)


Those would be convenient to generate MGXS for 2D-RZ calculations mostly. Those could remain common for cylindrical reactors with highly heterogeneous geometries (TMSR, PB-FHR for example).

—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub<https://github.com/openmc-dev/openmc/issues/1653>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AAH5GM5PRVFQAUKUIYHZ4ODSEJPNPANCNFSM4Q3CERBA>.


Resolving patch:
diff --git a/openmc/lib/mesh.py b/openmc/lib/mesh.py
--- a/openmc/lib/mesh.py
+++ b/openmc/lib/mesh.py
@@ -11,7 +11,7 @@
 from .core import _FortranObjectWithID
 from .error import _error_handler
 
-__all__ = ['RegularMesh', 'RectilinearMesh', 'meshes']
+__all__ = ['RegularMesh', 'RectilinearMesh', 'CylindricalMesh', 'SphericalMesh', 'meshes']
 
 # Mesh functions
 _dll.openmc_extend_meshes.argtypes = [c_int32, c_char_p, POINTER(c_int32),
@@ -56,6 +56,26 @@
 _dll.openmc_regular_mesh_set_params.restype = c_int
 _dll.openmc_regular_mesh_set_params.errcheck = _error_handler
 
+_dll.openmc_cylindrical_mesh_get_grid.argtypes = [c_int32,
+    POINTER(POINTER(c_double)), POINTER(c_int), POINTER(POINTER(c_double)),
+    POINTER(c_int), POINTER(POINTER(c_double)), POINTER(c_int)]
+_dll.openmc_cylindrical_mesh_get_grid.restype = c_int
+_dll.openmc_cylindrical_mesh_get_grid.errcheck = _error_handler
+_dll.openmc_cylindrical_mesh_set_grid.argtypes = [c_int32, POINTER(c_double),
+    c_int, POINTER(c_double), c_int, POINTER(c_double), c_int]
+_dll.openmc_cylindrical_mesh_set_grid.restype = c_int
+_dll.openmc_cylindrical_mesh_set_grid.errcheck = _error_handler
+
+_dll.openmc_spherical_mesh_get_grid.argtypes = [c_int32,
+    POINTER(POINTER(c_double)), POINTER(c_int), POINTER(POINTER(c_double)),
+    POINTER(c_int), POINTER(POINTER(c_double)), POINTER(c_int)]
+_dll.openmc_spherical_mesh_get_grid.restype = c_int
+_dll.openmc_spherical_mesh_get_grid.errcheck = _error_handler
+_dll.openmc_spherical_mesh_set_grid.argtypes = [c_int32, POINTER(c_double),
+    c_int, POINTER(c_double), c_int, POINTER(c_double), c_int]
+_dll.openmc_spherical_mesh_set_grid.restype = c_int
+_dll.openmc_spherical_mesh_set_grid.errcheck = _error_handler
+
 
 class Mesh(_FortranObjectWithID):
     """Base class to represent mesh objects
@@ -275,9 +295,184 @@ def set_grid(self, x_grid, y_grid, z_grid):
                                               ny, z_grid, nz)
 
 
+class CylindricalMesh(Mesh):
+    """CylindricalMesh 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
+    dimension : iterable of int
+        The number of mesh cells in each direction.
+    lower_left : numpy.ndarray
+        The lower-left corner of the structured mesh.
+    upper_right : numpy.ndarray
+        The upper-right corner of the structrued mesh.
+    width : numpy.ndarray
+        The width of mesh cells in each direction.
+
+    """
+    mesh_type = 'cylindrical'
+
+    def __init__(self, uid=None, new=True, index=None):
+        super().__init__(uid, new, index)
+
+    @property
+    def lower_left(self):
+        return self._get_parameters()[0]
+
+    @property
+    def upper_right(self):
+        return self._get_parameters()[1]
+
+    @property
+    def dimension(self):
+        return self._get_parameters()[2]
+
+    @property
+    def width(self):
+        return self._get_parameters()[3]
+
+    def _get_parameters(self):
+        gx = POINTER(c_double)()
+        nx = c_int()
+        gy = POINTER(c_double)()
+        ny = c_int()
+        gz = POINTER(c_double)()
+        nz = c_int()
+        # Call C API to get grid parameters
+        _dll.openmc_cylindrical_mesh_get_grid(self._index, gx, nx, gy, ny, gz,
+                                              nz)
+
+        # Convert grid parameters to Numpy arrays
+        grid_x = as_array(gx, (nx.value,))
+        grid_y = as_array(gy, (ny.value,))
+        grid_z = as_array(gz, (nz.value,))
+
+        # Calculate lower_left, upper_right, width, and dimension from grid
+        lower_left = np.array((grid_x[0], grid_y[0], grid_z[0]))
+        upper_right = np.array((grid_x[-1], grid_y[-1], grid_z[-1]))
+        dimension = np.array((nx.value - 1, ny.value - 1, nz.value - 1))
+        width = np.zeros(list(dimension) + [3])
+
+        for i, diff_x in enumerate(np.diff(grid_x)):
+            for j, diff_y in enumerate(np.diff(grid_y)):
+                for k, diff_z in enumerate(np.diff(grid_z)):
+                    width[i, j, k, :] = diff_x, diff_y, diff_z
+
+        return (lower_left, upper_right, dimension, width)
+
+    def set_grid(self, x_grid, y_grid, z_grid):
+        nx = len(x_grid)
+        x_grid = (c_double*nx)(*x_grid)
+        ny = len(y_grid)
+        y_grid = (c_double*ny)(*y_grid)
+        nz = len(z_grid)
+        z_grid = (c_double*nz)(*z_grid)
+        _dll.openmc_cylindrical_mesh_set_grid(self._index, x_grid, nx, y_grid,
+                                              ny, z_grid, nz)                                              
+
+class SphericalMesh(Mesh):
+    """SphericalMesh 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
+    dimension : iterable of int
+        The number of mesh cells in each direction.
+    lower_left : numpy.ndarray
+        The lower-left corner of the structured mesh.
+    upper_right : numpy.ndarray
+        The upper-right corner of the structrued mesh.
+    width : numpy.ndarray
+        The width of mesh cells in each direction.
+
+    """
+    mesh_type = 'spherical'
+
+    def __init__(self, uid=None, new=True, index=None):
+        super().__init__(uid, new, index)
+
+    @property
+    def lower_left(self):
+        return self._get_parameters()[0]
+
+    @property
+    def upper_right(self):
+        return self._get_parameters()[1]
+
+    @property
+    def dimension(self):
+        return self._get_parameters()[2]
+
+    @property
+    def width(self):
+        return self._get_parameters()[3]
+
+    def _get_parameters(self):
+        gx = POINTER(c_double)()
+        nx = c_int()
+        gy = POINTER(c_double)()
+        ny = c_int()
+        gz = POINTER(c_double)()
+        nz = c_int()
+        # Call C API to get grid parameters
+        _dll.openmc_spherical_mesh_get_grid(self._index, gx, nx, gy, ny, gz,
+                                              nz)
+
+        # Convert grid parameters to Numpy arrays
+        grid_x = as_array(gx, (nx.value,))
+        grid_y = as_array(gy, (ny.value,))
+        grid_z = as_array(gz, (nz.value,))
+
+        # Calculate lower_left, upper_right, width, and dimension from grid
+        lower_left = np.array((grid_x[0], grid_y[0], grid_z[0]))
+        upper_right = np.array((grid_x[-1], grid_y[-1], grid_z[-1]))
+        dimension = np.array((nx.value - 1, ny.value - 1, nz.value - 1))
+        width = np.zeros(list(dimension) + [3])
+
+        for i, diff_x in enumerate(np.diff(grid_x)):
+            for j, diff_y in enumerate(np.diff(grid_y)):
+                for k, diff_z in enumerate(np.diff(grid_z)):
+                    width[i, j, k, :] = diff_x, diff_y, diff_z
+
+        return (lower_left, upper_right, dimension, width)
+
+    def set_grid(self, x_grid, y_grid, z_grid):
+        nx = len(x_grid)
+        x_grid = (c_double*nx)(*x_grid)
+        ny = len(y_grid)
+        y_grid = (c_double*ny)(*y_grid)
+        nz = len(z_grid)
+        z_grid = (c_double*nz)(*z_grid)
+        _dll.openmc_spherical_mesh_set_grid(self._index, x_grid, nx, y_grid,
+                                              ny, z_grid, nz)                                              
+
+
 _MESH_TYPE_MAP = {
     'regular': RegularMesh,
-    'rectilinear': RectilinearMesh
+    'rectilinear': RectilinearMesh,
+    'cylindrical': CylindricalMesh,
+    'spherical': SphericalMesh
 }
 
 
diff --git a/openmc/mesh.py b/openmc/mesh.py
--- a/openmc/mesh.py
+++ b/openmc/mesh.py
@@ -79,6 +79,10 @@ def from_hdf5(cls, group):
             return RegularMesh.from_hdf5(group)
         elif mesh_type == 'rectilinear':
             return RectilinearMesh.from_hdf5(group)
+        elif mesh_type == 'cylindrical':
+            return CylindricalMesh.from_hdf5(group)
+        elif mesh_type == 'spherical':
+            return SphericalMesh.from_hdf5(group)
         elif mesh_type == 'unstructured':
             return UnstructuredMesh.from_hdf5(group)
         else:
@@ -624,6 +628,334 @@ def to_xml_element(self):
 
         return element
 
+class CylindricalMesh(MeshBase):
+    """A 3D cylindrical mesh
+
+    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
+    dimension : Iterable of int
+        The number of mesh cells in each direction.
+    n_dimension : int
+        Number of mesh dimensions (always 3 for a CylindricalMesh).
+    r_grid : Iterable of float
+        Mesh boundary points along the r-axis. 
+        Requirement is r >= 0.
+    phi_grid : Iterable of float
+        Mesh boundary points along the phi-axis. 
+        The default value is [0, 360], i.e. the full phi range.
+    z_grid : Iterable of float
+        Mesh boundary points along the z-axis.
+    indices : Iterable of tuple
+        An iterable of mesh indices for each mesh element, e.g. [(1, 1, 1),
+        (2, 1, 1), ...]
+
+    """
+
+    def __init__(self, mesh_id=None, name=''):
+        super().__init__(mesh_id, name)
+
+        self._r_grid = None
+        self._phi_grid = [0, 360]
+        self._z_grid = None
+
+    @property
+    def dimension(self):
+        return (len(self.r_grid) - 1,
+                len(self.phi_grid) - 1,
+                len(self.z_grid) - 1)
+
+    @property
+    def n_dimension(self):
+        return 3
+
+    @property
+    def r_grid(self):
+        return self._r_grid
+
+    @property
+    def phi_grid(self):
+        return self._phi_grid
+
+    @property
+    def z_grid(self):
+        return self._z_grid
+
+    @property
+    def indices(self):
+        nr, np, nz = self.dimension
+        np = len(self.phi_grid) - 1
+        nz = len(self.z_grid) - 1
+        return ((r, p, z)
+                for z in range(1, nz + 1)
+                for p in range(1, np + 1)
+                for r in range(1, nr + 1))
+
+    @r_grid.setter
+    def r_grid(self, grid):
+        cv.check_type('mesh r_grid', grid, Iterable, Real)
+        self._r_grid = grid
+
+    @phi_grid.setter
+    def phi_grid(self, grid):
+        cv.check_type('mesh phi_grid', grid, Iterable, Real)
+        self._phi_grid = np.array(grid)
+
+    @z_grid.setter
+    def z_grid(self, grid):
+        cv.check_type('mesh z_grid', grid, Iterable, Real)
+        self._z_grid = np.array(grid)
+
+    def __repr__(self):
+        fmt = '{0: <16}{1}{2}\n'
+        string = super().__repr__()
+        string += fmt.format('\tDimensions', '=\t', self.n_dimension)
+        r_grid_str = str(self._r_grid) if self._r_grid is None else len(self._r_grid)
+        string += fmt.format('\tN R pnts:', '=\t', r_grid_str)
+        if self._r_grid is not None:
+            string += fmt.format('\tR Min:', '=\t', self._r_grid[0])
+            string += fmt.format('\tR Max:', '=\t', self._r_grid[-1])
+        phi_grid_str = str(self._phi_grid) if self._phi_grid is None else len(self._phi_grid)
+        string += fmt.format('\tN Phi pnts:', '=\t', phi_grid_str)
+        if self._phi_grid is not None:
+            string += fmt.format('\tPhi Min:', '=\t', self._phi_grid[0])
+            string += fmt.format('\tPhi Max:', '=\t', self._phi_grid[-1])
+        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):
+        mesh_id = int(group.name.split('/')[-1].lstrip('mesh '))
+
+        # Read and assign mesh properties
+        mesh = cls(mesh_id)
+        mesh.r_grid = group['r_grid'][()]
+        mesh.phi_grid = 180 / np.pi * group['phi_grid'][()]
+        mesh.z_grid = group['z_grid'][()]
+
+        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", "cylindrical")
+
+        subelement = ET.SubElement(element, "r_grid")
+        subelement.text = ' '.join(map(str, self.r_grid))
+
+        subelement = ET.SubElement(element, "phi_grid")
+        subelement.text = ' '.join(map(str, self.phi_grid))
+
+        subelement = ET.SubElement(element, "z_grid")
+        subelement.text = ' '.join(map(str, self.z_grid))
+
+        return element
+
+    def calc_mesh_volumes(self):
+        """Return Volumes for every mesh cell
+
+        Returns
+        -------
+        volumes : Iterable of float
+            Volumes
+
+        """
+
+        V_r = np.diff(np.array(self.r_grid)**2 / 2)
+        V_p = np.diff(np.array(self.phi_grid) * np.pi / 180.0)
+        V_z = np.diff(np.array(self.z_grid))
+
+        return np.multiply.outer(np.outer(V_r, V_p), V_z)
+
+
+class SphericalMesh(MeshBase):
+    """A 3D spherical mesh
+
+    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
+    dimension : Iterable of int
+        The number of mesh cells in each direction.
+    n_dimension : int
+        Number of mesh dimensions (always 3 for a SphericalMesh).
+    r_grid : Iterable of float
+        Mesh boundary points along the r-axis.
+        Requirement is r >= 0.
+    theta_grid : Iterable of float
+        Mesh boundary points along the theta-axis in degrees.
+        The default value is [0, 180], i.e. the full theta range.
+    phi_grid : Iterable of float
+        Mesh boundary points along the phi-axis in degrees.
+        The default value is [0, 360], i.e. the full phi range.
+    indices : Iterable of tuple
+        An iterable of mesh indices for each mesh element, e.g. [(1, 1, 1),
+        (2, 1, 1), ...]
+
+    """
+
+    def __init__(self, mesh_id=None, name=''):
+        super().__init__(mesh_id, name)
+
+        self._r_grid = None
+        self._theta_grid = [0, 180]
+        self._phi_grid = [0, 360]
+
+    @property
+    def dimension(self):
+        return (len(self.r_grid) - 1,
+                len(self.theta_grid) - 1,
+                len(self.phi_grid) - 1)
+
+    @property
+    def n_dimension(self):
+        return 3
+
+    @property
+    def r_grid(self):
+        return self._r_grid
+
+    @property
+    def theta_grid(self):
+        return self._theta_grid
+
+    @property
+    def phi_grid(self):
+        return self._phi_grid
+
+    @property
+    def indices(self):
+        nr, nt, np = self.dimension
+        nt = len(self.theta_grid) - 1
+        np = len(self.phi_grid) - 1
+        return ((r, t, p)
+                for p in range(1, np + 1)
+                for t in range(1, nt + 1)
+                for r in range(1, nr + 1))
+
+    @r_grid.setter
+    def r_grid(self, grid):
+        cv.check_type('mesh r_grid', grid, Iterable, Real)
+        self._r_grid = grid
+
+    @theta_grid.setter
+    def theta_grid(self, grid):
+        cv.check_type('mesh theta_grid', grid, Iterable, Real)
+        self._theta_grid = np.array(grid)
+
+    @phi_grid.setter
+    def phi_grid(self, grid):
+        cv.check_type('mesh phi_grid', grid, Iterable, Real)
+        self._phi_grid = np.array(grid)
+
+    def __repr__(self):
+        fmt = '{0: <16}{1}{2}\n'
+        string = super().__repr__()
+        string += fmt.format('\tDimensions', '=\t', self.n_dimension)
+        r_grid_str = str(self._r_grid) if not self._r_grid else len(self._r_grid)
+        string += fmt.format('\tN R pnts:', '=\t', r_grid_str)
+        if self._r_grid:
+            string += fmt.format('\tR Min:', '=\t', self._r_grid[0])
+            string += fmt.format('\tR Max:', '=\t', self._r_grid[-1])
+        theta_grid_str = str(self._theta_grid) if not self._theta_grid else len(self._theta_grid)
+        string += fmt.format('\tN Theta pnts:', '=\t', theta_grid_str)
+        if self._theta_grid:
+            string += fmt.format('\tTheta Min:', '=\t', self._theta_grid[0])
+            string += fmt.format('\tTheta Max:', '=\t', self._theta_grid[-1])
+        phi_grid_str = str(self._phi_grid) if not self._phi_grid else len(self._phi_grid)
+        string += fmt.format('\tN Phi pnts:', '=\t', phi_grid_str)
+        if self._phi_grid:
+            string += fmt.format('\tPhi Min:', '=\t', self._phi_grid[0])
+            string += fmt.format('\tPhi Max:', '=\t', self._phi_grid[-1])
+        return string
+
+    @classmethod
+    def from_hdf5(cls, group):
+        mesh_id = int(group.name.split('/')[-1].lstrip('mesh '))
+
+        # Read and assign mesh properties
+        mesh = cls(mesh_id)
+        mesh.r_grid = group['r_grid'][()]
+        mesh.theta_grid = 180 / np.pi * group['theta_grid'][()]
+        mesh.phi_grid = 180 / np.pi * group['phi_grid'][()]
+
+        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", "spherical")
+
+        subelement = ET.SubElement(element, "r_grid")
+        subelement.text = ' '.join(map(str, self.r_grid))
+
+        subelement = ET.SubElement(element, "theta_grid")
+        subelement.text = ' '.join(map(str, self.theta_grid))
+
+        subelement = ET.SubElement(element, "phi_grid")
+        subelement.text = ' '.join(map(str, self.phi_grid))
+
+        return element
+
+    def calc_mesh_volumes(self):
+        """Return Volumes for every mesh cell
+
+        Returns
+        -------
+        volumes : Iterable of float
+            Volumes
+
+        """
+
+        V_r = np.diff(np.array(self.r_grid)**3 / 3)
+        V_t = np.diff(-np.cos(np.pi * np.array(self.theta_grid) / 180.0))
+        V_p = np.diff(np.array(self.phi_grid) * np.pi / 180)
+
+        return np.multiply.outer(np.outer(V_r, V_t), V_p)
+
+
+
 
 class UnstructuredMesh(MeshBase):
     """A 3D unstructured mesh


Test patch:
diff --git a/tests/regression_tests/filter_mesh/inputs_true.dat b/tests/regression_tests/filter_mesh/inputs_true.dat
--- a/tests/regression_tests/filter_mesh/inputs_true.dat
+++ b/tests/regression_tests/filter_mesh/inputs_true.dat
@@ -53,36 +53,58 @@
     <y_grid>-7.5 -6.617647058823529 -5.735294117647059 -4.852941176470589 -3.9705882352941178 -3.0882352941176467 -2.2058823529411766 -1.3235294117647065 -0.4411764705882355 0.4411764705882355 1.3235294117647065 2.2058823529411757 3.0882352941176467 3.9705882352941178 4.852941176470587 5.735294117647058 6.617647058823529 7.5</y_grid>
     <z_grid>1.0 1.223224374241637 1.4962778697388448 1.8302835609029084 2.2388474634702153 2.7386127875258306 3.3499379133114306 4.09772570775871 5.012437964687018 6.131336292779302 7.500000000000001</z_grid>
   </mesh>
+  <mesh id="5" type="cylindrical">
+    <r_grid>0.0 0.4411764705882353 0.8823529411764706 1.3235294117647058 1.7647058823529411 2.2058823529411766 2.6470588235294117 3.0882352941176467 3.5294117647058822 3.9705882352941178 4.411764705882353 4.852941176470588 5.294117647058823 5.735294117647059 6.1764705882352935 6.617647058823529 7.0588235294117645 7.5</r_grid>
+    <phi_grid>0.0 20.0 40.0 60.0 80.0 100.0 120.0 140.0 160.0 180.0 200.0 220.0 240.0 260.0 280.0 300.0 320.0 340.0 360.0</phi_grid>
+    <z_grid>-7.5 -6.5625 -5.625 -4.6875 -3.75 -2.8125 -1.875 -0.9375 0.0 0.9375 1.875 2.8125 3.75 4.6875 5.625 6.5625 7.5</z_grid>
+  </mesh>
+  <mesh id="6" type="spherical">
+    <r_grid>0.0 0.4411764705882353 0.8823529411764706 1.3235294117647058 1.7647058823529411 2.2058823529411766 2.6470588235294117 3.0882352941176467 3.5294117647058822 3.9705882352941178 4.411764705882353 4.852941176470588 5.294117647058823 5.735294117647059 6.1764705882352935 6.617647058823529 7.0588235294117645 7.5</r_grid>
+    <theta_grid>0.0 22.5 45.0 67.5 90.0 112.5 135.0 157.5 180.0</theta_grid>
+    <phi_grid>0.0 20.0 40.0 60.0 80.0 100.0 120.0 140.0 160.0 180.0 200.0 220.0 240.0 260.0 280.0 300.0 320.0 340.0 360.0</phi_grid>
+  </mesh>
   <filter id="1" type="mesh">
     <bins>1</bins>
   </filter>
-  <filter id="5" type="meshsurface">
+  <filter id="7" type="meshsurface">
     <bins>1</bins>
   </filter>
   <filter id="2" type="mesh">
     <bins>2</bins>
   </filter>
-  <filter id="6" type="meshsurface">
+  <filter id="8" type="meshsurface">
     <bins>2</bins>
   </filter>
   <filter id="3" type="mesh">
     <bins>3</bins>
   </filter>
-  <filter id="7" type="meshsurface">
+  <filter id="9" type="meshsurface">
     <bins>3</bins>
   </filter>
   <filter id="4" type="mesh">
     <bins>4</bins>
   </filter>
-  <filter id="8" type="meshsurface">
+  <filter id="10" type="meshsurface">
     <bins>4</bins>
   </filter>
+  <filter id="5" type="mesh">
+    <bins>5</bins>
+  </filter>
+  <filter id="11" type="meshsurface">
+    <bins>5</bins>
+  </filter>
+  <filter id="6" type="mesh">
+    <bins>6</bins>
+  </filter>
+  <filter id="12" type="meshsurface">
+    <bins>6</bins>
+  </filter>
   <tally id="1">
     <filters>1</filters>
     <scores>total</scores>
   </tally>
   <tally id="2">
-    <filters>5</filters>
+    <filters>7</filters>
     <scores>current</scores>
   </tally>
   <tally id="3">
@@ -90,7 +112,7 @@
     <scores>total</scores>
   </tally>
   <tally id="4">
-    <filters>6</filters>
+    <filters>8</filters>
     <scores>current</scores>
   </tally>
   <tally id="5">
@@ -98,7 +120,7 @@
     <scores>total</scores>
   </tally>
   <tally id="6">
-    <filters>7</filters>
+    <filters>9</filters>
     <scores>current</scores>
   </tally>
   <tally id="7">
@@ -106,7 +128,23 @@
     <scores>total</scores>
   </tally>
   <tally id="8">
-    <filters>8</filters>
+    <filters>10</filters>
+    <scores>current</scores>
+  </tally>
+  <tally id="9">
+    <filters>5</filters>
+    <scores>total</scores>
+  </tally>
+  <tally id="10">
+    <filters>11</filters>
+    <scores>current</scores>
+  </tally>
+  <tally id="11">
+    <filters>6</filters>
+    <scores>total</scores>
+  </tally>
+  <tally id="12">
+    <filters>12</filters>
     <scores>current</scores>
   </tally>
 </tallies>
diff --git a/tests/regression_tests/filter_mesh/results_true.dat b/tests/regression_tests/filter_mesh/results_true.dat
--- a/tests/regression_tests/filter_mesh/results_true.dat
+++ b/tests/regression_tests/filter_mesh/results_true.dat
@@ -1 +1 @@
-97cf099a24099d2af794752e145841a9ef9f24980c53ee71e3fdd400d4249a7e6f3f52f63132fe2fe8d1973604a72d87910ab13bc3721f7d0bec533b87756847
\ No newline at end of file
+6e02d01fc115c54e4c60477a9f963149bc83c82e6a7bdb2d29125b131546150c59de5ac3aae5b9469b89b343ecf2574f6c949b918ccb43231e8666ee2b7c1b7c
\ No newline at end of file
diff --git a/tests/regression_tests/filter_mesh/test.py b/tests/regression_tests/filter_mesh/test.py
--- a/tests/regression_tests/filter_mesh/test.py
+++ b/tests/regression_tests/filter_mesh/test.py
@@ -51,18 +51,32 @@ def model():
     recti_mesh.y_grid = np.linspace(-7.5, 7.5, 18)
     recti_mesh.z_grid = np.logspace(0, np.log10(7.5), 11)
 
+    cyl_mesh = openmc.CylindricalMesh()
+    cyl_mesh.r_grid = np.linspace(0, 7.5, 18)
+    cyl_mesh.phi_grid = np.linspace(0, 360, 19)
+    cyl_mesh.z_grid = np.linspace(-7.5, 7.5, 17)
+
+    sph_mesh = openmc.SphericalMesh()
+    sph_mesh.r_grid = np.linspace(0, 7.5, 18)
+    sph_mesh.theta_grid = np.linspace(0, 180, 9)
+    sph_mesh.phi_grid = np.linspace(0, 360, 19)
+
     # Create filters
     reg_filters = [
         openmc.MeshFilter(mesh_1d),
         openmc.MeshFilter(mesh_2d),
         openmc.MeshFilter(mesh_3d),
-        openmc.MeshFilter(recti_mesh)
+        openmc.MeshFilter(recti_mesh),
+        openmc.MeshFilter(cyl_mesh),
+        openmc.MeshFilter(sph_mesh)
     ]
     surf_filters = [
         openmc.MeshSurfaceFilter(mesh_1d),
         openmc.MeshSurfaceFilter(mesh_2d),
         openmc.MeshSurfaceFilter(mesh_3d),
-        openmc.MeshSurfaceFilter(recti_mesh)
+        openmc.MeshSurfaceFilter(recti_mesh),
+        openmc.MeshSurfaceFilter(cyl_mesh),
+        openmc.MeshSurfaceFilter(sph_mesh)
     ]
 
     # Create tallies
diff --git a/tests/regression_tests/mgxs_library_mesh/results_true.dat b/tests/regression_tests/mgxs_library_mesh/results_true.dat
--- a/tests/regression_tests/mgxs_library_mesh/results_true.dat
+++ b/tests/regression_tests/mgxs_library_mesh/results_true.dat
@@ -181,11 +181,11 @@
    mesh 1               group in nuclide    mean std. dev.
         x  y       surf                                   
 3       1  1   x-max in        1   total  0.1816  0.006129
-2       1  1  x-max out        1   total  0.2718  0.095077
+2       1  1  x-max out        1   total  0.1730  0.004382
 1       1  1   x-min in        1   total  0.0000  0.000000
 0       1  1  x-min out        1   total  0.0000  0.000000
 7       1  1   y-max in        1   total  0.1790  0.011921
-6       1  1  y-max out        1   total  0.2290  0.034821
+6       1  1  y-max out        1   total  0.1802  0.016599
 5       1  1   y-min in        1   total  0.0000  0.000000
 4       1  1  y-min out        1   total  0.0000  0.000000
 19      1  2   x-max in        1   total  0.1872  0.012447
@@ -194,23 +194,23 @@
 16      1  2  x-min out        1   total  0.0000  0.000000
 23      1  2   y-max in        1   total  0.0000  0.000000
 22      1  2  y-max out        1   total  0.0000  0.000000
-21      1  2   y-min in        1   total  0.2290  0.034821
+21      1  2   y-min in        1   total  0.1802  0.016599
 20      1  2  y-min out        1   total  0.1790  0.011921
 11      2  1   x-max in        1   total  0.0000  0.000000
 10      2  1  x-max out        1   total  0.0000  0.000000
-9       2  1   x-min in        1   total  0.2718  0.095077
+9       2  1   x-min in        1   total  0.1730  0.004382
 8       2  1  x-min out        1   total  0.1816  0.006129
 15      2  1   y-max in        1   total  0.1778  0.009484
-14      2  1  y-max out        1   total  0.2326  0.042782
+14      2  1  y-max out        1   total  0.1822  0.010077
 13      2  1   y-min in        1   total  0.0000  0.000000
 12      2  1  y-min out        1   total  0.0000  0.000000
 27      2  2   x-max in        1   total  0.0000  0.000000
-26      2  2  x-max out        1   total  0.0260  0.026000
+26      2  2  x-max out        1   total  0.0000  0.000000
 25      2  2   x-min in        1   total  0.1952  0.015948
 24      2  2  x-min out        1   total  0.1872  0.012447
 31      2  2   y-max in        1   total  0.0000  0.000000
-30      2  2  y-max out        1   total  0.0244  0.024400
-29      2  2   y-min in        1   total  0.2326  0.042782
+30      2  2  y-max out        1   total  0.0000  0.000000
+29      2  2   y-min in        1   total  0.1822  0.010077
 28      2  2  y-min out        1   total  0.1778  0.009484
   mesh 1       group in nuclide      mean std. dev.
        x  y  z                                     
diff --git a/tests/unit_tests/test_lib.py b/tests/unit_tests/test_lib.py
--- a/tests/unit_tests/test_lib.py
+++ b/tests/unit_tests/test_lib.py
@@ -580,6 +580,68 @@ def test_rectilinear_mesh(lib_init):
     msf = openmc.lib.MeshSurfaceFilter(mesh)
     assert msf.mesh == mesh
 
+def test_cylindrical_mesh(lib_init):
+    deg2rad = lambda deg: deg*np.pi/180
+    mesh = openmc.lib.CylindricalMesh()
+    x_grid = [0., 5., 10.]
+    y_grid = [0., 10., 20.]
+    z_grid = [10., 20., 30.]
+    mesh.set_grid(x_grid, y_grid, z_grid)
+    assert np.all(mesh.lower_left == (0., 0., 10.))
+    assert np.all(mesh.upper_right == (10., deg2rad(20.), 30.))
+    assert np.all(mesh.dimension == (2, 2, 2))
+    for i, diff_x in enumerate(np.diff(x_grid)):
+        for j, diff_y in enumerate(np.diff(y_grid)):
+            for k, diff_z in enumerate(np.diff(z_grid)):
+                assert np.all(mesh.width[i, j, k, :] == (5, deg2rad(10), 10))
+
+    with pytest.raises(exc.AllocationError):
+        mesh2 = openmc.lib.CylindricalMesh(mesh.id)
+
+    meshes = openmc.lib.meshes
+    assert isinstance(meshes, Mapping)
+    assert len(meshes) == 3
+
+    mesh = meshes[mesh.id]
+    assert isinstance(mesh, openmc.lib.CylindricalMesh)
+
+    mf = openmc.lib.MeshFilter(mesh)
+    assert mf.mesh == mesh
+
+    msf = openmc.lib.MeshSurfaceFilter(mesh)
+    assert msf.mesh == mesh
+
+def test_spherical_mesh(lib_init):
+    deg2rad = lambda deg: deg*np.pi/180
+    mesh = openmc.lib.SphericalMesh()
+    x_grid = [0., 5., 10.]
+    y_grid = [0., 10., 20.]
+    z_grid = [10., 20., 30.]
+    mesh.set_grid(x_grid, y_grid, z_grid)
+    assert np.all(mesh.lower_left == (0., 0., deg2rad(10.)))
+    assert np.all(mesh.upper_right == (10., deg2rad(20.), deg2rad(30.)))
+    assert np.all(mesh.dimension == (2, 2, 2))
+    for i, diff_x in enumerate(np.diff(x_grid)):
+        for j, diff_y in enumerate(np.diff(y_grid)):
+            for k, diff_z in enumerate(np.diff(z_grid)):
+                assert np.all(abs(mesh.width[i, j, k, :] - (5, deg2rad(10), deg2rad(10))) < 1e-16)
+
+    with pytest.raises(exc.AllocationError):
+        mesh2 = openmc.lib.SphericalMesh(mesh.id)
+
+    meshes = openmc.lib.meshes
+    assert isinstance(meshes, Mapping)
+    assert len(meshes) == 4
+
+    mesh = meshes[mesh.id]
+    assert isinstance(mesh, openmc.lib.SphericalMesh)
+
+    mf = openmc.lib.MeshFilter(mesh)
+    assert mf.mesh == mesh
+
+    msf = openmc.lib.MeshSurfaceFilter(mesh)
+    assert msf.mesh == mesh
+
 
 def test_restart(lib_init, mpi_intracomm):
     # Finalize and re-init to make internal state consistent with XML.