Repository: terrapower/armi

Issue:
Reduce or Eliminate getMasterCs and setMasterCs
The `getMasterCs()` and `setMasterCs()` methods are extremely problematic. First off, obviously, they prevent ARMI from being able to do multiple things at once. We can not do the following currently, because they mean over-writing the "master cs":

* read two ARMI DBs at one time
* have two ARMI Reactors in memory at once
* All unit tests must share the same ARMI `App()`
* any `Block`s in memory must reference the same settings file
* All interfaces have access to their own settings object (`self.cs`), but can also reference the "master cs"

Here are all locations of usage:

### getMasterCs

* [x] `armi/bookkeeping/report/html.py`:    `cs = settings.getMasterCs()`
* [ ] `armi/interfaces.py`:        `self.cs = settings.getMasterCs() if cs is None else cs`
* [ ] `armi/interfaces.py`:        `self.cs = cs or settings.getMasterCs()`
* [ ] `armi/interfaces.py`:        `self.cs = settings.getMasterCs()`
* [ ] `armi/operators/operatorMPI.py`:        `cs = settings.getMasterCs()`
* [x] `armi/reactor/blocks.py`:        `if len(xsType) == 2 and len(settings.getMasterCs()["buGroups"]) == 1:`
* [x] `armi/reactor/blocks.py`:        `burnupPeakingFactor = settings.getMasterCs()["burnupPeakingFactor"]`
* [x] `armi/reactor/blocks.py`:        `if settings.getMasterCs()["gridPlateDpaXsSet"] and self.hasFlags(`
* [x] `armi/reactor/blocks.py`:            `dpaXsSetName = settings.getMasterCs()["gridPlateDpaXsSet"]`
* [x] `armi/reactor/blocks.py`:            `dpaXsSetName = settings.getMasterCs()["dpaXsSet"]`
* [x] `armi/reactor/reactors.py`:        `self.processLoading(settings.getMasterCs())`
* [x] `armi/reactor/reactors.py`:        `self.buildZones(settings.getMasterCs())`
* [x] `armi/reactor/reactors.py`:            `cs or settings.getMasterCs(), name=assemType`
* [ ] `armi/operators/tests/test_operators.py`:            `self.cs = settings.getMasterCs()`

If we can remove all the instances of `getMasterCs()`, we will be able to just delete all `setMasterCs()` for free.

Discussion:
john-science: @sombrereau @opotowsky @drewj-usnctech 

This is an API-breaking change.  As such, I will not be merging it in willy-nilly.  I expect to help people understand the change and how to work with it. And I expect to do a lot of the fixing on internal repos myself.
jakehader: > Now that it's gone I have to wonder... Why was this feature here in the first place?

Convenience to get user configuration data anywhere, anytime, And all the time! It has caused some grief within Interfaces where they just change the behavior of the physics solvers (sometimes forgetting to turn them back to how the user wanted them!) to do things like have convergence criteria or boundary condition assumptions.

I think that removing global CS state marks a good level of maturing in the API and we should continue to codify the relationship between an `Interface`, the reactor data model, and an `Operator` on what they can and cannot do.


john-science: > I'd be super surprised if this doesnt break something downstream for us...

@jakehader Yeah, I will not just merge this one willy-nilly.  I will look downstream and open a bunch of PRs to pre-empt this change before I merge it in.  For sure.  I won't take API changes lightly.

Thanks, man!

albeanth: > @albeanth When merged, will this PR fix your unit testing woes?

I imagine so, yes. But only time will tell as that was a bug that seemed to only occasionally rear its head. 

@john-science before merging, please run this PR through the gamut of downstream tests. If they all come back clean, feel free to merge at will. Otherwise let's touch base about a merge plan. Thanks! 
john-science: > ...before merging, please run this PR through the gamut of downstream tests...

@albeanth I did that. But, of course, the downstream work is frozen for the moment, so we are freed from that constraint until February. 

Thanks!

