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:

\[0.75 = \frac{\pi d^2}{\pi 0.6962^2}\]

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
  numProcessors: 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).