7. Parameter sweeps

Parameter sweeps allow you to quickly and easily build a series of related cases that all change one or more aspects of the input model or modeling approximations. Because ARMI automates full-scope engineering analysis, ARMI-driven parameter sweeps are extremely useful for design exploration, sensitivity studies, and statistical analysis.

To get started with a parameter sweep, you first need some inputs.

  • Blueprints

  • Settings

  • Core map

  • Fuel management

Next, you need an app and a Case object as the starting point.

[1]:
# you can only configure an app once
import armi
if not armi.isConfigured():
    armi.configure(armi.apps.App())

       +===================================================+
       |            _      ____     __  __    ___          |
       |           / \    |  _ \   |  \/  |  |_ _|         |
       |          / _ \   | |_) |  | |\/| |   | |          |
       |         / ___ \  |  _ <   | |  | |   | |          |
       |        /_/   \_\ |_| \_\  |_|  |_|  |___|         |
       |        Advanced  Reactor  Modeling Interface      |
       |                                                   |
       |                    version 0.4.0                  |
       |                                                   |
       +===================================================+
[2]:
from armi import settings
from armi import cases
from armi.cases import suiteBuilder
from armi.cases.inputModifiers import inputModifiers

cs = settings.Settings('../anl-afci-177/anl-afci-177.yaml')
case = cases.Case(cs)

Next, you make a SuiteBuilder, which is the thing that will perturb the input files to generate a suite of related cases from the base case. There are two basic choices, the FullFactorialSuiteBuilder which will expand each degree of freedom in every combination (a full multi-dimensional matrix), and the SeparateEffectsSuiteBuilder builder, which varies each degree of freedom in isolation. We’ll make a FullFactorial case for this demo.

Once you have a SuiteBuilder, you start adding one or more degrees of freedom, each of which will adjust one aspect of the input definitions (modeling options, reactor design, etc.).

Note

You may also find the more detailed API documentation useful.

7.1. A simple one-dimensional parameter sweep

[3]:
builder = suiteBuilder.SeparateEffectsSuiteBuilder(case)

Each degree of freedom is defined by an InputModifier and a range of values. ARMI contains a few basic InputModifier for simple things (like changing settings), and for design-specific param sweeps you can make your own design-specific modifiers.

The simplest form of parameter sweep just adjusts settings. For example, we could adjust the reactor power from 10 MW to 100 MW in a few steps.

[4]:
import numpy as np

powers = np.linspace(10,100,4)
print(f"Building power modifiers with powers: {powers}")
powerModifications = [inputModifiers.SettingsModifier('power', mw*1e6) for mw in powers]
builder.addDegreeOfFreedom(powerModifications)
print(f"There are {len(builder.modifierSets)} cases in this suite so far.")
Building power modifiers with powers: [ 10.  40.  70. 100.]
There are 4 cases in this suite so far.

Now we can build the suite. The Suite object itself can write input files or just run on the local computer with suite.run.

The suite will generate copies of the base case with the power modified across the defined range.

