Cross Sections

Understanding Cross Sections

Nuclear cross sections represent the probability of neutron interactions with atomic nuclei. Think of them as effective target areas - larger cross sections mean higher interaction probabilities. OpenMC uses these data to determine what happens when neutrons encounter different materials.

Cross sections vary dramatically with neutron energy and depend on the specific nuclide and reaction type. OpenMC uses continuous-energy data rather than simplified group averages.

Main Reaction Types

  • Absorption: Neutron disappears (fission, capture)
  • Scattering: Neutron changes direction/energy
  • Fission: Nucleus splits, produces new neutrons
  • Total: Sum of all possible interactions

Key Properties

  • Units: barns (10⁻²⁴ cm²)
  • Energy-dependent (thermal to fast)
  • Temperature-dependent (Doppler effect)
  • Isotope-specific

Nuclear Data Libraries

OpenMC requires nuclear data files that contain cross section information for all nuclides in your model. These libraries are based on evaluated nuclear data like ENDF/B-VIII.0 and are processed into HDF5 format for efficient access.

Getting Nuclear Data

python
# Download pre-built HDF5 nuclear data from https://openmc.org/data/
# (e.g. the ENDF/B-VIII.0 library, ~1.5 GB)

# After downloading, point OpenMC to the data:
import os
os.environ['OPENMC_CROSS_SECTIONS'] = '/path/to/cross_sections.xml'

# Or set it permanently in your shell profile:
# export OPENMC_CROSS_SECTIONS=/path/to/cross_sections.xml

Tutorial snippet — no separate file in examples repo

Using Custom Data Libraries

python
# Point to a specific cross sections file
import openmc

# Set cross sections path for all materials
openmc.config['cross_sections'] = '/path/to/cross_sections.xml'

# Or set for specific materials
materials = openmc.Materials()
materials.cross_sections = '/path/to/cross_sections.xml'

# Check what's available
print(f"Using cross sections: {openmc.config['cross_sections']}")

Tutorial snippet — no separate file in examples repo

Data Requirements: Every nuclide in your materials must have corresponding cross section data. OpenMC will tell you if any data is missing when you run a simulation.

Temperature Effects

Cross sections change with temperature due to thermal motion of nuclei (Doppler broadening). This effect is most important for resonance reactions and significantly impacts reactor physics calculations.

Basic Temperature Handling

python
# Temperature is a cell property (set via cell.temperature)
fuel = openmc.Material(name='Hot Fuel')
fuel.set_density('g/cm3', 10.4)
fuel.add_nuclide('U235', 0.045)
fuel.add_nuclide('U238', 0.955)
fuel.add_element('O', 2.0)
# Temperature is set via cell.temperature, not on materials:
#   fuel_cell = openmc.Cell(fill=fuel, region=region)
#   fuel_cell.temperature = 900  # Kelvin

# OpenMC will interpolate cross sections to this temperature
# (if data is available at multiple temperatures)

Tutorial snippet — no separate file in examples repo

Multiple Temperature Points

python
# Assign different temperatures to different cells
fuel = openmc.Material(name='UO2 Fuel')
fuel.set_density('g/cm3', 10.4)
fuel.add_nuclide('U235', 0.045)
fuel.add_nuclide('U238', 0.955)
fuel.add_element('O', 2.0)

# Temperature is a cell-level property
fuel_cell = openmc.Cell(fill=fuel, region=fuel_region)
fuel_cell.temperature = 900  # Kelvin

clad_cell = openmc.Cell(fill=clad, region=clad_region)
clad_cell.temperature = 600  # Kelvin

# Configure simulation temperature handling
settings = openmc.Settings()
settings.temperature = {
    'method': 'interpolation',    # or 'nearest'
    'multipole': True            # Use windowed multipole data if available
}

Tutorial snippet — no separate file in examples repo

Temperature effects are particularly important for U-238 resonance absorption and fuel Doppler feedback calculations. Proper temperature modeling can significantly impact calculated reactivity and safety parameters.

Thermal Scattering

For materials containing light nuclei (like hydrogen in water), thermal neutron scattering is affected by molecular binding. OpenMC uses S(α,β) thermal scattering data to account for these effects accurately.

python
# Water with thermal scattering (essential for accuracy)
water = openmc.Material(name='Light Water')
water.set_density('g/cm3', 1.0)
water.add_nuclide('H1', 2.0)
water.add_element('O', 1.0)
water.add_s_alpha_beta('c_H_in_H2O')    # Critical for thermal neutrons

# Graphite moderator
graphite = openmc.Material(name='Graphite')
graphite.set_density('g/cm3', 1.7)
graphite.add_element('C', 1.0)
graphite.add_s_alpha_beta('c_Graphite')