Patch:
diff --git a/armi/__init__.py b/armi/__init__.py
index dff87ddd..abb40fff 100644
--- a/armi/__init__.py
+++ b/armi/__init__.py
@@ -142,8 +142,6 @@ def init(choice=None, fName=None, cs=None):
         if fName is None:
             fName = settings.promptForSettingsFile(choice)
         cs = settings.Settings(fName)
-    # clear out any old masterCs objects
-    settings.setMasterCs(cs)
 
     armiCase = cases.Case(cs=cs)
     armiCase.checkInputs()
diff --git a/armi/bookkeeping/db/__init__.py b/armi/bookkeeping/db/__init__.py
index bc4e8289..336d4c85 100644
--- a/armi/bookkeeping/db/__init__.py
+++ b/armi/bookkeeping/db/__init__.py
@@ -62,7 +62,6 @@ Database revision changelog
 import os
 from typing import Optional, List, Tuple
 
-from armi import settings
 from armi import runLog
 
 # re-export package components for easier import
@@ -142,8 +141,6 @@ def loadOperator(pathToDb, loadCycle, loadNode, allowMissing=False):
 
         r = db.load(loadCycle, loadNode, allowMissing=allowMissing)
 
-    settings.setMasterCs(cs)
-
     o = thisCase.initializeOperator(r=r)
     runLog.important(
         "The operator will not be in the same state that it was at that cycle and "
diff --git a/armi/bookkeeping/db/database3.py b/armi/bookkeeping/db/database3.py
index 5f8cc3bf..e3f7ccb4 100644
--- a/armi/bookkeeping/db/database3.py
+++ b/armi/bookkeeping/db/database3.py
@@ -680,10 +680,7 @@ class Database3:
         updateGlobalAssemNum : bool, optional
             DeprecationWarning: This is unused.
         updateMasterCs : bool, optional
-            Whether to apply the cs (whether provided as an argument or read from
-            the database) as the primary for the case. Default True. Can be useful
-            if you don't intend to use the loaded reactor as the basis for further
-            computations in the current operator.
+            TODO: Deprecated. Slated for removal.
 
         Returns
         -------
@@ -693,8 +690,6 @@ class Database3:
         runLog.info("Loading reactor state for time node ({}, {})".format(cycle, node))
 
         cs = cs or self.loadCS()
-        if updateMasterCs:
-            settings.setMasterCs(cs)
         bp = bp or self.loadBlueprints()
 
         if node < 0:
diff --git a/armi/cases/suite.py b/armi/cases/suite.py
index 3ed419ec..9320cd35 100644
--- a/armi/cases/suite.py
+++ b/armi/cases/suite.py
@@ -204,7 +204,6 @@ class CaseSuite:
         for ci, case in enumerate(self):
             runLog.important(f"Running case {ci+1}/{len(self)}: {case}")
             with directoryChangers.DirectoryChanger(case.directory):
-                settings.setMasterCs(case.cs)
                 try:
                     case.run()
                 except:  # noqa: bare-except
diff --git a/armi/cli/entryPoint.py b/armi/cli/entryPoint.py
index 052cc8b6..2a9c2fc8 100644
--- a/armi/cli/entryPoint.py
+++ b/armi/cli/entryPoint.py
@@ -94,7 +94,6 @@ class EntryPoint:
             )
 
         self.cs = self._initSettings()
