Building a Reactor Model in OpenMC

Overview

This guide walks through creating a simple reactor model in OpenMC. We'll build the model step by step, from basic components to a complete core. Each step builds on concepts from previous tutorials, focusing on practical implementation.

Step 1: Materials

First, define the basic materials needed for the reactor: fuel and moderator. This example uses UO2 fuel and light water moderator with appropriate thermal scattering treatment.

python
# Basic reactor model setup
import openmc
import numpy as np

# Define materials
fuel = openmc.Material(name='UO2 fuel')
fuel.add_nuclide('U235', 0.04)
fuel.add_nuclide('U238', 0.96)
fuel.add_nuclide('O16', 2.0)
fuel.set_density('g/cm3', 10.4)

water = openmc.Material(name='Water')
water.add_nuclide('H1', 2)
water.add_nuclide('O16', 1)
water.set_density('g/cm3', 1.0)
water.add_s_alpha_beta('c_H_in_H2O')

materials = openmc.Materials([fuel, water])
materials.export_to_xml()

For more complex material definitions, including burnable absorbers or control materials, see the Materials page.

Step 2: Pin Cell

Next, create a basic fuel pin cell. This forms the fundamental building block of our reactor model, consisting of a fuel cylinder surrounded by moderator.

python
# Define pin cell geometry
fuel_radius = 0.4
pin_pitch = 1.25

# Create cylinder and reflective square boundary
fuel_surf = openmc.ZCylinder(r=fuel_radius)
square_region = openmc.rectangular_prism(
    pin_pitch,
    pin_pitch,
    boundary_type='reflective'
)

# Create cells
fuel_cell = openmc.Cell(fill=fuel, region=-fuel_surf)
water_cell = openmc.Cell(fill=water, region=+fuel_surf & square_region)

# Create universe
pin_universe = openmc.Universe(cells=[fuel_cell, water_cell])

For detailed pin cell modeling, including gap and cladding, see thePin Cell Example.

Step 3: Fuel Assembly

Create a 17×17 fuel assembly using a lattice of pin cells. This example uses a simple uniform loading pattern.

python
# Create 17x17 assembly
assembly_pitch = 1.25
lattice = openmc.RectLattice()
lattice.lower_left = (-10.71, -10.71)
lattice.pitch = (assembly_pitch, assembly_pitch)
lattice.universes = [[pin_universe] * 17] * 17

# Reflective boundary around assembly
half_span = 17 * assembly_pitch / 2
xmin = openmc.XPlane(-half_span, boundary_type='reflective')
xmax = openmc.XPlane(half_span, boundary_type='reflective')
ymin = openmc.YPlane(-half_span, boundary_type='reflective')
ymax = openmc.YPlane(half_span, boundary_type='reflective')
zmin = openmc.ZPlane(-200.0)
zmax = openmc.ZPlane(200.0)

assembly_region = +xmin & -xmax & +ymin & -ymax & +zmin & -zmax

# Create assembly cell and universe
assembly_cell = openmc.Cell(fill=lattice, region=assembly_region)
assembly_universe = openmc.Universe(cells=[assembly_cell])

For advanced assembly configurations, including guide tubes and varying enrichments, see theAssembly Example.

Step 4: Core Configuration

Arrange assemblies into a 3×3 core layout. This demonstrates core construction while keeping the model computationally manageable.

python
# Create 3x3 core
core_lattice = openmc.RectLattice()
core_lattice.lower_left = (-32.13, -32.13)
core_lattice.pitch = (21.42, 21.42)
core_lattice.universes = [[assembly_universe] * 3] * 3

# Vacuum boundary around core
core_half_span = 3 * 21.42 / 2
core_xmin = openmc.XPlane(-core_half_span, boundary_type='vacuum')
core_xmax = openmc.XPlane(core_half_span, boundary_type='vacuum')
core_ymin = openmc.YPlane(-core_half_span, boundary_type='vacuum')
core_ymax = openmc.YPlane(core_half_span, boundary_type='vacuum')
zmin = openmc.ZPlane(-200.0, boundary_type='vacuum')
zmax = openmc.ZPlane(200.0, boundary_type='vacuum')

core_region = +core_xmin & -core_xmax & +core_ymin & -core_ymax & +zmin & -zmax

# Create core cell and root universe
core_cell = openmc.Cell(fill=core_lattice, region=core_region)
root_universe = openmc.Universe(cells=[core_cell])

# Create and export geometry
geometry = openmc.Geometry(root_universe)
geometry.export_to_xml()

Step 5: Simulation Settings

Configure basic simulation parameters including particle count, batches, and initial source distribution.

python
# Define simulation settings
settings = openmc.Settings()
settings.batches = 100
settings.inactive = 20
settings.particles = 10000

# Set initial fission source near the core center
lower_left = (-32.13, -32.13, -50.0)
upper_right = (32.13, 32.13, 50.0)
source = openmc.Source(space=openmc.stats.Box(lower_left, upper_right))
settings.source = source
settings.export_to_xml()

Step 6: Tallies

Set up basic tallies to score flux and fission rate distributions across the core.

python
# Define tallies
mesh = openmc.RegularMesh()
mesh.dimension = [34, 34]
mesh.lower_left = [-32.13, -32.13]
mesh.upper_right = [32.13, 32.13]

tally = openmc.Tally(name='core_mesh_flux')
tally.filters = [openmc.MeshFilter(mesh)]
tally.scores = ['flux', 'fission']
tallies = openmc.Tallies([tally])
tallies.export_to_xml()

For more advanced tally options, see theTallies page.

Next Steps

This example demonstrates basic reactor modeling. For more advanced topics, explore:

Step 7: Run the Model

Combine the geometry, materials, settings, and tallies into a complete model and run OpenMC to obtain k-effective.

python
# Build full model and run
model = openmc.Model(geometry, materials, settings, tallies)
sp = openmc.StatePoint(model.run())
print(f"k-effective: {sp.keff.nominal_value:.5f} ± {sp.keff.std_dev:.5f}")