Cell Creation Techniques

Understanding Cells

Building on geometry basics, cells are the fundamental building blocks that connect your geometric regions to physical materials. Every cell defines a region of space and specifies what fills it - whether material, void, or another universe.

Think of cells as containers: the region property defines the container's shape using surface combinations, and the fill property determines what goes inside.

python
import openmc

# Basic cell creation pattern
fuel_region = -fuel_cylinder  # Define the shape
fuel_cell = openmc.Cell(      # Create the container
    fill=fuel_material,       # What goes inside
    region=fuel_region        # The shape
)

Fill Options

Cells can be filled with materials, other universes, lattices, or left void. Understanding when to use each fill type is crucial for efficient modeling.

python
# Material fill - most common
fuel_cell = openmc.Cell(fill=fuel_material, region=fuel_region)

# Void fill - empty space
void_cell = openmc.Cell(fill=None, region=void_region)

# Universe fill - for nested structures
pin_cell = openmc.Cell(fill=pin_universe, region=pin_region)

# Lattice fill - for repeated arrays
assembly_cell = openmc.Cell(fill=pin_lattice, region=assembly_region)

# Temperature specification
hot_fuel_cell = openmc.Cell(
    fill=fuel_material, 
    region=fuel_region,
    temperature=900  # Kelvin
)

Complex Boolean Operations

For complex geometries, you'll need to combine multiple surfaces using boolean logic. The key is building up complexity step by step.

python
# Multiple cylinders example - fuel assembly with guide tubes
fuel_radius = 0.4096
clad_radius = 0.4750  
guide_radius = 0.6120
pitch = 1.26

# Define surfaces
fuel_surf = openmc.ZCylinder(r=fuel_radius)
clad_surf = openmc.ZCylinder(r=clad_radius)
guide_surf = openmc.ZCylinder(r=guide_radius)

# Create regions step by step
fuel_region = -fuel_surf
clad_region = +fuel_surf & -clad_surf
guide_region = -guide_surf
water_region = +clad_surf & +guide_surf  # Outside both cylinders

# Alternative: Use parentheses for clarity
complex_region = (+surf1 & -surf2) | (+surf3 & -surf4)
intersection_region = -surf1 & -surf2 & +surf3  # Inside 1&2, outside 3

Coordinate Transformations

When you need the same geometry in different locations or orientations, use transformations rather than creating new surfaces.

python
# Define base geometry once
base_pin = create_pin_universe()  # Function returning a universe

# Place multiple copies with transformations
assembly_cells = []

for i in range(17):
    for j in range(17):
        x_pos = (i - 8) * pitch
        y_pos = (j - 8) * pitch
        
        cell = openmc.Cell(
            fill=base_pin,
            region=pin_boundary,
            translation=(x_pos, y_pos, 0)
        )
        assembly_cells.append(cell)

# Rotation example (angles in degrees)
rotated_cell = openmc.Cell(
    fill=fuel_material,
    region=fuel_region,
    rotation=(0, 0, 45)  # 45° rotation around z-axis
)

Working with Distributed Materials

Sometimes you need different materials in the same geometric region. OpenMC supports distributed material fills for this purpose.

python
# Define multiple fuel compositions
fuel_fresh = openmc.Material(name='Fresh UO2')
fuel_fresh.set_density('g/cm3', 10.4)
fuel_fresh.add_nuclide('U235', 0.045)
fuel_fresh.add_nuclide('U238', 0.955)
fuel_fresh.add_element('O', 2.0)

fuel_burned = openmc.Material(name='Burned UO2')  
fuel_burned.set_density('g/cm3', 10.2)
fuel_burned.add_nuclide('U235', 0.020)
fuel_burned.add_nuclide('U238', 0.935)
fuel_burned.add_nuclide('Pu239', 0.008)
fuel_burned.add_element('O', 2.0)

# Create a distributed material (fractions sum to 1.0)
distributed_fuel = openmc.DistributedMaterial(
    materials=[fuel_fresh, fuel_burned],
    fractions=[0.7, 0.3]
)

fuel_cell = openmc.Cell(
    fill=distributed_fuel,
    region=fuel_region
)

Error Prevention

Avoid these common cell creation mistakes:

python
# Wrong: Overlapping cells
cell1 = openmc.Cell(fill=material1, region=-cylinder)
cell2 = openmc.Cell(fill=material2, region=-cylinder)  # Same region!

# Correct: Non-overlapping regions  
cell1 = openmc.Cell(fill=material1, region=-inner_cylinder)
cell2 = openmc.Cell(fill=material2, region=+inner_cylinder & -outer_cylinder)

# Wrong: Missing regions
incomplete_cell = openmc.Cell(fill=material)  # No region specified

# Correct: Always specify region
complete_cell = openmc.Cell(fill=material, region=region)

# Wrong: Complex expressions without testing
complex_region = +s1 & -s2 | +s3 & -s4 & +s5 | -s6  # Hard to debug

# Better: Build incrementally and test
base_region = +s1 & -s2
add_region = +s3 & -s4 & +s5  
final_region = base_region | add_region | -s6

Validation and Debugging

Always validate your cell definitions before running full simulations:

python
# Check for overlaps
universe = openmc.Universe(cells=[cell1, cell2, cell3])
geometry = openmc.Geometry(universe)
geometry.check_for_overlaps()

# Plot a slice to verify visually (requires XML export)
plot = openmc.Plot()
plot.basis = 'xy'
plot.origin = (0, 0, 0)
plot.width = (5, 5)
plot.pixels = (400, 400)
plot.color_by = 'material'

plots = openmc.Plots([plot])
plots.export_to_xml()
geometry.export_to_xml()
materials = openmc.Materials([cell.fill for cell in [cell1, cell2, cell3] if isinstance(cell.fill, openmc.Material)])
materials.export_to_xml()
openmc.plot_geometry()

# Check cell fills
for cell in [cell1, cell2, cell3]:
    if cell.fill is None:
        print(f"Warning: Cell {cell.id} has no fill")
    if cell.region is None:
        print(f"Warning: Cell {cell.id} has no region")

Visual verification is your best tool. If the geometry looks wrong in plots, it probably is wrong. Fix geometry issues before worrying about physics parameters.