UO Pebbles
In this tutorial, you will learn how to:
Couple OpenMC via temperature and heat source feedback to MOOSE for pebbles
Establish coupling between OpenMC and MOOSE for nested universe OpenMC models
Couple OpenMC solves in units of centimeters with MOOSE solves in units of meters
Repeat the same mesh tally several times throughout the OpenMC domain
To access this tutorial,
cd cardinal/tutorials/pebbles
Geometry and Computational Models
The geometry and computational model for this case consists of a vertical "stack" of three solid UO pebbles, each of 1.5 cm radius. The pebble centers are located at cm, cm, and cm. FLiBe coolant occupies the space around the pebbles. Heat is produced in the pebbles; we assume the total power is 1500 W.
While "pebbles" in nuclear applications typically refer to Tri-Structural Isotropic (TRISO) fuel geometries, this tutorial only considers homogeneous pebbles so as to serve as a stepping stone to modeling heterogeneous TRISO geometries in a later tutorial. Here, we first emphasize important features of the OpenMC wrapping having to do with lattices and unstructured mesh tallies.
Heat Conduction Model
The MOOSE heat transfer module is used to solve for steady-state heat conduction,
(1)
where is the solid thermal conductivity, is the solid temperature, and is a volumetric heat source in the solid.
The solid mesh is shown in Figure 1. The pebble surface is sideset 0. The MOOSE solid problem is set up with a length unit of meters, which is convenient for heat transfer applications because material properties for thermal-fluids physics are almost always given in SI units. Further, many MOOSE physics modules inherently assume SI units, such as the fluid property module and solid property module.

