On the Deviation of Results in Using OpenMC to Produce Multi group Cross Sections for Openmoc in the Presence of Vacuum Boundaries

I need to use OpenMC to create a cross-section library of a single three-dimensional fuel rod for use by OpenMOC. When all boundary conditions are reflection, I can use the cross-section library produced to calculate good results in OpenMOC. However, when the upper and lower surface boundary conditions are vacuum, the error reaches several thousand pcm. Can someone tell me the reason for this.Here is the code,

import numpy as np
import matplotlib.pyplot as plt
plt.style.use(‘seaborn-dark’)

import openmoc

import openmc
import openmc.mgxs as mgxs
import openmc.model as model
import openmc.data
from openmc.openmoc_compatible import get_openmoc_geometry

create a model object to tie together geometry, materials, settings, and tallies

#model = openmc.model()
model = model.Model()

1.6% enriched fuel

fuel = openmc.Material(name=‘3.1% Fuel’)
fuel.set_density(‘g/cm3’, 10.257)
fuel.add_nuclide(‘U234’, 6.11864E-06)
fuel.add_nuclide(‘U235’, 7.18132E-04)
fuel.add_nuclide(‘U236’, 3.29861E-06)
fuel.add_nuclide(‘U238’, 2.21546E-02)
fuel.add_nuclide(‘O16’, 4.57642E-02)

borated water

water = openmc.Material(name=‘Water’)
water.set_density(‘g/cm3’, 0.743)
water.add_nuclide(‘H1’, 4.96224E-02)
water.add_nuclide(‘B10’, 1.07070E-05)
water.add_nuclide(‘B11’, 4.30971E-05)
water.add_nuclide(‘O16’, 2.48112E-2)

zircaloy

gap = openmc.Material(name=‘gap’)
gap.set_density(‘g/cm3’, 0.0001786)
gap.add_nuclide(‘He4’, 2.68714E-05)

zircaloy

zircaloy = openmc.Material(name=‘clad’)
zircaloy.set_density(‘g/cm3’, 6.56)
zircaloy.add_nuclide(‘Zr90’, 2.18865E-02)
zircaloy.add_nuclide(‘Zr91’, 4.77292E-03)
zircaloy.add_nuclide(‘Zr92’, 7.29551E-03)
zircaloy.add_nuclide(‘Zr94’, 7.39335E-03)
zircaloy.add_nuclide(‘Zr96’, 1.19110E-03)
zircaloy.add_nuclide(‘Sn112’, 4.68066E-06)
zircaloy.add_nuclide(‘Sn114’, 3.18478E-06)
zircaloy.add_nuclide(‘Sn115’, 1.64064E-06)
zircaloy.add_nuclide(‘Sn116’, 7.01616E-05)
zircaloy.add_nuclide(‘Sn117’, 3.70592E-05)
zircaloy.add_nuclide(‘Sn118’, 1.16872E-04)
zircaloy.add_nuclide(‘Sn119’, 4.14504E-05)
zircaloy.add_nuclide(‘Sn120’, 1.57212E-04)
zircaloy.add_nuclide(‘Sn122’, 2.23417E-05)
zircaloy.add_nuclide(‘Sn124’, 2.79392E-05)
zircaloy.add_nuclide(‘Fe54’, 8.68307E-06)
zircaloy.add_nuclide(‘Fe56’, 1.36306E-04)
zircaloy.add_nuclide(‘Fe57’, 3.14789E-06)
zircaloy.add_nuclide(‘Fe58’, 4.18926E-07)
zircaloy.add_nuclide(‘Cr50’, 3.30121E-06)
zircaloy.add_nuclide(‘Cr52’, 6.36606E-05)
zircaloy.add_nuclide(‘Cr53’, 7.21860E-06)
zircaloy.add_nuclide(‘Cr54’, 1.79686E-06)
zircaloy.add_nuclide(‘Hf174’, 3.54138E-09)
zircaloy.add_nuclide(‘Hf176’, 1.16423E-07)
zircaloy.add_nuclide(‘Hf177’, 4.11686E-07)
zircaloy.add_nuclide(‘Hf178’, 6.03806E-07)
zircaloy.add_nuclide(‘Hf179’, 3.01460E-07)
zircaloy.add_nuclide(‘Hf180’, 7.76449E-07)