[5]:
suite = builder.buildSuite()
suite.echoConfiguration()
[info] Will expand HE, NA, SI, V, CR, MN, FE, NI, ZR, MO, W elementals to have natural isotopics
[info] Constructing assembly `inner fuel`
[warn] No component matched Flags.CLAD in <reflector block-bol-000 at ExCore XS: A ENV GP: A>. Returning None
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: A ENV GP: A> so pin-to-duct gap not calculated
[warn] The gap between wire wrap and clad in block <plenum block-bol-006 at ExCore XS: A ENV GP: A> was 3.999999999998449e-05 cm. Expected 0.0.
[info] Constructing assembly `middle core fuel`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: B ENV GP: A> so pin-to-duct gap not calculated
[warn] The gap between wire wrap and clad in block <plenum block-bol-006 at ExCore XS: B ENV GP: A> was 3.999999999998449e-05 cm. Expected 0.0.
[info] Constructing assembly `outer core fuel`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: C ENV GP: A> so pin-to-duct gap not calculated
[warn] The gap between wire wrap and clad in block <plenum block-bol-006 at ExCore XS: C ENV GP: A> was 3.999999999998449e-05 cm. Expected 0.0.
[info] Constructing assembly `radial reflector`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: A ENV GP: A> so pin-to-duct gap not calculated
[info] Constructing assembly `radial shield`
[warn] Temperature 597.0 out of range (25 to 500) for B4C linear expansion percent
[warn] No component matched Flags.WIRE in <radial shield block-bol-000 at ExCore XS: A ENV GP: A>. Returning None
[info] Constructing assembly `control`
[info] Constructing assembly `ultimate shutdown`
=========== Verifying Assembly Configurations ===========
[info] Will expand HE, NA, SI, V, CR, MN, FE, NI, ZR, MO, W elementals to have natural isotopics
[info] Constructing assembly `inner fuel`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: A ENV GP: A> so pin-to-duct gap not calculated
[info] Constructing assembly `middle core fuel`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: B ENV GP: A> so pin-to-duct gap not calculated
[info] Constructing assembly `outer core fuel`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: C ENV GP: A> so pin-to-duct gap not calculated
[info] Constructing assembly `radial reflector`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: A ENV GP: A> so pin-to-duct gap not calculated
[info] Constructing assembly `radial shield`
[info] Constructing assembly `control`
[info] Constructing assembly `ultimate shutdown`
=========== Verifying Assembly Configurations ===========
[info] Will expand HE, NA, SI, V, CR, MN, FE, NI, ZR, MO, W elementals to have natural isotopics
[info] Constructing assembly `inner fuel`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: A ENV GP: A> so pin-to-duct gap not calculated
[info] Constructing assembly `middle core fuel`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: B ENV GP: A> so pin-to-duct gap not calculated
[info] Constructing assembly `outer core fuel`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: C ENV GP: A> so pin-to-duct gap not calculated
[info] Constructing assembly `radial reflector`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: A ENV GP: A> so pin-to-duct gap not calculated
[info] Constructing assembly `radial shield`
[info] Constructing assembly `control`
[info] Constructing assembly `ultimate shutdown`
=========== Verifying Assembly Configurations ===========
[info] Will expand HE, NA, SI, V, CR, MN, FE, NI, ZR, MO, W elementals to have natural isotopics
[info] Constructing assembly `inner fuel`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: A ENV GP: A> so pin-to-duct gap not calculated
[info] Constructing assembly `middle core fuel`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: B ENV GP: A> so pin-to-duct gap not calculated
[info] Constructing assembly `outer core fuel`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: C ENV GP: A> so pin-to-duct gap not calculated
[info] Constructing assembly `radial reflector`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: A ENV GP: A> so pin-to-duct gap not calculated
[info] Constructing assembly `radial shield`
[info] Constructing assembly `control`
[info] Constructing assembly `ultimate shutdown`
=========== Verifying Assembly Configurations ===========
[impt] Use the Python Tracer: False
[impt] Turn On the Profiler: False
[impt] Turn On Coverage Report Generation: False
[impt] Worker Log Verbosity: error
[impt] Module-Level Verbosity: {}
[impt] Primary Log Verbosity: info
[impt] Location of Output Cache:
[impt] Test inputs will be taken from test case results when they have finished
[impt] -----------------  ---------  --------------
       Title              Enabled    Dependencies
       -----------------  ---------  --------------
       anl-afci-177-0000  T
       anl-afci-177-0001  T
       anl-afci-177-0002  T
       anl-afci-177-0003  T
       -----------------  ---------  --------------

On the other hand, if you want to write inputs and then submit them all to a high-performance computer, you can do that too with suite.writeInputs()

[6]:
suite.writeInputs()

You can now see that perturbed input files have been produced in the case-suite folder.

