OpenMC Guide
Surface Types in OpenMC
What you'll learn
- Pick the right surface class for a shape, and know why axis-aligned ones are faster.
- Apply each boundary type, including the transmission default.
- Fall back to a general Plane or Quadric when no axis-aligned class fits.
- Build a finite cylinder by intersecting a cylinder with two planes.
Before you start
Introduction to Surfaces
Come back to this page once the pin cell from Example: Pin Cell needs a shape that a cylinder and two planes cannot express — a cone, a torus, a hemispherical core cap, or a general quadric.
Each surface is defined by a mathematical equation f(x,y,z) = 0 that divides space into positive and negative half-spaces. In cell definitions, the + and - operators select which side of a surface the cell occupies.
Basic Surface Concepts
Surface Equations and Half-Spaces
Every surface is an equation set equal to zero, and the sign of the left-hand side at a point tells you which side of the surface that point is on. Where f(x,y,z) is negative you are in the negative half-space, which OpenMC writes as -surface; where it is positive you are in the positive half-space, written +surface. For a sphere written as x² + y² + z² − r² = 0 this works out intuitively — inside is negative — and the same holds for cylinders. It is worth checking rather than assuming for planes and cones, where which side is "inside" is a matter of how the equation happens to be written.
Surface ID and Name
Each surface can have an optional ID (integer) and name (string):
# Creating a surface with ID and name
sphere = openmc.Sphere(r=10.0, surface_id=1, name="fuel_outer_boundary")
# IDs and names can be accessed or modified
print(sphere.id)
sphere.name = "new_name"Tutorial snippet — no separate file in examples repo
Boundary Conditions
A surface's boundary_type is what turns geometry into a physical model, because it decides what a particle does on arrival. The default is transmission: the particle crosses and carries on, which is what every interior surface should be. The two you will set deliberately are vacuum, which kills the particle and so represents everything outside the problem, and reflective, which reflects it specularly — the mirror condition that makes a single pin cell stand in for an infinite lattice.
Two more are situational. periodic translates the particle to a matching surface instead of reflecting it, which is what a genuinely repeating lattice with an asymmetric unit cell requires. white re-emits the particle in a direction sampled from a cosine distribution, discarding the memory of how it arrived.
The choice between reflective and white is where models go wrong, and it matters most on curved surfaces. Specular reflection off a cylinder conserves the impact parameter, so a neutron launched tangentially circles the annulus forever without ever crossing back into the fuel. A Wigner–Seitz equivalent cylinder therefore needs a white boundary, while a flat symmetry plane cut through a square lattice needs a reflective one.
# Setting boundary conditions
outer_surface = openmc.Sphere(r=50.0, boundary_type='vacuum')
reflective_surface = openmc.XPlane(x0=0.0, boundary_type='reflective')
white_surface = openmc.YPlane(y0=0.0, boundary_type='white')Tutorial snippet — no separate file in examples repo
Planar Surfaces
Planar surfaces are defined by equations of the form Ax + By + Cz = D, where (A,B,C) is the normal vector to the plane.
Planes Perpendicular to Axes
OpenMC provides dedicated classes for planes perpendicular to the coordinate axes:
# X-plane: x = x0
xplane = openmc.XPlane(x0=5.0) # Equation: x - 5.0 = 0
# Y-plane: y = y0
yplane = openmc.YPlane(y0=-2.0) # Equation: y + 2.0 = 0
# Z-plane: z = z0
zplane = openmc.ZPlane(z0=10.0) # Equation: z - 10.0 = 0Tutorial snippet — no separate file in examples repo
General Planes
For planes with arbitrary orientation, you can use the general Plane class:
# Plane defined by coefficients A, B, C, D in equation Ax + By + Cz = D
plane1 = openmc.Plane(a=1.0, b=1.0, c=1.0, d=5.0) # x + y + z = 5
# To define a plane from three points, compute the coefficients manually
import numpy as np
p1 = np.array([0.0, 0.0, 5.0])
p2 = np.array([1.0, 0.0, 5.0])
p3 = np.array([0.0, 1.0, 5.0])
normal = np.cross(p2 - p1, p3 - p1) # normal vector (A, B, C)
A, B, C = normal
D = np.dot(normal, p1)
plane2 = openmc.Plane(a=A, b=B, c=C, d=D)Tutorial snippet — no separate file in examples repo
Cylindrical Surfaces
Cylinders Parallel to Axes
# Cylinder parallel to the z-axis
zcyl = openmc.ZCylinder(r=0.5, x0=0.0, y0=0.0) # (x-x0)² + (y-y0)² = r²
# Cylinder parallel to the y-axis
ycyl = openmc.YCylinder(r=1.0, x0=0.0, z0=0.0) # (x-x0)² + (z-z0)² = r²
# Cylinder parallel to the x-axis
xcyl = openmc.XCylinder(r=0.8, y0=0.0, z0=0.0) # (y-y0)² + (z-z0)² = r²Tutorial snippet — no separate file in examples repo
Arbitrarily Oriented Cylinders
OpenMC has no general Cylinder surface class. For a cylinder with arbitrary orientation, either define an axis-aligned cylinder inside a universe and rotate the cell that contains it, or express the cylinder as a general Quadric surface:
# Option 1 (preferred): axis-aligned cylinder in a rotated, universe-filled cell
zcyl = openmc.ZCylinder(r=0.5)
inner_cell = openmc.Cell(fill=some_material, region=-zcyl)
tilted_univ = openmc.Universe(cells=[inner_cell])
container = openmc.Cell(fill=tilted_univ, region=container_region)
container.rotation = (0, 30, 0) # tilt 30 degrees about the y-axis
# Option 2: general Quadric surface (coefficients derived by hand)
# Ax² + By² + Cz² + Dxy + Eyz + Fxz + Gx + Hy + Jz + K = 0Tutorial snippet — no separate file in examples repo
Note: For most typical reactor geometries, the axis-aligned cylinders (XCylinder, YCylinder, ZCylinder) are more efficient and easier to use.
Spherical and Other Curved Surfaces
Spheres
Spherical surfaces are defined by the equation (x-x0)² + (y-y0)² + (z-z0)² = r², where (x0,y0,z0) is the center and r is the radius:
# Sphere centered at the origin with radius 10.0
sphere1 = openmc.Sphere(r=10.0)
# Sphere with custom center
sphere2 = openmc.Sphere(r=5.0, x0=1.0, y0=2.0, z0=3.0)Tutorial snippet — no separate file in examples repo
Cones
Conical surfaces are available for coordinate-aligned cones:
# Cone parallel to the z-axis
zcone = openmc.ZCone(r2=1.0, x0=0.0, y0=0.0, z0=0.0) # (x-x0)² + (y-y0)² = r2*(z-z0)²
# Cone parallel to the x-axis
xcone = openmc.XCone(r2=2.0, x0=0.0, y0=0.0, z0=0.0) # (y-y0)² + (z-z0)² = r2*(x-x0)²
# Cone parallel to the y-axis
ycone = openmc.YCone(r2=0.5, x0=0.0, y0=0.0, z0=0.0) # (x-x0)² + (z-z0)² = r2*(y-y0)²Tutorial snippet — no separate file in examples repo
The r2 parameter represents the square of the tangent of the cone's half-angle.
Tori (Toruses)
Toroidal surfaces can be used for more complex geometries:
# Torus parallel to the z-axis
ztorus = openmc.ZTorus(a=5.0, b=1.0, c=1.0)
# where:
# a = major radius (distance from center of torus to center of tube)
# b = minor radius of the tube in the x-y plane
# c = minor radius of the tube in the z direction
# (√((x-x0)² + (y-y0)²) - a)²/b² + (z-z0)²/c² = 1Tutorial snippet — no separate file in examples repo
Quadric and Other Advanced Surfaces
General Quadric Surface
# General quadric surface: Ax² + By² + Cz² + Dxy + Eyz + Fxz + Gx + Hy + Jz + K = 0
quadric = openmc.Quadric(a=1.0, b=1.0, c=1.0, k=-1.0) # x² + y² + z² - 1 = 0 (a sphere)
# Ellipsoid with semi-axes 2, 3, and 4
ellipsoid = openmc.Quadric(a=0.25, b=1/9, c=1/16, k=-1.0) # x²/4 + y²/9 + z²/16 = 1Tutorial snippet — no separate file in examples repo
Creating Half-Spaces
# Create a sphere
sphere = openmc.Sphere(r=10.0)
# Create the region inside the sphere (negative half-space)
inside_sphere = -sphere
# Create the region outside the sphere (positive half-space)
outside_sphere = +sphereTutorial snippet — no separate file in examples repo
Geometry Transformations
Geometric transformations (translation and rotation) are applied at the cell level, not the surface level. Define surfaces in a local coordinate system, then translate or rotate the cell.
# Define surfaces in a local coordinate system
sphere = openmc.Sphere(r=10.0)
cell = openmc.Cell(region=-sphere)
# Translate the cell so the sphere is centered at (10, 0, 0)
cell.translation = (10.0, 0.0, 0.0)
# Rotate the cell using Euler angles (phi, theta, psi) in degrees
cell.rotation = (0.0, 0.0, 45.0)Tutorial snippet — no separate file in examples repo
Tip: For repeated structures such as fuel pin lattices, it is often more convenient to use universes and lattices to position and orient geometry rather than applying individual cell transformations.
Practical Examples
Creating a Simple Fuel Pin
Here's how to create a simple fuel pin model using surfaces. Unlike the Path A pin, which starts cladding right at the pellet surface, this example carries an explicit helium gap between fuel and clad.
# Define the surfaces for a fuel pin
fuel_radius = 0.4096
clad_inner_radius = 0.4178
clad_outer_radius = 0.4750
# Create cylindrical surfaces
fuel_surface = openmc.ZCylinder(r=fuel_radius)
clad_inner_surface = openmc.ZCylinder(r=clad_inner_radius)
clad_outer_surface = openmc.ZCylinder(r=clad_outer_radius)
# Create top and bottom planes for finite height
top_plane = openmc.ZPlane(z0=10.0)
bottom_plane = openmc.ZPlane(z0=-10.0)
# Define regions
fuel_region = -fuel_surface & -top_plane & +bottom_plane
gap_region = +fuel_surface & -clad_inner_surface & -top_plane & +bottom_plane
clad_region = +clad_inner_surface & -clad_outer_surface & -top_plane & +bottom_planeTutorial snippet — no separate file in examples repo
Creating a Reactor Core Boundary
Defining a cylindrical reactor core with a hemispherical bottom:
# Define the dimensions
core_radius = 200.0
core_height = 400.0
# Create the cylindrical side
cylinder = openmc.ZCylinder(r=core_radius)
# Create the top plane
top_plane = openmc.ZPlane(z0=core_height)
# Create the hemispherical bottom
sphere = openmc.Sphere(r=core_radius, z0=0.0)
bottom_plane = openmc.ZPlane(z0=0.0)
# Define the core region
cylindrical_part = -cylinder & -top_plane & +bottom_plane
hemispherical_part = -sphere & -bottom_plane
core_region = cylindrical_part | hemispherical_partTutorial snippet — no separate file in examples repo
Choosing surfaces well
Prefer the specialized axis-aligned classes over the general ones wherever the geometry allows it. ZCylinder and Quadric can describe the same cylinder, but the first reduces the distance-to-surface calculation to a two-dimensional problem with a closed-form answer, while the second solves the general quadratic. Since that calculation happens for every surface a particle might reach, at every flight, the difference is measurable in a way that few other modeling choices are.
Surface count matters for the same reason, and it is the argument for building repeated structures out of universes and lattices rather than out of explicit surfaces. Ten thousand fuel pins written as ten thousand cylinders is a model that runs; the same core written as one pin universe placed in a lattice is a model that runs quickly, because OpenMC only ever considers the surfaces of the current lattice element.
Beyond performance, two habits save debugging time. Name surfaces that mark something physically meaningful, since a warning about surface 47 is far less useful than one about clad_outer. And when a region behaves strangely, suspect a sign before suspecting anything else — a mistaken + for a - produces a valid model of a different object, and the resulting geometry error, if there is one at all, usually appears somewhere other than where the mistake is.
Check yourself
- Pick the right surface class for a shape, and say why axis-aligned ones are faster?
- Apply each boundary type, including the transmission default?
- Build a finite cylinder by intersecting a cylinder with two planes?