Instantiate a Materials collection

model.materials = openmc.Materials([fuel, gap ,water, zircaloy])

Create cylinders for the fuel and clad

fuel_outer_radius = openmc.ZCylinder(x0=0.0, y0=0.0, r=0.4096)
gap_outer_radius = openmc.ZCylinder(x0=0.0, y0=0.0, r=0.418)
clad_outer_radius = openmc.ZCylinder(x0=0.0, y0=0.0, r=0.475)

Create box to surround the geometry

#box = openmc.model.rectangular_prism(1.26, 1.26, boundary_type=‘reflective’)
min_x = openmc.XPlane(x0=-0.63, boundary_type=‘reflective’)
max_x = openmc.XPlane(x0=+0.63, boundary_type=‘reflective’)
min_y = openmc.YPlane(y0=-0.63, boundary_type=‘reflective’)
max_y = openmc.YPlane(y0=+0.63, boundary_type=‘reflective’)
min_z = openmc.ZPlane(z0=-182.88, boundary_type=‘vacuum’)
max_z = openmc.ZPlane(z0=+182.88, boundary_type=‘vacuum’)

Create a Universe to encapsulate a fuel pin

pin_cell_universe = openmc.Universe(name=‘1.6% Fuel Pin’)

Create fuel Cell

fuel_cell = openmc.Cell(name=‘1.6% Fuel’)
fuel_cell.fill = fuel
fuel_cell.region = -fuel_outer_radius
pin_cell_universe.add_cell(fuel_cell)

Create a gap Cell

gap_cell = openmc.Cell(name=‘1.6% Clad’)
gap_cell.fill = gap
gap_cell.region = +fuel_outer_radius & -gap_outer_radius
pin_cell_universe.add_cell(gap_cell)

Create a clad Cell

clad_cell = openmc.Cell(name=‘1.6% Clad’)
clad_cell.fill = zircaloy
clad_cell.region = +gap_outer_radius & -clad_outer_radius
pin_cell_universe.add_cell(clad_cell)

Create a moderator Cell

moderator_cell = openmc.Cell(name=‘1.6% Moderator’)
moderator_cell.fill = water
moderator_cell.region = +clad_outer_radius
pin_cell_universe.add_cell(moderator_cell)

Create root Cell

root_cell = openmc.Cell(name=‘root cell’)
root_cell.fill = pin_cell_universe

Add boundary planes

root_cell.region = +min_x & -max_x & +min_y & -max_y & +min_z & -max_z

Create root Universe

root_universe = openmc.Universe(universe_id=0, name=‘root universe’)
root_universe.add_cell(root_cell)

Create Geometry and set root Universe

model.geometry = openmc.Geometry(root_universe)

OpenMC simulation parameters

batches = 500
inactive = 50
particles = 10000

Instantiate a Settings object

settings = openmc.Settings()
settings.batches = batches
settings.inactive = inactive
settings.particles = particles
settings.output = {‘tallies’: False}

Create an initial uniform spatial source distribution over fissionable zones

bounds = [-1.26, -1.26, -1.26, 1.26, 1.26, 1.26]
uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:], only_fissionable=True)
settings.source = openmc.Source(space=uniform_dist)

Activate tally precision triggers

settings.trigger_active = True
settings.trigger_max_batches = settings.batches * 4

model.settings = settings

Instantiate a “fine” 8-group EnergyGroups object

fine_groups = mgxs.EnergyGroups([0., 0.058, 0.14, 0.28,
0.625, 4.0, 5.53e3, 821.0e3, 20.0e6])

Extract all Cells filled by Materials

openmc_cells = model.geometry.get_all_material_cells().values()

Create dictionary to store multi-group cross sections for all cells

xs_library = {}

Instantiate 8-group cross sections for each cell