[7]:
!grep -R "power:" case-suite/*
case-suite/0000/anl-afci-177-0000.yaml:  power: 10000000.0
case-suite/0001/anl-afci-177-0001.yaml:  power: 40000000.0
case-suite/0002/anl-afci-177-0002.yaml:  power: 70000000.0
case-suite/0003/anl-afci-177-0003.yaml:  power: 100000000.0

To submit this suite to a computer cluster, one would run a series of python -m armi run commands from the case-suite folder. On a HPC, one would submit these commands to the HPC using the queuing system.

7.2. Modifying the reactor design

Modifying settings is one thing, but the real power of parameter sweeps comes from programatically perturbing the reactor component designs themselves. We accomplish this by modifying ARMI Blueprint objects as derived from the base input.

[8]:
class CladThicknessModifier(inputModifiers.InputModifier):
    """Modifier that adjust the cladding outer diameter"""
    def __call__(self, cs, bp, geom):
        for blockDesign in bp.blockDesigns:
            for componentDesign in blockDesign:
                if componentDesign.name == "clad":
                    # by default, values passed to a modifier end up in the
                    # independentVariable dict
                    componentDesign.od = self.independentVariable["cladThickness"]
        return cs, bp, geom

cladThicknesses = np.linspace(0.8, 0.9, 5)
builder = suiteBuilder.SeparateEffectsSuiteBuilder(case)
cladModifications = [CladThicknessModifier({"cladThickness":float(od)}) for od in cladThicknesses]
builder.addDegreeOfFreedom(cladModifications)
suite = builder.buildSuite()
suite.echoConfiguration()
suite.writeInputs()
[info] Will expand HE, NA, SI, V, CR, MN, FE, NI, ZR, MO, W elementals to have natural isotopics
[info] Constructing assembly `inner fuel`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: A ENV GP: A> so pin-to-duct gap not calculated
[info] Constructing assembly `middle core fuel`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: B ENV GP: A> so pin-to-duct gap not calculated
[info] Constructing assembly `outer core fuel`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: C ENV GP: A> so pin-to-duct gap not calculated
[info] Constructing assembly `radial reflector`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: A ENV GP: A> so pin-to-duct gap not calculated
[info] Constructing assembly `radial shield`
[info] Constructing assembly `control`
[info] Constructing assembly `ultimate shutdown`
=========== Verifying Assembly Configurations ===========
[info] Will expand HE, NA, SI, V, CR, MN, FE, NI, ZR, MO, W elementals to have natural isotopics
[info] Constructing assembly `inner fuel`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: A ENV GP: A> so pin-to-duct gap not calculated
[info] Constructing assembly `middle core fuel`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: B ENV GP: A> so pin-to-duct gap not calculated
[info] Constructing assembly `outer core fuel`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: C ENV GP: A> so pin-to-duct gap not calculated
[info] Constructing assembly `radial reflector`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: A ENV GP: A> so pin-to-duct gap not calculated
[info] Constructing assembly `radial shield`
[info] Constructing assembly `control`
[info] Constructing assembly `ultimate shutdown`
=========== Verifying Assembly Configurations ===========
[info] Will expand HE, NA, SI, V, CR, MN, FE, NI, ZR, MO, W elementals to have natural isotopics
[info] Constructing assembly `inner fuel`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: A ENV GP: A> so pin-to-duct gap not calculated
[info] Constructing assembly `middle core fuel`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: B ENV GP: A> so pin-to-duct gap not calculated
[info] Constructing assembly `outer core fuel`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: C ENV GP: A> so pin-to-duct gap not calculated
[info] Constructing assembly `radial reflector`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: A ENV GP: A> so pin-to-duct gap not calculated
[info] Constructing assembly `radial shield`
[info] Constructing assembly `control`
[info] Constructing assembly `ultimate shutdown`
=========== Verifying Assembly Configurations ===========
[info] Will expand HE, NA, SI, V, CR, MN, FE, NI, ZR, MO, W elementals to have natural isotopics
[info] Constructing assembly `inner fuel`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: A ENV GP: A> so pin-to-duct gap not calculated
[info] Constructing assembly `middle core fuel`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: B ENV GP: A> so pin-to-duct gap not calculated
[info] Constructing assembly `outer core fuel`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: C ENV GP: A> so pin-to-duct gap not calculated
[info] Constructing assembly `radial reflector`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: A ENV GP: A> so pin-to-duct gap not calculated
[info] Constructing assembly `radial shield`
[info] Constructing assembly `control`
[info] Constructing assembly `ultimate shutdown`
=========== Verifying Assembly Configurations ===========
[info] Will expand HE, NA, SI, V, CR, MN, FE, NI, ZR, MO, W elementals to have natural isotopics
[info] Constructing assembly `inner fuel`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: A ENV GP: A> so pin-to-duct gap not calculated
[info] Constructing assembly `middle core fuel`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: B ENV GP: A> so pin-to-duct gap not calculated
[info] Constructing assembly `outer core fuel`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: C ENV GP: A> so pin-to-duct gap not calculated
[info] Constructing assembly `radial reflector`
[warn] Some component was missing in <reflector block-bol-000 at ExCore XS: A ENV GP: A> so pin-to-duct gap not calculated
[info] Constructing assembly `radial shield`
[info] Constructing assembly `control`
[info] Constructing assembly `ultimate shutdown`
=========== Verifying Assembly Configurations ===========
[impt] Use the Python Tracer: False
[impt] Turn On the Profiler: False
[impt] Turn On Coverage Report Generation: False
[impt] Worker Log Verbosity: error
[impt] Module-Level Verbosity: {}
[impt] Primary Log Verbosity: info
[impt] Location of Output Cache:
[impt] Test inputs will be taken from test case results when they have finished
[impt] -----------------  ---------  --------------
       Title              Enabled    Dependencies
       -----------------  ---------  --------------
       anl-afci-177-0000  T
       anl-afci-177-0001  T
       anl-afci-177-0002  T
       anl-afci-177-0003  T
       anl-afci-177-0004  T
       -----------------  ---------  --------------

Now we can inspect the input files and see that the cladding outer diameter definition has indeed been modified

[9]:
!grep -R "clad:" -A6 case-suite/* | grep "od:"
case-suite/0000/anl-afci-177-0000-blueprints.yaml-      od: 0.8
case-suite/0000/anl-afci-177-0000-blueprints.yaml-      od: 0.8
case-suite/0001/anl-afci-177-0001-blueprints.yaml-      od: 0.8250000000000001
case-suite/0001/anl-afci-177-0001-blueprints.yaml-      od: 0.8250000000000001
case-suite/0002/anl-afci-177-0002-blueprints.yaml-      od: 0.8500000000000001
case-suite/0002/anl-afci-177-0002-blueprints.yaml-      od: 0.8500000000000001
case-suite/0003/anl-afci-177-0003-blueprints.yaml-      od: 0.875
case-suite/0003/anl-afci-177-0003-blueprints.yaml-      od: 0.875
case-suite/0004/anl-afci-177-0004-blueprints.yaml-      od: 0.9
case-suite/0004/anl-afci-177-0004-blueprints.yaml-      od: 0.9

7.3. A full factorial parameter sweep

Of course, one can use factorial sweeps as well. Below we add two degrees of freedom, one of length 5 and another of length 20. This suite has 100 cases total with all combinations of each setting.

[10]:
builder = suiteBuilder.FullFactorialSuiteBuilder(case)
powers = np.linspace(10,100,5)
powerModifications = [inputModifiers.SettingsModifier('power', mw*1e6) for mw in powers]
builder.addDegreeOfFreedom(powerModifications)

cycleLengths = np.linspace(200,1000,20)
cycleLengthMods = [inputModifiers.SettingsModifier('cycleLength', cL) for cL in cycleLengths]
builder.addDegreeOfFreedom(cycleLengthMods)
print(f"There are {len(builder.modifierSets)} cases in this suite.")
There are 100 cases in this suite.

7.4. Post-processing the results of the sweep

After all the runs have completed in a parameter sweep, you will want to post-process them to come to some kind of useful conclusion. Because post-processing is very design-specific, you need to make a simple post-processing script. The ARMI framework has useful functions that will assist you in this task.

First, we assume you’re in a new shell and we discover all the cases that ran:

[11]:
def loadSuite():
    print('Loading suite results...')
    cs = settings.Settings('../anl-afci-177/anl-afci-177.yaml')
    suite = cases.CaseSuite(cs)
    suite.discover(patterns=["anl-afci-177-????.yaml"])
    suite = sorted(suite, key=lambda c: c.cs.inputDirectory)
    return suite
suite = loadSuite()
Loading suite results...
[info] Finding potential settings files matching ['anl-afci-177-????.yaml'].
[info] Checking for valid settings files.
=========== Settings Validation Checks ===========
=========== Settings Validation Checks ===========
=========== Settings Validation Checks ===========
=========== Settings Validation Checks ===========
=========== Settings Validation Checks ===========

At this point, you have two options based on your needs:

  • Read the ARMI HDF5 output databases directly (useful if you just need to pull certain scalar parameters directly out of the database)

  • Have ARMI load HDF5 output databases into full ARMI reactor objects and use the ARMI API to extract data (useful if you want to loop over certain parts of the plant to sum things up)

Directly reading the database will be inherently less stable (e.g. in case the underlying DB format changes), but can be very fast. Loading ARMI reactors for each case is slower, but should also be more powerful and more stable.

After you extract the data, you can plot it or make tables or anything else you need. We often pass it to non-parametric regression systems like the Alternating Conditional Expectation (ACE) and then on to a multi-objective optimization system (like Physical Programming).