-        settings.setMasterCs(self.cs)
 
         self.parser = argparse.ArgumentParser(
             prog="{} {}".format(context.APP_NAME, self.name),
diff --git a/armi/interfaces.py b/armi/interfaces.py
index 5d48075e..8e96b3ff 100644
--- a/armi/interfaces.py
+++ b/armi/interfaces.py
@@ -274,7 +274,7 @@ class Interface:
         SKIP = 4
 
     def __init__(self, r, cs):
-        r"""
+        """
         Construct an interface.
 
         The ``r`` and ``cs`` arguments are required, but may be ``None``, where
@@ -300,7 +300,7 @@ class Interface:
         self._enabled = True
         self.reverseAtEOL = False
         self._bolForce = False  # override disabled flag in interactBOL if true.
-        self.cs = settings.getMasterCs() if cs is None else cs
+        self.cs = cs
         self.r = r
         self.o = r.o if r else None
         self.coupler = _setTightCouplerByInterfaceFunction(self, cs)
@@ -623,7 +623,7 @@ class InputWriter:
         self.externalCodeInterface = externalCodeInterface
         self.eci = externalCodeInterface
         self.r = r
-        self.cs = cs or settings.getMasterCs()
+        self.cs = cs
 
     def getInterface(self, name):
         """Get another interface by name."""
@@ -652,11 +652,11 @@ class OutputReader:
     and would rather just have an apply(reactor) method.
     """
 
-    def __init__(self, r=None, externalCodeInterface=None, fName=None):
+    def __init__(self, r=None, externalCodeInterface=None, fName=None, cs=None):
         self.externalCodeInterface = externalCodeInterface
         self.eci = self.externalCodeInterface
         self.r = r
-        self.cs = settings.getMasterCs()
+        self.cs = cs
         if fName:
             self.output = textProcessors.TextProcessor(fName)
         else:
diff --git a/armi/mpiActions.py b/armi/mpiActions.py
index 383adf9a..ce9b3223 100644
--- a/armi/mpiActions.py
+++ b/armi/mpiActions.py
@@ -52,12 +52,11 @@ sends it to the workers, and then both the primary and the workers
 
 In order to create a new, custom MPI Action, inherit from :py:class:`~armi.mpiActions.MpiAction`,
 and override the :py:meth:`~armi.mpiActions.MpiAction.invokeHook` method.
-
 """
 import collections
 import gc
-import timeit
 import math
+import timeit
 
 from six.moves import cPickle
 import tabulate
@@ -583,7 +582,6 @@ class DistributeStateAction(MpiAction):
             raise RuntimeError("Failed to transmit settings, received: {}".format(cs))
 
         if context.MPI_RANK != 0:
-            settings.setMasterCs(cs)
             self.o.cs = cs
         return cs
 
diff --git a/armi/operators/operator.py b/armi/operators/operator.py
index 6196c215..b9381b19 100644
--- a/armi/operators/operator.py
+++ b/armi/operators/operator.py
@@ -26,16 +26,15 @@ the end of plant life.
    :id: IMPL_EVOLVING_STATE_0
    :links: REQ_EVOLVING_STATE
 """
+import collections
 import os
 import re
 import shutil
 import time
-import collections
 
 from armi import context
 from armi import interfaces
 from armi import runLog
-from armi import settings
 from armi.bookkeeping import memoryProfiler
 from armi.bookkeeping.report import reportingUtils
 from armi.operators import settingsValidation
@@ -484,8 +483,6 @@ class Operator:
                     )
                 )
 
-            self._checkCsConsistency()
-
         runLog.header(
             "===========  Completed {} Event ===========\n".format(
                 interactionName + cycleNodeTag
@@ -564,23 +561,6 @@ class Operator:
 
             db.writeToDB(self.r, statePointName=statePointName)
 
-    def _checkCsConsistency(self):
-        """Debugging check to verify that CS objects are not unexpectedly multiplying."""
-        cs = settings.getMasterCs()
-        wrong = (self.cs is not cs) or any((i.cs is not cs) for i in self.interfaces)
-        if wrong:
-            msg = ["Primary cs ID is {}".format(id(cs))]
-            for i in self.interfaces:
-                msg.append("{:30s} has cs ID: {:12d}".format(str(i), id(i.cs)))
-            msg.append("{:30s} has cs ID: {:12d}".format(str(self), id(self.cs)))
-            raise RuntimeError("\n".join(msg))
-
-        runLog.debug(
-            "Reactors, operators, and interfaces all share primary cs: {}".format(
-                id(cs)
-            )
-        )
-
     def interactAllInit(self):
         """Call interactInit on all interfaces in the stack after they are initialized."""
         allInterfaces = self.interfaces[:]  # copy just in case
diff --git a/armi/operators/operatorMPI.py b/armi/operators/operatorMPI.py
index 17a7418e..6cf7d64a 100644
--- a/armi/operators/operatorMPI.py
+++ b/armi/operators/operatorMPI.py
@@ -41,7 +41,6 @@ from armi import context
 from armi import getPluginManager
 from armi import mpiActions
 from armi import runLog
-from armi import settings
 from armi.operators.operator import Operator
 from armi.reactor import reactors
 
@@ -213,7 +212,7 @@ class OperatorMPI(Operator):
         xsGroups = self.getInterface("xsGroups")
         if xsGroups:
             xsGroups.clearRepresentativeBlocks()
-        cs = settings.getMasterCs()
+        cs = self.cs
         bp = self.r.blueprints
         spatialGrid = self.r.core.spatialGrid
         self.detach()
diff --git a/armi/settings/__init__.py b/armi/settings/__init__.py
index 65d109fd..1a456628 100644
--- a/armi/settings/__init__.py
+++ b/armi/settings/__init__.py
@@ -151,34 +151,3 @@ def promptForSettingsFile(choice=None):
         choice = int(input("Enter choice: "))
 
     return files[choice]
-
-
-def getMasterCs():
-    """
-    Return the global case-settings object (cs).
-
-    This can be called at any time to create or obtain the primary Cs, a module-level CS
-    intended to be shared by many other objects.
-
-    It can have multiple instances in multiprocessing cases.
-
-    Returns
-    -------
-    cs : Settings
-        The loaded cs object
-    """
-    cs = Settings.instance
-    if cs is None:
-        cs = Settings()
-        setMasterCs(cs)
-    return cs
-
-
-def setMasterCs(cs):
-    """
-    Set the primary Cs to be the one that is passed in.
-
-    These are kept track of independently on a PID basis to allow independent multiprocessing.
-    """
-    Settings.instance = cs
-    runLog.debug("Primary cs set to {} with ID: {}".format(cs, id(cs)))
diff --git a/armi/settings/caseSettings.py b/armi/settings/caseSettings.py
index d94f634a..1ce2dd42 100644
--- a/armi/settings/caseSettings.py
+++ b/armi/settings/caseSettings.py
@@ -64,10 +64,6 @@ class Settings:
     The actual settings in any instance of this class are immutable.
     """
 
-    # Settings is not a singleton, but there is a globally
-    # shared instance considered most germane to the current run
-    instance = None
-
     defaultCaseTitle = "armi"
 
     def __init__(self, fName=None):
@@ -98,8 +94,6 @@ class Settings:
         app = getApp()
         assert app is not None
         self.__settings = app.getSettings()
-        if not Settings.instance:
-            Settings.instance = self
 
         if fName:
             self.loadFromInputFile(fName)
diff --git a/doc/gallery-src/framework/run_programmaticReactorDefinition.py b/doc/gallery-src/framework/run_programmaticReactorDefinition.py
index e6fc92d2..e270afeb 100644
--- a/doc/gallery-src/framework/run_programmaticReactorDefinition.py
+++ b/doc/gallery-src/framework/run_programmaticReactorDefinition.py
@@ -36,7 +36,6 @@ from armi import configure
 configure(permissive=True)
 
 from armi import cases
-from armi import settings
 from armi.reactor import blueprints
 from armi.reactor.blueprints import assemblyBlueprint
 from armi.reactor.blueprints import blockBlueprint
@@ -61,7 +60,6 @@ def buildCase():
     bp.systemDesigns = buildSystems()
 
     cs = caseSettings.Settings()
-    settings.setMasterCs(cs)  # remove once we eliminate masterCs
     cs.path = None
     cs.caseTitle = "scripted-case"
     case = cases.Case(cs=cs, bp=bp)
diff --git a/doc/release/0.2.rst b/doc/release/0.2.rst
index 2a41d594..3b594476 100644
--- a/doc/release/0.2.rst
+++ b/doc/release/0.2.rst
@@ -9,6 +9,7 @@ Release Date: TBD
 What's new in ARMI
 ------------------
 #. The Spent Fuel Pool (``sfp``) was moved from the ``Core`` out to the ``Reactor``. (`PR#1336 <https://github.com/terrapower/armi/pull/1336>`_)
+#. Removed ``getMasterCs()`` and ``setMasterCs()``. (`PR#1399 <https://github.com/terrapower/armi/pull/1399>`_)
 #. Broad cleanup of ``Parameters``: filled in all empty units and descriptions, removed unused params. (`PR#1345 <https://github.com/terrapower/armi/pull/1345>`_)
 #. Removed redundant ``Material.name`` variable. (`PR#1335 <https://github.com/terrapower/armi/pull/1335>`_)
 #. Moved the ``Reactor`` assembly number from the global scope to a ``Parameter``. (`PR#1383 <https://github.com/terrapower/armi/pull/1383>`_)


Test patch:
diff --git a/armi/bookkeeping/db/tests/test_databaseInterface.py b/armi/bookkeeping/db/tests/test_databaseInterface.py
index 2b653b1a..860d3236 100644
--- a/armi/bookkeeping/db/tests/test_databaseInterface.py
+++ b/armi/bookkeeping/db/tests/test_databaseInterface.py
@@ -53,7 +53,6 @@ def getSimpleDBOperator(cs):
     newSettings["nCycles"] = 1
     cs = cs.modified(newSettings=newSettings)
     genDBCase = case.Case(cs)
-    settings.setMasterCs(cs)
     runLog.setVerbosity("info")
 
     o = genDBCase.initializeOperator()
@@ -264,8 +263,6 @@ class TestDatabaseReading(unittest.TestCase):
         o, r = loadTestReactor(customSettings=newSettings)
         reduceTestReactorRings(r, o.cs, 3)
 
-        settings.setMasterCs(o.cs)
-
         o.interfaces = [i for i in o.interfaces if isinstance(i, (DatabaseInterface))]
         dbi = o.getInterface("database")
         dbi.enabled(True)
diff --git a/armi/bookkeeping/tests/test_historyTracker.py b/armi/bookkeeping/tests/test_historyTracker.py
index 179d5df1..8a2bbb0a 100644
--- a/armi/bookkeeping/tests/test_historyTracker.py
+++ b/armi/bookkeeping/tests/test_historyTracker.py
@@ -84,7 +84,6 @@ class TestHistoryTracker(ArmiTestHelper):
 
         c = case.Case(cs)
         case2 = c.clone(title="armiRun")
-        settings.setMasterCs(case2.cs)
         self.o = case2.initializeOperator()
         self.r = self.o.r
 
@@ -187,7 +186,8 @@ class TestHistoryTrackerNoModel(unittest.TestCase):
     """History tracker tests that do not require a Reactor Model."""
 
     def setUp(self):
-        self.history = historyTracker.HistoryTrackerInterface(None, None)
+        cs = settings.Settings()
+        self.history = historyTracker.HistoryTrackerInterface(None, cs=cs)
         self._origCaseTitle = (
             self.history.cs.caseTitle
         )  # to avoid parallel test interference.
diff --git a/armi/cases/tests/test_cases.py b/armi/cases/tests/test_cases.py
index 49644fba..94356c21 100644
--- a/armi/cases/tests/test_cases.py
+++ b/armi/cases/tests/test_cases.py
@@ -30,7 +30,6 @@ from armi import settings
 from armi.physics.fuelCycle.settings import CONF_SHUFFLE_LOGIC
 from armi.reactor import blueprints
 from armi.reactor import systemLayoutInput
-from armi.settings import setMasterCs
 from armi.tests import ARMI_RUN_PATH
 from armi.tests import mockRunLogs
 from armi.tests import TEST_ROOT
@@ -211,7 +210,6 @@ class TestArmiCase(unittest.TestCase):
                 "verbosity": "important",
             }
             cs = cs.modified(newSettings=newSettings)