for cell in openmc_cells:
xs_library[cell.id] = {}
xs_library[cell.id][‘total’] = mgxs.TotalXS(groups=fine_groups)
xs_library[cell.id][‘transport’] = mgxs.TransportXS(groups=fine_groups)
xs_library[cell.id][‘fission’] = mgxs.FissionXS(groups=fine_groups)
xs_library[cell.id][‘nu-fission’] = mgxs.FissionXS(groups=fine_groups, nu=True)
xs_library[cell.id][‘nu-scatter’] = mgxs.ScatterMatrixXS(groups=fine_groups, nu=True)
xs_library[cell.id][‘chi’] = mgxs.Chi(groups=fine_groups)

Create a tally trigger for +/- 0.01 on each tally used to compute the multi-group cross sections

tally_trigger = openmc.Trigger(‘std_dev’, 1e-2)

Add the tally trigger to each of the multi-group cross section tallies

for cell in openmc_cells:
for mgxs_type in xs_library[cell.id]:
xs_library[cell.id][mgxs_type].tally_trigger = tally_trigger

Instantiate an empty Tallies object

tallies = openmc.Tallies()

Iterate over all cells and cross section types

for cell in openmc_cells:
for rxn_type in xs_library[cell.id]:

    # Set the cross sections domain to the cell
    xs_library[cell.id][rxn_type].domain = cell
    
    # Tally cross sections by nuclide
    xs_library[cell.id][rxn_type].by_nuclide = True
            
    # Add OpenMC tallies to the tallies file for XML generation
    for tally in xs_library[cell.id][rxn_type].tallies.values():
        tallies.append(tally, merge=True)

model.tallies = tallies

Run OpenMC

sp_file = model.run()

Load the last statepoint file

sp = openmc.StatePoint(sp_file)
#sp = openmc.StatePoint(‘statepoint.2000.h5’)

Iterate over all cells and cross section types

for cell in openmc_cells:
for rxn_type in xs_library[cell.id]:
xs_library[cell.id][rxn_type].load_from_statepoint(sp)

Create an OpenMOC Geometry from the OpenMC Geometry

openmoc_geometry = get_openmoc_geometry(sp.summary.geometry)

Get all OpenMOC cells in the gometry

openmoc_cells = openmoc_geometry.getRootUniverse().getAllCells()

Inject multi-group cross sections into OpenMOC Materials

for cell_id, cell in openmoc_cells.items():

# Ignore the root cell
if cell.getName() == 'root cell':
    continue

# Get a reference to the Material filling this Cell
openmoc_material = cell.getFillMaterial()

# Set the number of energy groups for the Material
openmoc_material.setNumEnergyGroups(fine_groups.num_groups)

# Extract the appropriate cross section objects for this cell
transport = xs_library[cell_id]['transport']
nufission = xs_library[cell_id]['nu-fission']
nuscatter = xs_library[cell_id]['nu-scatter']
chi = xs_library[cell_id]['chi']

# Inject NumPy arrays of cross section data into the Material
# NOTE: Sum across nuclides to get macro cross sections needed by OpenMOC
openmoc_material.setSigmaT(transport.get_xs(nuclides='sum').flatten())
openmoc_material.setNuSigmaF(nufission.get_xs(nuclides='sum').flatten())
openmoc_material.setSigmaS0(nuscatter.get_xs(nuclides='sum').flatten())
openmoc_material.setChi(chi.get_xs(nuclides='sum').flatten())

Generate tracks for OpenMOC

track_generator = openmoc.TrackGenerator3D(openmoc_geometry, num_azim=16, num_polar=6,
azim_spacing=0.1, z_spacing=0.75)
track_generator.generateTracks()

Run OpenMOC

solver = openmoc.CPUSolver(track_generator)
solver.setNumThreads(12)
solver.computeEigenvalue()
solver.setConvergenceThreshold(1e-6)

Print report of keff and bias with OpenMC

openmoc_keff = solver.getKeff()
#openmc_keff = sp.keff.n
openmc_keff = sp.k_combined.nominal_value
bias = (openmoc_keff - openmc_keff) * 1e5

print(‘openmc keff = {0:1.6f}’.format(openmc_keff))
print(‘openmoc keff = {0:1.6f}’.format(openmoc_keff))
print(‘bias [pcm]: {0:1.1f}’.format(bias))

The results of running the above code,
openmc_keff = 1.198678
openmoc_keff = 1.127340
bias [pcm]: -7133.8