Figure 1: Mesh for solid domain
Because heat transfer and fluid flow in the FLiBe is not modeled in this example, heat removal by the fluid is approximated by setting the outer surface of the pebble to a convection boundary condition,
(2)
where W/m/K and is set to a function linearly ranging from 650°C at cm to 750°C at cm. In this example, the MOOSE heat transfer module will be run first. The initial solid temperature is set to 650°C and the heat source to zero.
OpenMC Model
The OpenMC model is built using CSG; we will leverage the lattice feature in OpenMC to repeat a universe in a structured manner throughout the domain. Here, the universe consists of a single pebble and surrounding FLiBe, which is repeated three times throughout the geometry. The overall domain is enclosed in a box with - width of 44 cm and height of 12 cm. All boundaries of this box are reflecting.
OpenMC's Python API is used to create the pebbles model with the script shown below. First, we define materials for the various regions and create the geometry. We create a universe consisting of a single pebble, and then repeat that universe three times in a lattice. Because we have one cell per pebble, each pebble will receive a single temperature from MOOSE. The cell level is 1 because the pebble lattice is nested once with respect to the highest level, and we want to apply feedback to each pebble individually. If we were to specify level zero in the geometry, we would apply feedback to the main_cell
, which would apply temperature feedback across the problem globally. The OpenMC geometry as produced via plots is shown in Figure 2.
import openmc
import numpy as np
# Add materials for the pebbles and the FLiBe
uo2 = openmc.Material()
uo2.set_density('g/cc', 10.0)
uo2.add_element('U', 1.0, enrichment=5.0)
uo2.add_element('O', 2.0)
enrichment_li7 = 0.99995
flibe = openmc.Material(name='2LiF-BeF2')
flibe.set_density('kg/m3', 1960)
flibe.add_nuclide('Li7', 2.0*enrichment_li7)
flibe.add_nuclide('Li6', 2.0*(1 - enrichment_li7))
flibe.add_element('Be', 1.0)
flibe.add_element('F', 4.0)
mats = openmc.Materials([uo2, flibe])
mats.export_to_xml()
model = openmc.model.Model()
# create a universe containing the repeatable universe of
# a single pebble plus surrounding flibe
R = 1.5
L = 4.0
sphere_surface = openmc.Sphere(r=R, name='Sphere surface')
flibe_surface = openmc.model.RectangularPrism(L, L, boundary_type='reflective')
sphere_cell = openmc.Cell(fill=uo2, region=-sphere_surface, name='Pebble')
flibe_cell = openmc.Cell(fill=flibe, region=+sphere_surface & -flibe_surface, name='Flibe')
repeatable_univ = openmc.Universe(cells=[sphere_cell, flibe_cell])
outer_cell = openmc.Cell(fill=flibe, region=+sphere_surface & -flibe_surface, name='Outside')
outer_univ = openmc.Universe(cells=[outer_cell])
# create a lattice to repeat the pebble + flibe universe
N = 3
lattice = openmc.RectLattice()
lattice.lower_left = (-L/2.0, -L/2.0, 0)
lattice.pitch = (L, L, L)
lattice.universes = np.full((N, 1, 1), repeatable_univ)
lattice.outer = outer_univ
# create the surfaces that will bound the lattice
top = openmc.ZPlane(z0=N*L, boundary_type='reflective')
bottom = openmc.ZPlane(z0=0.0, boundary_type='reflective')
main_cell = openmc.Cell(fill=lattice, region=-flibe_surface & +bottom & -top, name='Main cell')
model.geometry = openmc.Geometry([main_cell])
model.geometry.export_to_xml()
# Finally, define some run settings
model.settings = openmc.Settings()
model.settings.batches = 1000
model.settings.inactive = 100
model.settings.particles = 10000
lower_left = (-L, -L, 0)
upper_right = (L, L, N*L)
uniform_dist = openmc.stats.Box(lower_left, upper_right, only_fissionable=True)
model.settings.source = openmc.IndependentSource(space=uniform_dist)
model.settings.temperature = {'default': 650.0 + 273.15,
'method': 'interpolation',
'multipole': False,
'tolerance': 1000.0,
'range': (294.0, 1600.0)}
model.settings.export_to_xml()
# create some plots for making images in the tutorial
height = N*L
plot1 = openmc.Plot()
plot1.filename = 'plot1'
plot1.width = (L, height)
plot1.basis = 'xz'
plot1.origin = (0.0, 0.0, height/2.0)
plot1.pixels = (100, 300)
plot1.color_by = 'cell'
plot2 = openmc.Plot()
plot2.filename = 'plot2'
plot2.width = (L, L)
plot2.basis = 'xy'
plot2.origin = (0.0, 0.0, 2.0)
plot2.pixels = (100, 100)
plot2.color_by = 'cell'
plots = openmc.Plots([plot1, plot2])
plots.export_to_xml()
(tutorials/pebbles/make_openmc_model.py)
Figure 2: OpenMC geometry (colored by cell ID)
As you can see, there are only two unique cell IDs. This problem is built using distributed cells, meaning a cell with the same ID appears multiple times in the geometry; each time the cell is repeated, we assign a new instance to that cell. So, the three pebbles are represented as:
Cell ID 1, instance 0
Cell ID 1, instance 1
Cell ID 1, instance 2
and the FLiBe region is represented as:
Cell ID 2, instance 0
Cell ID 2, instance 1
Cell ID 2, instance 2
Because OpenMC runs after the MOOSE heat transfer module, initial conditions are only required for the FLiBe temperature, which is set to 650°C. Because there is no density feedback in this example, the densities initially imposed in the OpenMC model remain fixed at the values set in the OpenMC input files.
To create the XML files required to run OpenMC, run the script:
python make_openmc_model.py
Multiphysics Coupling
In this section, OpenMC and MOOSE are coupled for heat source and temperature feedback for the solid regions of a stack of three pebbles. The following sub-sections describe these files.
Solid Input Files
The solid phase is solved with the MOOSE heat transfer module, and is described in the solid.i
input. We set up the mesh using the CombinerGenerator to translate a single sphere mesh to multiple locations.
[Mesh<<<{"href": "../syntax/Mesh/index.html"}>>>]
[pebble]
type = SphereMeshGenerator<<<{"description": "Generate a 3-D sphere mesh centered on the origin", "href": "../source/meshgenerators/SphereMeshGenerator.html"}>>>
nr<<<{"description": "Number of radial elements"}>>> = 2
radius<<<{"description": "Sphere radius"}>>> = 0.015
[]
[repeat]
type = CombinerGenerator<<<{"description": "Combine multiple meshes (or copies of one mesh) together into one (disjoint) mesh. Can optionally translate those meshes before combining them.", "href": "../source/meshgenerators/CombinerGenerator.html"}>>>
inputs<<<{"description": "The input MeshGenerators. This can either be N generators or 1 generator. If only 1 is given then 'positions' must also be given."}>>> = pebble
positions<<<{"description": "The (optional) position of each given mesh. If N 'inputs' were given then this must either be left blank or N positions must be given. If 1 input was given then this MUST be provided."}>>> = '0 0 0.02
0 0 0.06
0 0 0.10'
[]
[]
(tutorials/pebbles/solid.i)Next, we define the temperature variable, temp
, and specify the governing equations and boundary conditions that we will apply. We set the thermal conductivity of the pebbles to 50 W/m/K.
[Variables<<<{"href": "../syntax/Variables/index.html"}>>>]
[temp]
initial_condition<<<{"description": "Specifies a constant initial condition for this variable"}>>> = ${T_fluid}
[]
[]
[Kernels<<<{"href": "../syntax/Kernels/index.html"}>>>]
[hc]
type = HeatConduction<<<{"description": "Diffusive heat conduction term $-\\nabla\\cdot(k\\nabla T)$ of the thermal energy conservation equation", "href": "../source/kernels/HeatConduction.html"}>>>
variable<<<{"description": "The name of the variable that this residual object operates on"}>>> = temp
[]
[heat]
type = CoupledForce<<<{"description": "Implements a source term proportional to the value of a coupled variable. Weak form: $(\\psi_i, -\\sigma v)$.", "href": "../source/kernels/CoupledForce.html"}>>>
variable<<<{"description": "The name of the variable that this residual object operates on"}>>> = temp
v<<<{"description": "The coupled variable which provides the force"}>>> = heat_source
[]
[]
[Functions<<<{"href": "../syntax/Functions/index.html"}>>>]
[T_fluid]
type = ParsedFunction<<<{"description": "Function created by parsing a string", "href": "../source/functions/MooseParsedFunction.html"}>>>
expression<<<{"description": "The user defined function."}>>> = '${T_fluid}+z*1000'
[]
[]
[BCs<<<{"href": "../syntax/BCs/index.html"}>>>]
[surface]
type = ConvectiveFluxFunction<<<{"description": "Determines boundary value by fluid heat transfer coefficient and far-field temperature", "href": "../source/bcs/ConvectiveFluxFunction.html"}>>>
T_infinity<<<{"description": "Function describing far-field temperature"}>>> = T_fluid
coefficient<<<{"description": "Function describing heat transfer coefficient"}>>> = 1000.0
variable<<<{"description": "The name of the variable that this residual object operates on"}>>> = temp
boundary<<<{"description": "The list of boundary IDs from the mesh where this object applies"}>>> = '0'
[]
[]
[Materials<<<{"href": "../syntax/Materials/index.html"}>>>]
[k]
type = GenericConstantMaterial<<<{"description": "Declares material properties based on names and values prescribed by input parameters.", "href": "../source/materials/GenericConstantMaterial.html"}>>>
prop_values<<<{"description": "The values associated with the named properties"}>>> = '50.0'
prop_names<<<{"description": "The names of the properties this material will have"}>>> = 'thermal_conductivity'
[]
[]
(tutorials/pebbles/solid.i)The heat source received from OpenMC is stored in the heat_source
auxiliary variable.
[AuxVariables<<<{"href": "../syntax/AuxVariables/index.html"}>>>]
[heat_source]
family<<<{"description": "Specifies the family of FE shape functions to use for this variable"}>>> = MONOMIAL
order<<<{"description": "Specifies the order of the FE shape function to use for this variable (additional orders not listed are allowed)"}>>> = CONSTANT
[]
[]
(tutorials/pebbles/solid.i)Finally, we specify that we will run a OpenMC as a sub-application, with data transfers of heat source from OpenMC and temperature to OpenMC and use a transient executioner.
[MultiApps<<<{"href": "../syntax/MultiApps/index.html"}>>>]
[openmc]
type = TransientMultiApp<<<{"description": "MultiApp for performing coupled simulations with the parent and sub-application both progressing in time.", "href": "../source/multiapps/TransientMultiApp.html"}>>>
input_files<<<{"description": "The input file for each App. If this parameter only contains one input file it will be used for all of the Apps. When using 'positions_from_file' it is also admissable to provide one input_file per file."}>>> = 'openmc.i'
execute_on<<<{"description": "The list of flag(s) indicating when this object should be executed. For a description of each flag, see https://mooseframework.inl.gov/source/interfaces/SetupInterface.html."}>>> = timestep_end
[]
[]
[Transfers<<<{"href": "../syntax/Transfers/index.html"}>>>]
[heat_source_from_openmc]
type = MultiAppGeneralFieldNearestLocationTransfer<<<{"description": "Transfers field data at the MultiApp position by finding the value at the nearest neighbor(s) in the origin application.", "href": "../source/transfers/MultiAppGeneralFieldNearestLocationTransfer.html"}>>>
from_multi_app<<<{"description": "The name of the MultiApp to receive data from"}>>> = openmc
variable<<<{"description": "The auxiliary variable to store the transferred values in."}>>> = heat_source
source_variable<<<{"description": "The variable to transfer from."}>>> = heat_source
from_postprocessors_to_be_preserved<<<{"description": "The name of the Postprocessor in the from-app to evaluate an adjusting factor."}>>> = heat_source
to_postprocessors_to_be_preserved<<<{"description": "The name of the Postprocessor in the to-app to evaluate an adjusting factor."}>>> = source_integral
[]
[temp_to_openmc]
type = MultiAppGeneralFieldShapeEvaluationTransfer<<<{"description": "Transfers field data at the MultiApp position using the finite element shape functions from the origin application.", "href": "../source/transfers/MultiAppGeneralFieldShapeEvaluationTransfer.html"}>>>
to_multi_app<<<{"description": "The name of the MultiApp to transfer the data to"}>>> = openmc
variable<<<{"description": "The auxiliary variable to store the transferred values in."}>>> = temp
source_variable<<<{"description": "The variable to transfer from."}>>> = temp
[]
[]
[Postprocessors<<<{"href": "../syntax/Postprocessors/index.html"}>>>]
[source_integral]
type = ElementIntegralVariablePostprocessor<<<{"description": "Computes a volume integral of the specified variable", "href": "../source/postprocessors/ElementIntegralVariablePostprocessor.html"}>>>
variable<<<{"description": "The name of the variable that this object operates on"}>>> = heat_source
execute_on<<<{"description": "The list of flag(s) indicating when this object should be executed. For a description of each flag, see https://mooseframework.inl.gov/source/interfaces/SetupInterface.html."}>>> = transfer
[]
[max_T]
type = NodalExtremeValue<<<{"description": "Finds either the min or max elemental value of a variable over the domain.", "href": "../source/postprocessors/NodalExtremeValue.html"}>>>
variable<<<{"description": "The name of the variable that this postprocessor operates on"}>>> = temp
[]
[]
[Executioner<<<{"href": "../syntax/Executioner/index.html"}>>>]
type = Transient
petsc_options_iname = '-pc_type -pc_hypre_type'
num_steps = 3
petsc_options_value = 'hypre boomeramg'
nl_abs_tol = 1e-10
[]
[Outputs<<<{"href": "../syntax/Outputs/index.html"}>>>]
exodus<<<{"description": "Output the results using the default settings for Exodus output."}>>> = true
[]
(tutorials/pebbles/solid.i)Neutronics Input Files
The neutronics physics is solved over the entire domain using OpenMC. The OpenMC wrapping is described in the openmc.i
input file. We begin by defining a mesh mirror on which OpenMC will receive temperature from the coupled MOOSE application, and on which OpenMC will write the fission heat source.
[Mesh<<<{"href": "../syntax/Mesh/index.html"}>>>]
[pebble]
type = SphereMeshGenerator<<<{"description": "Generate a 3-D sphere mesh centered on the origin", "href": "../source/meshgenerators/SphereMeshGenerator.html"}>>>
nr<<<{"description": "Number of radial elements"}>>> = 2
radius<<<{"description": "Sphere radius"}>>> = 0.015
[]
[repeat]
type = CombinerGenerator<<<{"description": "Combine multiple meshes (or copies of one mesh) together into one (disjoint) mesh. Can optionally translate those meshes before combining them.", "href": "../source/meshgenerators/CombinerGenerator.html"}>>>
inputs<<<{"description": "The input MeshGenerators. This can either be N generators or 1 generator. If only 1 is given then 'positions' must also be given."}>>> = pebble
positions<<<{"description": "The (optional) position of each given mesh. If N 'inputs' were given then this must either be left blank or N positions must be given. If 1 input was given then this MUST be provided."}>>> = '0 0 0.02
0 0 0.06
0 0 0.10'
[]
[]
(tutorials/pebbles/openmc.i)Even though OpenMC solves in units of centimeters, the mesh put in the [Mesh]
block must use the same units as in the coupled MOOSE application. Otherwise, the transfers used to send temperature and heat source to/from OpenMC will not map to the correct elements across applications. For instance, if the [Mesh]
in the solid.i
input were in units of meters, but the [Mesh]
in the openmc.i
input were in units of centimeters, then a point m in solid.i
would get mapped to the node closest to the point cm in openmc.i
(when we actually want the point to map to cm).
Cardinal will also automatically output a variable named cell_id
(CellIDAux) and a variable named cell_instance
( CellInstanceAux) to show the spatial mapping. We also define a CellTemperatureAux to show the OpenMC volume-averaged temperatures as they map to the [Mesh]
.
[AuxVariables<<<{"href": "../syntax/AuxVariables/index.html"}>>>]
[cell_temperature]
family<<<{"description": "Specifies the family of FE shape functions to use for this variable"}>>> = MONOMIAL
order<<<{"description": "Specifies the order of the FE shape function to use for this variable (additional orders not listed are allowed)"}>>> = CONSTANT
[]
[]
[AuxKernels<<<{"href": "../syntax/AuxKernels/index.html"}>>>]
[cell_temperature]
type = CellTemperatureAux<<<{"description": "OpenMC cell temperature (K), mapped to each MOOSE element", "href": "../source/auxkernels/CellTemperatureAux.html"}>>>
variable<<<{"description": "The name of the variable that this object applies to"}>>> = cell_temperature
[]
[]
(tutorials/pebbles/openmc.i)The [Problem]
and [Tallies]
blocks are then used to specify the OpenMC wrapping. We define a total power of 1500 W, and indicate that we'd like to add a CellTally on block 0, which corresponds to the pebbles. The cell tally setup in Cardinal will then automatically add a tally for each unique cell ID+instance combination. Because the repeated pebble cells we'd like to tally are repeated in the lattice nested one level below the root universe, we set the cell_level = 1
.
[Problem<<<{"href": "../syntax/Problem/index.html"}>>>]
type = OpenMCCellAverageProblem
verbose = true
power = 1500.0
temperature_blocks = '0'
cell_level = 1
scaling = 100.0
[Tallies<<<{"href": "../syntax/Problem/Tallies/index.html"}>>>]
[heat_source]
type = CellTally<<<{"description": "A class which implements distributed cell tallies.", "href": "../source/tallies/CellTally.html"}>>>
block<<<{"description": "Subdomains for which to add tallies in OpenMC. If not provided, cell tallies will be applied over the entire mesh."}>>> = '0'
name<<<{"description": "Auxiliary variable name(s) to use for OpenMC tallies. If not specified, defaults to the names of the scores"}>>> = heat_source
[]
[]
[]
(tutorials/pebbles/openmc.i)The scaling
parameter is used to indicate a multiplicative factor that should be applied to the [Mesh]
in order to get to units of centimeters. Because the [Mesh]
is in units of meters, we set scaling = 100.0
. This scaling factor is applied within the OpenMCCellAverageProblem::findCell
routine that maps MOOSE elements to OpenMC cells - no actual changes are made to the mesh in the [Mesh]
block. The scaling is also applied to ensure that the heat source is on the correct per-unit-volume scale that is expected by the [Mesh]
.
Finally, we set a transient executioner, specify an Exodus output, and define several postprocessors.
[Executioner<<<{"href": "../syntax/Executioner/index.html"}>>>]
type = Transient
[]
[Outputs<<<{"href": "../syntax/Outputs/index.html"}>>>]
exodus<<<{"description": "Output the results using the default settings for Exodus output."}>>> = true
csv<<<{"description": "Output the scalar variable and postprocessors to a *.csv file using the default CSV output."}>>> = true
[]
[Postprocessors<<<{"href": "../syntax/Postprocessors/index.html"}>>>]
[heat_source]
type = ElementIntegralVariablePostprocessor<<<{"description": "Computes a volume integral of the specified variable", "href": "../source/postprocessors/ElementIntegralVariablePostprocessor.html"}>>>
variable<<<{"description": "The name of the variable that this object operates on"}>>> = heat_source
[]
[max_tally_rel_err]
type = TallyRelativeError<<<{"description": "Maximum/minimum tally relative error", "href": "../source/postprocessors/TallyRelativeError.html"}>>>
[]
[k]
type = KEigenvalue<<<{"description": "k eigenvalue computed by OpenMC", "href": "../source/postprocessors/KEigenvalue.html"}>>>
[]
[]
(tutorials/pebbles/openmc.i)Execution and Postprocessing
To run the coupled calculation,
mpiexec -np 2 cardinal-opt -i solid.i --n-threads=2
This will run both MOOSE and OpenMC with 8 MPI processes and 2 OpenMP threads per rank. When the simulation has completed, you will have created a number of different output files:
solid_out.e
, an Exodus file with the solid mesh and solutionsolid_out_openmc0.e
, an Exodus file with the OpenMC solution and the data that was ultimately transferred in/out of OpenMC
First, let's examine how the mapping between OpenMC and MOOSE was established. When we run with verbose = true
, you will see the following mapping information displayed:
===================> MAPPING FROM OPENMC TO MOOSE <===================
T: # elems providing temperature feedback
T+rho: # elems providing temperature and density feedback
Other: # elems which do not provide feedback to OpenMC
(but receives a cell tally from OpenMC)
Mapped Vol: volume of MOOSE elems each cell maps to
Actual Vol: OpenMC cell volume (computed with 'volume_calculation')
--------------------------------------------------------------------------
| Cell | T | T+rho | Other | Mapped Vol | Actual Vol |
--------------------------------------------------------------------------
| 1, instance 0 (of 3) | 448 | 0 | 0 | 1.322e-05 | |
| 1, instance 1 (of 3) | 448 | 0 | 0 | 1.322e-05 | |
| 1, instance 2 (of 3) | 448 | 0 | 0 | 1.322e-05 | |
--------------------------------------------------------------------------
The three cells representing the pebbles are mapped to the corresponding MOOSE elements for each pebble. Shown below is the heat source computed by OpenMC, along with the temperature computed by MOOSE (before and after being averaged and sent to OpenMC cells). The temperature is shown on a cut plane through the centers of the pebbles; because one temperature is set for each pebble, the temperature in OpenMC for each pebble is an average over the pebble.

