3. Building input files for a fast reactor
The true power of ARMI comes when you have a reactor at your fingertips. To get this, you must describe the reactor via input files. This tutorial will walk you through building input files from scratch for a reactor. We will model the CR=1.0 sodium-cooled fast reactor documented in ANL-AFCI-177. The full documentation for input files is available here.
Tip
The full inputs created in this tutorial are available for download at the bottom of this page.
3.1. Setting up the blueprints
First we’ll set up the fuel assembly design in the blueprints input. Make a new
file called anl-afci-177-blueprints.yaml
. We’ll be entering information
based on Table 4.4 of the reference. To define the pin cell we need dimensions
of the fuel pin, cladding, ducts, wire wrap, and so on.
The cladding dimensions are clear from the table. The outer diameter is given
as the pin diameter, and the inner diameter is simply that minus twice the
cladding thickness. We will use the Circle
shape, and make the material
HT9
steel. Since we’re inputting cold dimensions, we’ll set Tinput
to
room temperature and let ARMI thermally expand the clad up to an average
operating temperature of 450 °C. Lastly, since there are 271 pins in the
assembly, we’ll set the mult
(short for multiplicity) to 271:
blocks:
fuel: &block_fuel
clad:
shape: Circle
material: HT9
Tinput: 25.0
Thot: 450.0
id: 0.6962
od: 0.808
mult: 271
Note
In fast reactors, neutrons aren’t as affected by spatial details at a
pin level, and compositions are often quite spatially flat across an assembly.
Thus we can often just copy a component using the mult
input equal to the
number of pins, and neutronic modeling is sufficient. For subchannel T/H, the
spatial details are of course much more important.
Note
The &block_fuel
is a YAML anchor which will be discussed more below.
Next, let’s enter the wire wrap. This is a helical wire used in fast reactors
to mix coolant and keep pins separate (used in lieu of a grid spacer). ARMI has
a special shape for this, called a Helix
. Helices are defined by their
axial pitch (how much linear distance between two wrappings axially), the wire
diameter, and the diameter of the pin they’re wrapping around (called
helixDiameter
). Thus, we input the wire wrap into the blueprints as
follows.
Note
The wire axial pitch isn’t specified in the table so we just use a typical value of 30 cm.
wire:
shape: Helix
material: HT9
Tinput: 25.0
Thot: 450.0
axialPitch: 30.0
helixDiameter: 0.8888
id: 0.0
od: 0.0808
mult: 271
We set the wire inner diameter to 0 to make it a solid wire. If we set it to something non-zero, the wire itself would be hollow on the inside, which would be crazy.
Now, it’s time to do the fuel. This example reactor uses UZr metal fuel with a liquid sodium thermal bond in the large gap between the fuel and the cladding. The fraction of space inside the clad that is fuel is called the “smeared density”, so we can figure out the actual fuel slug dimensions from the information in the table.
Specifically, the smeared density is 75%, which means that 75% of the area inside the circle made by the inner diameter of the cladding (0.6962 cm) is fuel. Thus, the fuel outer diameter is given by solving:
which gives \(d = 0.6029\), our fuel outer diameter. Now we can enter our fuel slug component into blueprints:
fuel:
shape: Circle
material: UZr
Tinput: 25.0
Thot: 500.0
id: 0.0
mult: 271
od: 0.6029
Note
We upped the hot temperature to 500 °C, indicative of the fact that fuel will be running a bit hotter than cladding.
Let’s enter a description of the thermal bond next. This is an annulus of sodium between the fuel and the cladding. Since those dimensions are already set, we will use linked dimensions. Thus, no numbers (beyond temperatures) are needed!
bond:
shape: Circle
material: Sodium
Tinput: 447.0
Thot: 447.0
id: fuel.od
mult: fuel.mult
od: clad.id
The next physical component we need to model is the hexagonal assembly duct.
This information is provided in Table 4.3 of ANL-AFCI-177. For the Hexagon
shape, we enter inner and outer flat-to-flat distances (“pitch”) instead of
diameters. The outer pitch is given as 15.710
, and we can calculate the
inner pitch from that and the duct thickness. It ends up looking like this:
duct:
shape: Hexagon
material: HT9
Tinput: 25.0
Thot: 450.0
ip: 14.922
op: 15.710
mult: 1.0
It’s essential to capture the spacing between adjacent ducts too (the assembly
pitch, also defined in Table 4.3), and we define this by defining a special
Hexagon
full of interstitial coolant outside the duct:
intercoolant:
shape: Hexagon
material: Sodium
Tinput: 450.0
Thot: 450.0
ip: duct.op
op: 16.142
mult: 1.0
That defines everything in our assembly except for the coolant. The shape of
the coolant is geometrically complex, it’s a hexagon with holes punched
through it (one for each cladding tube/wire wrap). Rather than explicitly
defining this shape, ARMI allows you to input a DerivedShape
in certain
conditions (e.g. when the rest of the assembly is filled and only one
DerivedComponents
is defined. It can simply back-calculate the area of this
shape automatically. And that’s just what we’ll do with the coolant:
coolant:
shape: DerivedShape
material: Sodium
Tinput: 450.0
Thot: 450.0
And that completes our generic fuel block description.
3.1.1. Defining non-fuel blocks
For this core model, we will need some reflectors, shields, and control blocks as well. In detailed models, it can often be important to model these in detail. For this example, we’ll keep it simple. Control blocks will simply be filled with sodium (representing an all-rods-out condition), reflectors will just be full pins of HT9 steel with coolant around them, and shields will be unclad B4C in sodium. Normally the pin sizes would be different, but again for simplicity, we’re just duplicating the pin dimensions.
For brevity, we will simply provide the definitions as described.
3.1.1.1. Radial Shields
Here is a very simplified radial shield:
radial shield: &block_shield
control:
shape: Circle
material: B4C
Tinput: 597.0
Thot: 597.0
id: 0.0
od: 0.6962
mult: 271
duct:
shape: Hexagon
material: HT9
Tinput: 25.0
Thot: 450.0
ip: 14.922
op: 15.710
mult: 1.0
intercoolant:
shape: Hexagon
material: Sodium
Tinput: 447.0
Thot: 447.0
ip: duct.op
mult: 1.0
op: 16.142
coolant:
shape: DerivedShape
material: Sodium
Tinput: 450.0
Thot: 450.0
3.1.1.2. Reflectors
Here is a reflector block definition. We can use this for radial reflectors and axial reflectors. We include wire wrap so the axial reflector will work with our basic thermal hydraulic solver:
reflector: &block_reflector
reflector:
shape: Circle
material: HT9
Tinput: 450.0
Thot: 450.0
id: 0.0
od: 0.6962
mult: 271
wire:
shape: Helix
material: HT9
Tinput: 25.0
Thot: 450.0
axialPitch: 30.0
helixDiameter: 0.777
id: 0.0
od: 0.0808
mult: 271
duct:
shape: Hexagon
material: HT9
Tinput: 25.0
Thot: 450.0
ip: 14.922
op: 15.710
mult: 1.0
intercoolant:
shape: Hexagon
material: Sodium
Tinput: 447.0
Thot: 447.0
ip: duct.op
mult: 1.0
op: 16.142
coolant:
shape: DerivedShape
material: Sodium
Tinput: 450.0
Thot: 450.0
3.1.1.3. Control
Here is a big empty sodium duct (what you’d find below a control absorber bundle):
control: &block_control
duct:
shape: Hexagon
material: HT9
Tinput: 25.0
Thot: 450.0
ip: 14.922
op: 15.710
mult: 1.0
intercoolant:
shape: Hexagon
material: Sodium
Tinput: 447.0
Thot: 447.0
ip: duct.op
op: 16.142
mult: 1.0
coolant:
shape: DerivedShape
material: Sodium
Tinput: 450.0
Thot: 450.0
3.1.1.4. Plenum
We also need to define empty cladding tubes above the fuel for the fission
gasses to accumulate in. This just has a gap
component made of the Void
material, which is just empty space:
plenum: &block_plenum
clad:
shape: Circle
material: HT9
Tinput: 25.0
Thot: 450.0
id: 0.6962
od: 0.808
mult: 271
wire:
shape: Helix
material: HT9
Tinput: 25.0
Thot: 450.0
axialPitch: 30.0
helixDiameter: 0.88888
id: 0.0
od: 0.0808
mult: 271
gap:
shape: Circle
material: Void
Tinput: 450.0
Thot: 450.0
id: 0.0
od: clad.id
mult: 271
duct:
shape: Hexagon
material: HT9
Tinput: 25.0
Thot: 450.0
ip: 14.922
op: 15.710
mult: 1.0
intercoolant:
shape: Hexagon
material: Sodium
Tinput: 447.0
Thot: 447.0
ip: duct.op
mult: 1.0
op: 16.142
coolant:
shape: DerivedShape
material: Sodium
Tinput: 450.0
Thot: 450.0
That should be enough to define the whole core.
3.1.2. Defining how the blocks are arranged into assemblies
With block cross-sections defined, we now set their heights and stack them up into assemblies. While we’re at it, we can conveniently adjust some frequently-modified material parameters, such as the uranium enrichment.
3.1.2.1. Defining the fuel assemblies
There are three fuel assemblies defined in ANL-AFCI-177, each with different
enrichments. We can specify some assembly data to be shared across all
assemblies and just overlay the differences. We define the assemblies
section of the blueprints input file. We get core and plenum height from table
4.4, and split the core into 5 equally-sized sections at 20.32 cm tall each.
This defines the depletion mesh. Each of these 5 blocks will deplete and
accumulate state independently. In the axial mesh points
section, we
specify a roughly even neutronic/transport mesh, with slightly larger neutronic
mesh points in the very tall single-block plenum:
assemblies:
heights: &heights
- 15.0
- 20.32
- 20.32
- 20.32
- 20.32
- 20.32
- 191.14
axial mesh points: &mesh
- 1
- 2
- 2
- 2
- 2
- 2
- 8
Now that the common heights and neutronic mesh are specified, we start applying them to the various individual assemblies. We start with the inner core and refer to the heights and mesh with YAML anchors. As described in Section 2.0 of the reference, an enrichment splitting of 1.0, 1.25, and 1.5 was used for inner, middle, and outer core in order to help minimize radial power peaking. The specific enrichments of each zone are shown in Table 4.8. For simplicity, let’s just use these as uranium enrichments rather than the detailed material from the paper. Specifying more details is possible via the custom isotopics input fields.:
inner fuel:
specifier: IC
blocks: &fuel_blocks
- *block_reflector
- *block_fuel
- *block_fuel
- *block_fuel
- *block_fuel
- *block_fuel
- *block_plenum
height: *heights
axial mesh points: *mesh
material modifications:
U235_wt_frac:
- ''
- 0.127
- 0.127
- 0.127
- 0.127
- 0.127
- ''
xs types: &IC_xs
- A
- A
- A
- A
- A
- A
- A
Warning
The weirdest thing about this input section is the use of YAML
anchors for the blocks. Under the hood, this copies the entire block definition
into each entry of that list. This is a bit strange, and we plan to switch this
to a string-based block name rather than a full YAML anchor in the blocks
list.
Note
Notice the blank strings in the U235_wt_frac
section? Those are
placeholders indicating that the material in those blocks does not have uranium
in it, and thus adjusting uranium enrichment doesn’t make sense. These are the
axial reflectors, plena, grid plates, etc.
For the middle core, we can use the same stack of blocks (using an anchor), but
we need different enrichments. We can choose whether or not to use the same
xs types
. When composition is different, one often uses independent cross
section types so you get cross sections specific to different enrichments. This
is a trade-off, since more cross section types means more lattice physics
calculations, which can require either more time or more processors:
middle core fuel:
specifier: MC
blocks: *fuel_blocks
height: *heights
axial mesh points: *mesh
material modifications:
U235_wt_frac:
- ''
- 0.153
- 0.153
- 0.153
- 0.153
- 0.153
- ''
xs types:
- B
- B
- B
- B
- B
- B
- B
Same deal for the outer core.
Note
The columnar form of YAML lists is very convenient when using text editors with column-edit capabilities. It is highly recommended to make sure you know how to column edit.
outer core fuel:
specifier: OC
blocks: *fuel_blocks
height: *heights
axial mesh points: *mesh
material modifications:
U235_wt_frac:
- ''
- 0.180
- 0.180
- 0.180
- 0.180
- 0.180
- ''
xs types:
- C
- C
- C
- C
- C
- C
- C
3.1.2.2. Defining the non-fuel assemblies
Let’s make some shield, reflector, and control assemblies. It’s fine for these to have different numbers of blocks. Some physics kernels (like DIF3D) have some requirements of axial mesh boundaries at least lining up between assemblies, but there are some ARMI features that can automatically adjust the mesh if you have very complicated assemblies:
radial reflector:
specifier: RR
blocks: [*block_reflector]
height: [307.74]
axial mesh points: [1]
xs types: [A]
Note
Here we just re-use the fuel block cross sections. In more precise models, a different approach may be used.
Here is the radial shield:
radial shield:
specifier: SH
blocks: [*block_shield]
height: [307.74]
axial mesh points: [1]
xs types: [A]
Here are the control blocks:
control:
specifier: PC
blocks: [*block_control]
height: [307.74]
axial mesh points: [1]
xs types: [A]
ultimate shutdown:
specifier: US
blocks: [*block_control]
height: [307.74]
axial mesh points: [1]
xs types: [A]
And that’s it! All blueprints are now defined.
3.2. Specifying the core map
With blueprints defined we can now arrange assemblies into the core. This is with the geometry input file.
Note
There are GUI tools to help making the core map easy to set up.
Note
We plan to converge on consistent input between pin maps and core maps for the physics kernels and analyses that require finer detail of how the pins are arranged within blocks.
Geometry can be input various ways. The most straightforward is to provide a
simple ASCII-based map of the core. For this problem, a 1/3 hexagonal model
can be input as follows (see Figure 4.3 in the reference). First, we refer to a
geometry file from the systems
section of the blueprints
file:
systems:
core:
grid name: core
origin:
x: 0.0
y: 0.0
z: 0.0
grids:
core:
!include anl-afci-177-coreMap.yaml
And then, in the core map file (anl-afci-177-coreMap.yaml
):
geom: hex
symmetry: third periodic
lattice map: |
- SH SH SH
- SH SH SH SH
SH RR RR RR SH
RR RR RR RR SH
RR RR RR RR RR SH
RR OC OC RR RR SH
OC OC OC RR RR SH
OC OC OC OC RR RR
OC MC OC OC RR SH
MC MC PC OC RR SH
MC MC MC OC OC RR
MC MC MC OC RR SH
PC MC MC OC RR SH
MC MC MC MC OC RR
IC MC MC OC RR SH
IC US MC OC RR
IC IC MC OC RR SH
IC MC MC OC RR
IC IC MC PC RR SH
Note
The two-letter values here can be any contiguous strings, and
correspond with the specifier
field in the blueprints input.
Note
GUI utilities are also useful for building core maps like this.
3.3. Specifying settings
Now we need to specify some settings that define fundamental reactor
parameters, as well as modeling approximation options. For this, we make a
settings file, called anl-afci-177.yaml
.
The thermal power in this reference is 1000 MWt. The thermal efficiency isn’t specified, so let’s assume 0.38. From Table 4.8, the cycle length is 370 EFPD. Let’s also assume a 0.90 capacity factor which will gives full cycles of 411.1 days.
settings:
availabilityFactor: 0.9
power: 1000000000.0
cycleLength: 411.11
We need to tell the system which other input files to load by bringing in the blueprints and geometry (the shuffling and fuel handler info will be described momentarily):
loadingFile: anl-afci-177-blueprints.yaml
shuffleLogic: anl-afci-177-fuelManagement.py
fuelHandlerName: SampleShuffler
In terms of our simulation parameters, let’s run it for 10 cycles, with 2 depletion time steps per cycle:
nCycles: 10
burnSteps: 2
Set some physics kernel and environment options:
buGroups:
- 100
comment: ANL-AFCI-177 CR 1.0 metal core but with HALEU instead of TRU
genXS: Neutron
nTasks: 1
versions:
armi: uncontrolled
Note
The ARMI GUI is simply an optional frontend to this settings file. Behind the scenes it just reads and writes this. It is quite convenient for discovering important settings and describing what they do, however.
3.4. Defining fuel management
Finally, let’s specify the fuel management file that we referred to above by
creating the file anl-afci-177-fuelManagement.py
. Fuel management is very
wide-open, so we use Python scripts to drive it. It’s generally overly
constraining to require any higher-level input for such a general problem.
In ANL-AFCI-177, section 2 says no shuffling was modeled, and that the core is in a batch shuffling mode, limited by a cladding fast fluence of 4.0e23 n/cm2. Often, SFR studies use the REBUS code’s implicit equilibrium fuel cycle mode. There is an ARMI equilibrium module at TerraPower that performs this useful calculation (with different inputs), but for this sample problem, we will simply model 10 cycles with explicit fuel management.
The shuffling algorithm we’ll write will simply predict whether or not the stated fluence limit will be violated in the next cycle. If it will be, the fuel assembly will be replaced with a fresh one of the same kind.
# Copyright 2019 TerraPower, LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from armi.utils import units
from armi.physics.fuelCycle.fuelHandlers import FuelHandler
class SampleShuffler(FuelHandler):
def chooseSwaps(self, shuffleParameters):
cycleSeconds = (
self.r.p.cycleLength * self.r.p.availabilityFactor * units.SECONDS_PER_DAY
)
for a in self.r.core:
peakFluence = a.getMaxParam("fastFluence")
peakFlux = a.getMaxParam("fastFlux")
if peakFluence + peakFlux * cycleSeconds > 4.0e23:
newAssem = self.r.core.createAssemblyOfType(a.getType())
self.dischargeSwap(newAssem, a)
def getFactorList(self, cycle, cs=None):
"""Parameters here can be used to adjust shuffling philosophy vs. cycle."""
return {}, []
There! You have now created all the ARMI inputs, from scratch, needed to perform a simplified reactor analysis of one of the SFRs in the ANL-AFCI-177 document. The possibilities from here are only limited by your creativity, (and a few code limitations ;).
As you load the inputs in ARMI it will provide some consistency checks and errors to help identify common mistakes.
Here are the full files used in this example:
The next tutorial will guide you through inputs for a classic LWR benchmark problem (C5G7).