-            setMasterCs(cs)
             case = cases.Case(cs)
 
             with mockRunLogs.BufferLog() as mock:
@@ -231,7 +229,6 @@ class TestArmiCase(unittest.TestCase):
         # test the short write style
         with directoryChangers.TemporaryDirectoryChanger():
             cs = settings.Settings(ARMI_RUN_PATH)
-            setMasterCs(cs)
             case = cases.Case(cs)
             shortCase = case.clone(
                 additionalFiles=["ISOAA"],
@@ -254,7 +251,6 @@ class TestArmiCase(unittest.TestCase):
         # test the medium write style
         with directoryChangers.TemporaryDirectoryChanger():
             cs = settings.Settings(ARMI_RUN_PATH)
-            setMasterCs(cs)
             case = cases.Case(cs)
             case.clone(writeStyle="medium")
             clonedYaml = "armiRun.yaml"
diff --git a/armi/conftest.py b/armi/conftest.py
index 7eb1a52e..c26d1513 100644
--- a/armi/conftest.py
+++ b/armi/conftest.py
@@ -28,7 +28,7 @@ import os
 
 import matplotlib
 
-from armi import apps, configure, context, settings
+from armi import apps, configure, context
 from armi.settings import caseSettings
 from armi.tests import TEST_ROOT
 
@@ -51,7 +51,6 @@ def bootstrapArmiTestEnv():
     cs = caseSettings.Settings()
 
     context.Mode.setMode(context.Mode.BATCH)
-    settings.setMasterCs(cs)
     # Need to init burnChain.
     # see armi.cases.case.Case._initBurnChain
     with open(cs["burnChainFileName"]) as burnChainStream:
diff --git a/armi/operators/tests/test_operators.py b/armi/operators/tests/test_operators.py
index 183b54f7..e75d1b73 100644
--- a/armi/operators/tests/test_operators.py
+++ b/armi/operators/tests/test_operators.py
@@ -92,13 +92,6 @@ class OperatorTests(unittest.TestCase):
         self.assertEqual(self.o.getInterface("Second"), interfaceB)
         self.assertEqual(self.o.getInterface("Third"), interfaceC)
 
-    def test_checkCsConsistency(self):
-        self.o._checkCsConsistency()  # passes without error
-
-        self.o.cs = self.o.cs.modified(newSettings={"nCycles": 66})
-        with self.assertRaises(RuntimeError):
-            self.o._checkCsConsistency()
-
     def test_interfaceIsActive(self):
         self.o, _r = test_reactors.loadTestReactor()
         self.assertTrue(self.o.interfaceIsActive("main"))
@@ -111,7 +104,6 @@ class OperatorTests(unittest.TestCase):
             self.o.loadState(0, 1)
 
     def test_setStateToDefault(self):
-
         # reset the runType for testing
         self.assertEqual(self.o.cs[CONF_RUN_TYPE], "Standard")
         self.o.cs = self.o.cs.modified(newSettings={"runType": "fake"})
@@ -166,7 +158,7 @@ class OperatorTests(unittest.TestCase):
 
 class TestTightCoupling(unittest.TestCase):
     def setUp(self):
-        self.cs = settings.getMasterCs()
+        self.cs = settings.Settings()
         self.cs[CONF_TIGHT_COUPLING] = True
         self.o = Operator(self.cs)
         self.o.r = Reactor("empty", None)
diff --git a/armi/physics/neutronics/globalFlux/tests/test_globalFluxInterface.py b/armi/physics/neutronics/globalFlux/tests/test_globalFluxInterface.py
index 9bbae2d5..ff895766 100644
--- a/armi/physics/neutronics/globalFlux/tests/test_globalFluxInterface.py
+++ b/armi/physics/neutronics/globalFlux/tests/test_globalFluxInterface.py
@@ -262,7 +262,7 @@ class TestGlobalFluxResultMapper(unittest.TestCase):
         o, r = test_reactors.loadTestReactor(customSettings={CONF_XS_KERNEL: "MC2v2"})
         applyDummyFlux(r)
         r.core.lib = isotxs.readBinary(ISOAA_PATH)
-        mapper = globalFluxInterface.GlobalFluxResultMapper()
+        mapper = globalFluxInterface.GlobalFluxResultMapper(cs=o.cs)
         mapper.r = r
         mapper._renormalizeNeutronFluxByBlock(100)
         self.assertAlmostEqual(r.core.calcTotalParam("power", generationNum=2), 100)
@@ -295,7 +295,8 @@ class TestGlobalFluxResultMapper(unittest.TestCase):
         self.assertEqual(len(block.p.mgFlux), 0)
 
     def test_getDpaXs(self):
-        mapper = globalFluxInterface.GlobalFluxResultMapper()
+        cs = settings.Settings()
+        mapper = globalFluxInterface.GlobalFluxResultMapper(cs=cs)
 
         # test fuel block
         b = HexBlock("fuel", height=10.0)
@@ -320,7 +321,8 @@ class TestGlobalFluxResultMapper(unittest.TestCase):
             mapper.getDpaXs(b)
 
     def test_getBurnupPeakingFactor(self):
-        mapper = globalFluxInterface.GlobalFluxResultMapper()
+        cs = settings.Settings()
+        mapper = globalFluxInterface.GlobalFluxResultMapper(cs=cs)
 
         # test fuel block
         mapper.cs["burnupPeakingFactor"] = 0.0
@@ -331,7 +333,8 @@ class TestGlobalFluxResultMapper(unittest.TestCase):
         self.assertEqual(factor, 2.5)
 
     def test_getBurnupPeakingFactorZero(self):
-        mapper = globalFluxInterface.GlobalFluxResultMapper()
+        cs = settings.Settings()
+        mapper = globalFluxInterface.GlobalFluxResultMapper(cs=cs)
 
         # test fuel block without any peaking factor set
         b = HexBlock("fuel", height=10.0)
diff --git a/armi/physics/neutronics/tests/test_crossSectionManager.py b/armi/physics/neutronics/tests/test_crossSectionManager.py
index af284386..2282651c 100644
--- a/armi/physics/neutronics/tests/test_crossSectionManager.py
+++ b/armi/physics/neutronics/tests/test_crossSectionManager.py
@@ -24,7 +24,7 @@ import unittest
 
 from six.moves import cPickle
 
-from armi import settings  # noqa: unused-import
+from armi import settings
 from armi.physics.neutronics import crossSectionGroupManager
 from armi.physics.neutronics.const import CONF_CROSS_SECTION
 from armi.physics.neutronics.crossSectionGroupManager import (
@@ -473,8 +473,9 @@ class TestBlockCollectionFluxWeightedAverage(unittest.TestCase):
 
 class Test_CrossSectionGroupManager(unittest.TestCase):
     def setUp(self):
+        cs = settings.Settings()
         self.blockList = makeBlocks(20)
-        self.csm = CrossSectionGroupManager(self.blockList[0].r, None)
+        self.csm = CrossSectionGroupManager(self.blockList[0].r, cs)
         for bi, b in enumerate(self.blockList):
             b.p.percentBu = bi / 19.0 * 100
         self.csm._setBuGroupBounds([3, 10, 30, 100])
diff --git a/armi/reactor/tests/test_assemblies.py b/armi/reactor/tests/test_assemblies.py
index 9c897ee0..47ee795d 100644
--- a/armi/reactor/tests/test_assemblies.py
+++ b/armi/reactor/tests/test_assemblies.py
@@ -13,10 +13,10 @@
 # limitations under the License.
 
 """Tests assemblies.py."""
+import numpy as np
 import pathlib
 import random
 import unittest
-import numpy as np
 from numpy.testing import assert_allclose
 
 from armi import settings
@@ -58,8 +58,7 @@ def buildTestAssemblies():
         * One with half UZr pins and half UTh pins
         * One with all UThZr pins
     """
-    caseSetting = settings.Settings()
-    settings.setMasterCs(caseSetting)
+    settings.Settings()
 
     temperature = 273.0
     fuelID = 0.0
@@ -1282,7 +1281,6 @@ class AnnularFuelTestCase(unittest.TestCase):
         newSettings = {CONF_XS_KERNEL: "MC2v2"}  # don't try to expand elementals
         self.cs = self.cs.modified(newSettings=newSettings)
 
-        settings.setMasterCs(self.cs)
         bp = blueprints.Blueprints()
         self.r = reactors.Reactor("test", bp)
         self.r.add(reactors.Core("Core"))
diff --git a/armi/reactor/tests/test_blocks.py b/armi/reactor/tests/test_blocks.py
index 5a38a107..381bce35 100644
--- a/armi/reactor/tests/test_blocks.py
+++ b/armi/reactor/tests/test_blocks.py
@@ -75,7 +75,6 @@ def buildSimpleFuelBlock():
 def loadTestBlock(cold=True):
     """Build an annular test block for evaluating unit tests."""
     caseSetting = settings.Settings()
-    settings.setMasterCs(caseSetting)
     caseSetting[CONF_XS_KERNEL] = "MC2v2"
     runLog.setVerbosity("error")
     caseSetting["nCycles"] = 1
diff --git a/armi/reactor/tests/test_reactors.py b/armi/reactor/tests/test_reactors.py
index 43056fbd..f1b4b1f2 100644
--- a/armi/reactor/tests/test_reactors.py
+++ b/armi/reactor/tests/test_reactors.py
@@ -54,16 +54,12 @@ def buildOperatorOfEmptyHexBlocks(customSettings=None):
     customSettings : dict
         Dictionary of off-default settings to update
     """
-    settings.setMasterCs(None)  # clear
     cs = settings.Settings()  # fetch new
-    settings.setMasterCs(cs)  # reset
-
     if customSettings is None:
         customSettings = {}
 
     customSettings["db"] = False  # stop use of database
     cs = cs.modified(newSettings=customSettings)
-    settings.setMasterCs(cs)  # reset so everything matches the primary Cs
 
     r = tests.getEmptyHexReactor()
     r.core.setOptionsFromCs(cs)
@@ -95,16 +91,12 @@ def buildOperatorOfEmptyCartesianBlocks(customSettings=None):
     customSettings : dict
         Off-default settings to update
     """
-    settings.setMasterCs(None)  # clear
     cs = settings.Settings()  # fetch new
-    settings.setMasterCs(cs)  # reset
-
     if customSettings is None:
         customSettings = {}
 
     customSettings["db"] = False  # stop use of database
     cs = cs.modified(newSettings=customSettings)
-    settings.setMasterCs(cs)  # reset
 
     r = tests.getEmptyCartesianReactor()
     r.core.setOptionsFromCs(cs)
@@ -176,7 +168,6 @@ def loadTestReactor(
     if isPickeledReactor and TEST_REACTOR:
         # return test reactor only if no custom settings are needed.
         o, r, assemNum = cPickle.loads(TEST_REACTOR)
-        settings.setMasterCs(o.cs)
         o.reattach(r, o.cs)
         return o, r
 
@@ -191,7 +182,6 @@ def loadTestReactor(
 
     newSettings = {}
     cs = cs.modified(newSettings=newSettings)
-    settings.setMasterCs(cs)
 
     o = operators.factory(cs)
     r = reactors.loadFromCs(cs)
diff --git a/armi/tests/test_mpiFeatures.py b/armi/tests/test_mpiFeatures.py
index 03893096..4ee46b49 100644
--- a/armi/tests/test_mpiFeatures.py
+++ b/armi/tests/test_mpiFeatures.py
@@ -159,7 +159,6 @@ class MpiDistributeStateTests(unittest.TestCase):
         self.cs = settings.Settings(fName=ARMI_RUN_PATH)
         bp = blueprints.loadFromCs(self.cs)
 
-        settings.setMasterCs(self.cs)
         self.o = OperatorMPI(self.cs)
         self.o.r = reactors.factory(self.cs, bp)
         self.action = DistributeStateAction()