Figure 3: Heat source and temperature computed by a coupled OpenMC-MOOSE model of pebbles
You can confirm that the scaling
factor was applied correctly by comparing the heat_source
postprocessor, of the volume integral of the heat source, with the total power specified in OpenMCCellAverageProblem - both are equal to 1500 W. If the integral is off by a factor of from the power
, then you know right away that the scaling
was not applied correctly.
Repeated Mesh Tallies
Many geometries of interest to nuclear reactor analysis contain repeated structures - such as pebbles in a Pebble Bed Reactor (PBR) or pincells in a LWR. In this section, we build upon our model by using an unstructured mesh tally, with a single unstructured mesh translated multiple times throughout the OpenMC geometry. In other words, if the memory used to store an unstructured mesh of a single pebble is 500 kB, then an unstructured mesh tally of 300,000 pebbles still only requires a mesh storage of 500 kB (as opposed to generating a mesh of 300,000 pebbles that is ~150 GB).
The files for this stage of the coupling are the solid_um.i
and openmc_um.i
inputs in the tutorials/pebbles
directory. For the solid, we simply need to swap out the sub-application to point to a different OpenMC input file.
[MultiApps<<<{"href": "../syntax/MultiApps/index.html"}>>>]
[openmc]
type = TransientMultiApp<<<{"description": "MultiApp for performing coupled simulations with the parent and sub-application both progressing in time.", "href": "../source/multiapps/TransientMultiApp.html"}>>>
input_files<<<{"description": "The input file for each App. If this parameter only contains one input file it will be used for all of the Apps. When using 'positions_from_file' it is also admissable to provide one input_file per file."}>>> = 'openmc_um.i'
execute_on<<<{"description": "The list of flag(s) indicating when this object should be executed. For a description of each flag, see https://mooseframework.inl.gov/source/interfaces/SetupInterface.html."}>>> = timestep_end
[]
[]
(tutorials/pebbles/solid_um.i)We also use a finer solid pebble heat conduction mesh to provide an example of the case where the OpenMC [Mesh]
block differs from the mesh used in the coupled MOOSE application. To do this, we increase nr
to add more radial discretization.
[Mesh<<<{"href": "../syntax/Mesh/index.html"}>>>]
[pebble]
type = SphereMeshGenerator<<<{"description": "Generate a 3-D sphere mesh centered on the origin", "href": "../source/meshgenerators/SphereMeshGenerator.html"}>>>
nr<<<{"description": "Number of radial elements"}>>> = 4
radius<<<{"description": "Sphere radius"}>>> = 0.015
[]
[rotate]
# we can rotate the pebble, MOOSEs mesh transfers still handle the transfer properly
type = TransformGenerator<<<{"description": "Applies a linear transform to the entire mesh.", "href": "../source/meshgenerators/TransformGenerator.html"}>>>
input<<<{"description": "The mesh we want to modify"}>>> = pebble
transform<<<{"description": "The type of transformation to perform (TRANSLATE, TRANSLATE_CENTER_ORIGIN, TRANSLATE_MIN_ORIGIN, ROTATE, SCALE)"}>>> = rotate
vector_value<<<{"description": "The value to use for the transformation. When using TRANSLATE or SCALE, the xyz coordinates are applied in each direction respectively. When using ROTATE, the values are interpreted as the Euler angles phi, theta and psi given in degrees."}>>> = '0.5 0.2 0.2'
[]
[repeat]
type = CombinerGenerator<<<{"description": "Combine multiple meshes (or copies of one mesh) together into one (disjoint) mesh. Can optionally translate those meshes before combining them.", "href": "../source/meshgenerators/CombinerGenerator.html"}>>>
inputs<<<{"description": "The input MeshGenerators. This can either be N generators or 1 generator. If only 1 is given then 'positions' must also be given."}>>> = rotate
positions<<<{"description": "The (optional) position of each given mesh. If N 'inputs' were given then this must either be left blank or N positions must be given. If 1 input was given then this MUST be provided."}>>> = '0 0 0.02
0 0 0.06
0 0 0.10'
[]
[]
(tutorials/pebbles/solid_um.i)For the OpenMC wrapping, the only changes required are that we change the added tally to a MeshTally, provide a mesh template with the mesh, and specify the translations to apply to replicate the mesh at the desired end positions in OpenMC's domain. For the mesh tally, let's create a mesh for a single pebble using MOOSE's mesh generators. We simply need to run the mesh.i
file in --mesh-only
mode:
[Mesh<<<{"href": "../syntax/Mesh/index.html"}>>>]
[pebble]
type = SphereMeshGenerator<<<{"description": "Generate a 3-D sphere mesh centered on the origin", "href": "../source/meshgenerators/SphereMeshGenerator.html"}>>>
nr<<<{"description": "Number of radial elements"}>>> = 2
radius<<<{"description": "Sphere radius"}>>> = 0.015
[]
[]
(tutorials/pebbles/mesh.i)
cardinal-opt -i mesh.i --mesh-only
which will create a mesh file named mesh_in.e
. We then list that mesh as the mesh_template
in the [Tallies]
block.
[Problem<<<{"href": "../syntax/Problem/index.html"}>>>]
type = OpenMCCellAverageProblem
verbose = true
power = 1500.0
temperature_blocks = '0'
normalize_by_global_tally = false
cell_level = 1
scaling = 100.0
[Tallies<<<{"href": "../syntax/Problem/Tallies/index.html"}>>>]
[heat_source]
type = MeshTally<<<{"description": "A class which implements unstructured mesh tallies.", "href": "../source/tallies/MeshTally.html"}>>>
mesh_translations = '0 0 0.02
0 0 0.06
0 0 0.10'
mesh_template<<<{"description": "Mesh tally template for OpenMC when using mesh tallies; at present, this mesh must exactly match the mesh used in the [Mesh] block because a one-to-one copy is used to get OpenMC's tally results on the [Mesh]."}>>> = mesh_in.e
name<<<{"description": "Auxiliary variable name(s) to use for OpenMC tallies. If not specified, defaults to the names of the scores"}>>> = heat_source
[]
[]
[]
(tutorials/pebbles/openmc_um.i)Note that the mesh template and mesh translations must be in the same units as the [Mesh]
block. In addition, because our sphere mesh does not perfectly preserve the volume of the sphere cells, we set normalize_by_global_tally
to false so that we normalize only by the sum of the mesh tally. Otherwise, we would miss a small amount of power produced within the spheres, but slightly outside the faceted surface of the sphere mesh. Setting this parameter to false ensures that the tally normalization is correct in that the heat sources are normalized by a tally sum over the same tally domain in the OpenMC model.
To run this input,
mpiexec -np 2 cardinal-opt -i solid_um.i --n-threads=2
Figure 4 shows the heat source computed by OpenMC, the heat source applied in MOOSE, the temperature computed by MOOSE, and the temperature applied in OpenMC. The MOOSE transfers handle the different meshes seamlessly. The mesh translation feature allows a single pebble mesh to be translated to three individual positions in the OpenMC tally. Note that the heat sources on the OpenMC [Mesh]
and on the MOOSE heat conduction mesh are shown on a different color scale - the volumetric power density on the MOOSE mesh is lower than that on the OpenMC mesh because the MOOSE mesh has a greater volume (since there is a better approximation of the sphere volume). The conservative MultiAppGeneralFieldNearestLocationTransfer conserves total integrated power.

Figure 4: Temperature and heat source distributions shown on the different MOOSE and OpenMC meshes. The temperatures are shown on a slice through the centers of the pebbles.
Finally, recall that adding unstructured mesh tallies does not affect the resolution of temperature and density feedback sent to OpenMC - this resolution is controlled by the cell definitions when constructing the OpenMC input. Therefore, each pebble still receives a single temperature value from MOOSE because each pebble is represented as a single cell.