Geometry and CSG Model

MC/DC uses Constructive Solid Geometry (CSG) to define the spatial domain. Complex geometries are built by combining simple surfaces with Boolean region operators.

Surfaces

A surface is a mathematical equation that divides space into a positive half-space (\(f(\mathbf{r}) > 0\)) and a negative half-space (\(f(\mathbf{r}) < 0\)). MC/DC provides the following surface types:

Surface

Equation

Constructor

Plane X

\(x - x_0 = 0\)

Surface.PlaneX(x=x0)

Plane Y

\(y - y_0 = 0\)

Surface.PlaneY(y=y0)

Plane Z

\(z - z_0 = 0\)

Surface.PlaneZ(z=z0)

Cylinder X

\((y-y_0)^2 + (z-z_0)^2 - R^2 = 0\)

Surface.CylinderX(center, radius)

Cylinder Y

\((x-x_0)^2 + (z-z_0)^2 - R^2 = 0\)

Surface.CylinderY(center, radius)

Cylinder Z

\((x-x_0)^2 + (y-y_0)^2 - R^2 = 0\)

Surface.CylinderZ(center, radius)

Sphere

\(|\mathbf{r} - \mathbf{r}_0|^2 - R^2 = 0\)

Surface.Sphere(center, radius)

Boundary conditions are set on outermost surfaces:

s = mcdc.Surface.PlaneX(x=10.0, boundary_condition="vacuum")     # particles leak
s = mcdc.Surface.PlaneX(x=0.0, boundary_condition="reflective")  # mirror reflection

Region Operators

A region (also called a half-space) is obtained by applying the unary + or - operator to a surface:

  • +s — the positive half-space (\(f > 0\)).

  • -s — the negative half-space (\(f < 0\)).

Regions are combined with Boolean operators:

Operator

Meaning

Example

&

Intersection (AND)

+s1 & -s2 — between planes s1 and s2

|

Union (OR)

region_A | region_B — either region

~

Complement (NOT)

~region_A — everything outside region_A

Operator precedence: ~ > & > |. Use parentheses for clarity.

Cells

A cell is a region filled with a material:

mcdc.Cell(region=+s1 & -s2 & +s3 & -s4, fill=material)

Every point in the problem domain must belong to exactly one cell. MC/DC does not verify non-overlapping coverage automatically — the user is responsible for ensuring consistent cell definitions.

Named cells can be used for cell-filtered tallies:

sphere_cell = mcdc.Cell(name="Fuel sphere", region=-sphere, fill=fuel)
mcdc.Cell(cell=sphere_cell, scores=["fission"])

Universes and Packaging

A universe groups a set of cells into a reusable geometry unit. Universes can be translated and rotated when placed inside a container cell, enabling duplication of complex assemblies without redefining their internal geometry.

assembly = mcdc.Universe(cells=[fuel_cell, clad_cell, water_cell])

# Place two copies with different positions and rotations
mcdc.Cell(region=left_region,  fill=assembly, translation=[-5, 0, 0])
mcdc.Cell(region=right_region, fill=assembly, translation=[+5, 0, 0],
          rotation=[0, 10, 0])

When a particle enters a universe cell, MC/DC transforms coordinates into the universe’s local frame, tracks through local surfaces, then transforms back.

For a complete example, see Packaged Fuel Array (Universe and Lattice).

Lattices

A lattice is a regular array of universes arranged on a Cartesian grid. Each grid position is mapped to a universe by an integer index.

lattice = mcdc.Lattice(
    x=[-5.0, 0.0, 5.0],   # 2 cells in x
    y=[-5.0, 0.0, 5.0],   # 2 cells in y
    universes=[[u1, u2],
               [u3, u4]],
)

Lattices are powerful for reactor-core models where fuel assemblies repeat in a regular pattern. See the C5G7 — k-eigenvalue example for a full-core lattice example.

Root Universe

When using universes or lattices, the top-level cell collection is registered as the root universe:

mcdc.simulation.set_root_universe(cells=[cell_left, cell_right])

Geometry Visualization

MC/DC includes a built-in ray-casting visualizer for inspecting CSG geometries before running a full transport simulation:

mcdc.visualize(
    "xz",                         # projection plane
    y=0.0,                        # slice position
    x=[-10.0, 10.0],              # plot range
    z=[-5.0, 5.0],
    pixels=(400, 400),
    colors={fuel: "red", water: "blue"},
)

This produces a pixel map showing which material fills each pixel. Animated geometry (moving surfaces) can be visualized with the time argument:

mcdc.visualize(..., time=np.linspace(0, 9, 19), save_as="geo_animation")

For details on moving surfaces and sources, see Continuous Movement.

Moving Surfaces

Any surface can be given a piecewise-constant velocity using the move() method, enabling time-dependent geometry:

surface.move(velocities=[[vx, vy, vz]], durations=[dt])

MC/DC solves for the exact intersection of a particle trajectory with the moving surface — no time-step discretization error is introduced. For the mathematical formulation, see Continuous Movement.