# Available thermal scattering data:
# c_H_in_H2O    - hydrogen in water
# c_Graphite    - graphite
# c_Be          - beryllium metal
# c_BeO         - beryllium oxide
# ... and others in your nuclear data library

Tutorial snippet — no separate file in examples repo

Critical for Accuracy: Always include thermal scattering data for water, graphite, and other light-nucleus materials. Omitting this data can lead to significant errors in thermal neutron calculations.

Working with Cross Section Data

Basic Data Inspection

python
import openmc.data
import numpy as np
import matplotlib.pyplot as plt

# Load cross section data for a nuclide
u235_path = '/path/to/nuclear/data/U235.h5'
u235 = openmc.data.IncidentNeutron.from_hdf5(u235_path)

# Examine available reactions (keyed by MT number)
print("Available reactions for U-235:")
for mt, rxn in u235.reactions.items():
    print(f"  MT {mt}: {rxn}")

# Access the total cross section (MT 1) at a specific temperature
total_rxn = u235[1]                  # MT 1 = total
xs = total_rxn.xs['294K']           # Tabulated1D at 294 K

# Plot total cross section vs energy
energies = np.logspace(-2, 7, 1000)  # 0.01 eV to 10 MeV
total_xs = xs(energies)

plt.figure(figsize=(10, 6))
plt.loglog(energies, total_xs, 'b-', linewidth=2)
plt.xlabel('Energy (eV)')
plt.ylabel('Cross Section (barns)')
plt.title('U-235 Total Cross Section')
plt.grid(True, which='both', alpha=0.3)
plt.show()

# Check cross section at specific energy
thermal_energy = 0.0253  # eV (thermal)
fast_energy = 1e6       # eV (1 MeV)

print(f"U-235 total XS at thermal: {xs(thermal_energy):.1f} barns")
print(f"U-235 total XS at 1 MeV:   {xs(fast_energy):.1f} barns")

Tutorial snippet — no separate file in examples repo

Understanding Cross Section Behavior

python
# Compare different nuclides
u235 = openmc.data.IncidentNeutron.from_hdf5('/path/to/U235.h5')
u238 = openmc.data.IncidentNeutron.from_hdf5('/path/to/U238.h5')

energies = np.logspace(-2, 7, 1000)

# Access reactions by MT number at a specific temperature
u235_fission_xs = u235[18].xs['294K']   # MT 18 = fission
u235_total_xs   = u235[1].xs['294K']    # MT 1  = total
u238_total_xs   = u238[1].xs['294K']

plt.figure(figsize=(12, 8))

# Fission cross sections (U-235)
plt.subplot(2, 2, 1)
plt.loglog(energies, u235_fission_xs(energies), 'r-', label='U-235')
plt.xlabel('Energy (eV)')
plt.ylabel('Fission XS (barns)')
plt.title('Fission Cross Sections')
plt.legend()
plt.grid(True)

# Total cross sections comparison
plt.subplot(2, 2, 2)
plt.loglog(energies, u235_total_xs(energies), 'r-', label='U-235')
plt.loglog(energies, u238_total_xs(energies), 'b-', label='U-238')
plt.xlabel('Energy (eV)')
plt.ylabel('Total XS (barns)')
plt.title('Total Cross Sections')
plt.legend()
plt.grid(True)

plt.tight_layout()
plt.show()

# This helps explain why U-235 is fissile and U-238 has strong resonances

Tutorial snippet — no separate file in examples repo

Practical Tips

Data Management

  • Start Simple: Use the default ENDF/B-VIII.0 data for most applications
  • Check Coverage: Ensure your data library includes all nuclides in your materials
  • Temperature Consistency: Use appropriate temperatures for your operating conditions
  • Thermal Scattering: Always include S(α,β) data for light-nucleus materials

Common Issues

python
# Check for missing cross section data
try:
    model = openmc.Model(geometry, materials, settings)
    model.run()
except Exception as e:
    if "does not contain cross sections" in str(e):
        print("Missing nuclear data - check your cross_sections.xml")
        print("Missing nuclide:", str(e).split("'")[1])
    else:
        raise e

# Verify thermal scattering is included
for material in materials:
    if any(n.name == 'H1' for n in material.nuclides):
        if not hasattr(material, '_sab') or not material._sab:
            print(f"Warning: {material.name} contains H1 but no S(a,b) data")
            
# Temperature is set on cells, not materials:
# cell.temperature = 900  # Kelvin

Tutorial snippet — no separate file in examples repo

Proper cross section handling — correct libraries, thermal scattering data, and temperature treatment — directly determines simulation accuracy.