Repository: terrapower/armi
Pull request #2133 (2025-05-01 16:06:28)

Moving NeutronicsPlugin to its own file
## What is the change? Why is it being made?

Moving `NeutronicsPlugin` to its own file.

My goal here is to make a little progress on: https://github.com/terrapower/armi/issues/1452

This is the only change I could make from that ticket that wasn't API breaking.

## SCR Information

<!-- MANDATORY: uncomment one-and-only-one of these -->
Change Type: trivial

<!-- MANDATORY: Describe the change in one sentence -->
One-Sentence Description: Moving NeutronicsPlugin to its own file.

<!-- MANDATORY: Describe any impact on the requirements, all on one line -->
One-line Impact on Requirements: NA


---

## Checklist

<!--
    The pull request author should check the box if the condition is met OR if it does not apply.
-->

- [x] This PR has only [one purpose or idea](https://terrapower.github.io/armi/developer/tooling.html#one-idea-one-pr).
- [x] [Tests](https://terrapower.github.io/armi/developer/tooling.html#test-it) have been added/updated to verify any new/changed code.
- [x] The [documentation](https://terrapower.github.io/armi/developer/tooling.html#document-it) is still up-to-date in the `doc` folder.
- [x] The code style follows [good practices](https://terrapower.github.io/armi/developer/standards_and_practices.html).
- [x] The dependencies are still up-to-date in `pyproject.toml`.



Patch:
diff --git a/armi/bookkeeping/db/database.py b/armi/bookkeeping/db/database.py
index 0d372c37..c7e0ce61 100644
--- a/armi/bookkeeping/db/database.py
+++ b/armi/bookkeeping/db/database.py
@@ -50,6 +50,7 @@ from typing import (
     Sequence,
     Tuple,
     Type,
+    Union,
 )
 
 import h5py
@@ -74,7 +75,10 @@ from armi.reactor.composites import ArmiObject
 from armi.reactor.parameters import parameterCollections
 from armi.reactor.reactorParameters import makeParametersReadOnly
 from armi.reactor.reactors import Core, Reactor
-from armi.settings.fwSettings.globalSettings import CONF_SORT_REACTOR
+from armi.settings.fwSettings.globalSettings import (
+    CONF_GROW_TO_FULL_CORE_AFTER_LOAD,
+    CONF_SORT_REACTOR,
+)
 from armi.utils import getNodesPerCycle, safeCopy, safeMove
 from armi.utils.textProcessors import resolveMarkupInclusions
 
@@ -782,6 +786,9 @@ class Database:
                 "temporary and will be removed by 2024."
             )
 
+        if cs[CONF_GROW_TO_FULL_CORE_AFTER_LOAD] and not root.core.isFullCore:
+            root.core.growToFullCore(cs)
+
         return root
 
     def loadReadOnly(self, cycle, node, statePointName=None):
@@ -892,7 +899,7 @@ class Database:
         return comp
 
     @staticmethod
-    def _getArrayShape(arr: [np.ndarray, List, Tuple]):
+    def _getArrayShape(arr: Union[np.ndarray, List, Tuple]):
         """Get the shape of a np.ndarray, list, or tuple."""
         if isinstance(arr, np.ndarray):
             return arr.shape
diff --git a/armi/bookkeeping/db/databaseInterface.py b/armi/bookkeeping/db/databaseInterface.py
index c0f9f0d7..f6c2b212 100644
--- a/armi/bookkeeping/db/databaseInterface.py
+++ b/armi/bookkeeping/db/databaseInterface.py
@@ -309,15 +309,15 @@ class DatabaseInterface(interfaces.Interface):
 
         Notes
         -----
-        Will load preferentially from the `fileName` if passed. Otherwise will load from
-        existing database in memory or `cs["reloadDBName"]` in that order.
+        Will load preferentially from the ``fileName`` if passed. Otherwise will load from
+        existing database in memory or ``cs["reloadDBName"`]` in that order.
 
         Raises
         ------
         RuntimeError
             If fileName is specified and that  file does not have the time step.
             If fileName is not specified and neither the database in memory, nor the
-            `cs["reloadDBName"]` have the time step specified.
+            ``cs["reloadDBName"]`` have the time step specified.
         """
         for potentialDatabase in self._getLoadDB(fileName):
             with potentialDatabase as loadDB:
diff --git a/armi/cases/case.py b/armi/cases/case.py
index f26de268..e41f2a94 100644
--- a/armi/cases/case.py
+++ b/armi/cases/case.py
@@ -473,7 +473,7 @@ class Case:
         with DirectoryChanger(self.cs.inputDirectory, dumpOnException=False):
             self._initBurnChain()
             o = operators.factory(self.cs)
-            if not r:
+            if r is None:
                 r = reactors.factory(self.cs, self.bp)
             o.initializeInterfaces(r)
             # Set this here to make sure the full duration of initialization is properly captured.
diff --git a/armi/physics/neutronics/__init__.py b/armi/physics/neutronics/__init__.py
index 472ace73..8b3fb8f2 100644
--- a/armi/physics/neutronics/__init__.py
+++ b/armi/physics/neutronics/__init__.py
@@ -15,118 +15,12 @@
 """
 The neutronics physics package in the ARMI framework.
 
-Neutronics encompasses the modeling of nuclear chain reactions and their associated transmutation and decay.
-
-The ARMI Framework comes with a neutronics plugin that introduces two
-independent interfaces:
-
-:py:mod:`~armi.physics.neutronics.fissionProductModel`
-    Handles fission product modeling
-
-:py:mod:`~armi.physics.neutronics.crossSectionGroupManager`
-    Handles the management of different cross section "groups"
-
-.. warning:: There is also some legacy and question-raising code in this module that
-    is here temporarily while we finish untangling some of the neutronics
-    plugins outside of ARMI.
+Neutronics encompasses the modeling of nuclear chain reactions and their associated transmutation
+and decay.
 """
-# ruff: noqa: F401, E402
+# ruff: noqa: F401
 from enum import IntEnum
 
-import numpy as np
-
-from armi import plugins, runLog
-from armi.physics.neutronics.const import CONF_CROSS_SECTION
-from armi.utils import tabulate
-
-
-class NeutronicsPlugin(plugins.ArmiPlugin):
-    """The built-in neutronics plugin with a few capabilities and a lot of state parameter definitions."""
-
-    @staticmethod
-    @plugins.HOOKIMPL
-    def exposeInterfaces(cs):
-        """Collect and expose all of the interfaces that live under the built-in neutronics package."""
-        from armi.physics.neutronics import crossSectionGroupManager
-        from armi.physics.neutronics.fissionProductModel import fissionProductModel
-
-        interfaceInfo = []
-        for mod in (crossSectionGroupManager, fissionProductModel):
-            interfaceInfo += plugins.collectInterfaceDescriptions(mod, cs)
-
-        return interfaceInfo
-
-    @staticmethod
-    @plugins.HOOKIMPL
-    def defineParameters():
-        """Define parameters for the plugin."""
-        from armi.physics.neutronics import parameters as neutronicsParameters
-
-        return neutronicsParameters.getNeutronicsParameterDefinitions()
-
-    @staticmethod
-    @plugins.HOOKIMPL
-    def defineParameterRenames():
-        return {"buGroup": "envGroup", "buGroupNum": "envGroupNum"}
-
-    @staticmethod
-    @plugins.HOOKIMPL
-    def defineEntryPoints():
-        """Define entry points for the plugin."""
-        from armi.physics.neutronics import diffIsotxs
-
-        entryPoints = [diffIsotxs.CompareIsotxsLibraries]
-
-        return entryPoints
-
-    @staticmethod
-    @plugins.HOOKIMPL
-    def defineSettings():
-        """Define settings for the plugin."""
-        from armi.physics.neutronics import crossSectionSettings
-        from armi.physics.neutronics import settings as neutronicsSettings
-        from armi.physics.neutronics.fissionProductModel import (
-            fissionProductModelSettings,
-        )
-
-        settings = [
-            crossSectionSettings.XSSettingDef(
-                CONF_CROSS_SECTION,
-            )
-        ]
-        settings += neutronicsSettings.defineSettings()
-        settings += fissionProductModelSettings.defineSettings()
-
-        return settings
-
-    @staticmethod
-    @plugins.HOOKIMPL
-    def defineSettingsValidators(inspector):
-        """Implementation of settings inspections for neutronics settings."""
-        from armi.physics.neutronics.fissionProductModel.fissionProductModelSettings import (
-            getFissionProductModelSettingValidators,
-        )
-        from armi.physics.neutronics.settings import getNeutronicsSettingValidators
-
-        settingsValidators = getNeutronicsSettingValidators(inspector)
-        settingsValidators.extend(getFissionProductModelSettingValidators(inspector))
-        return settingsValidators
-
-    @staticmethod
-    @plugins.HOOKIMPL
-    def onProcessCoreLoading(core, cs, dbLoad):
-        """Called whenever a Core object is newly built."""
-        applyEffectiveDelayedNeutronFractionToCore(core, cs)
-
-    @staticmethod
-    @plugins.HOOKIMPL
-    def getReportContents(r, cs, report, stage, blueprint):
-        """Generates the Report Content for the Neutronics Report."""
-        from armi.physics.neutronics import reports
-
-        return reports.insertNeutronicsReport(r, cs, report, stage)
-
-
 from armi.physics.neutronics.const import (
     ALL,
     FLUXFILES,
@@ -136,6 +30,7 @@ from armi.physics.neutronics.const import (
     NEUTRONGAMMA,
     RESTARTFILES,
 )
+from armi.physics.neutronics.plugin import NeutronicsPlugin
 
 # ARC and CCCC cross section file format names
 COMPXS = "COMPXS"
@@ -176,6 +71,7 @@ EXTRAPOLATED = "Extrapolated"
 ZEROFLUX = "ZeroSurfaceFlux"
 ZERO_INWARD_CURRENT = "ZeroInwardCurrent"
 
+
 # Common settings checks
 def gammaTransportIsRequested(cs):
     """
@@ -229,57 +125,6 @@ def realCalculationRequested(cs):
     return cs[CONF_NEUTRONICS_TYPE] in ["real", "both"]
 
 
-def applyEffectiveDelayedNeutronFractionToCore(core, cs):
-    """Process the settings for the delayed neutron fraction and precursor decay constants."""
-    # Verify and set the core beta parameters based on the user-supplied settings
-    beta = cs["beta"]
-    decayConstants = cs["decayConstants"]
-
-    # If beta is interpreted as a float, then assign it to
-    # the total delayed neutron fraction parameter. Otherwise, setup the
-    # group-wise delayed neutron fractions and precursor decay constants.
-    reportTableData = []
-    if isinstance(beta, float):
-        core.p.beta = beta
-        reportTableData.append(("Total Delayed Neutron Fraction", core.p.beta))
-
-    elif isinstance(beta, list) and isinstance(decayConstants, list):
-        if len(beta) != len(decayConstants):
-            raise ValueError(
-                f"The values for `beta` ({beta}) and `decayConstants` "
-                f"({decayConstants}) are not consistent lengths."
-            )
-
-        core.p.beta = sum(beta)
-        core.p.betaComponents = np.array(beta)
-        core.p.betaDecayConstants = np.array(decayConstants)
-
-        reportTableData.append(("Total Delayed Neutron Fraction", core.p.beta))
-        for i, betaComponent in enumerate(core.p.betaComponents):
-            reportTableData.append(
-                (f"Group {i} Delayed Neutron Fractions", betaComponent)
-            )
-        for i, decayConstant in enumerate(core.p.betaDecayConstants):
-            reportTableData.append(
-                ("Group {i} Precursor Decay Constants", decayConstant)
-            )
-
-    # Report to the user the values were not applied.
-    if not reportTableData and (beta is not None or decayConstants is not None):
-        runLog.warning(
-            f"Delayed neutron fraction(s) - {beta} and decay constants"
-            f" - {decayConstants} have not been applied."
-        )
-    else:
-        runLog.extra(
-            tabulate.tabulate(
-                data=reportTableData,
-                headers=["Component", "Value"],
-                tableFmt="armi",
-            )
-        )
-
-
 class LatticePhysicsFrequency(IntEnum):
     """
     Enumeration for lattice physics update frequency options.
@@ -293,12 +138,12 @@ class LatticePhysicsFrequency(IntEnum):
 
     Notes
     -----
-    firstCoupledIteration only updates the cross sections during the first coupled iteration,
-    but not on any subsequent iterations. This may be an appropriate approximation in some cases
-    to save compute time, but each individual user should give careful consideration to whether
-    this is the behavior they want for a particular application. The main purpose of this setting
-    is to capture a large change in temperature distribution when running a snapshot at a different
-    power/flow condition than the original state being loaded from the database.
+    firstCoupledIteration only updates the cross sections during the first coupled iteration, but
+    not on any subsequent iterations. This may be an appropriate approximation in some cases to save
+    compute time, but each individual user should give careful consideration to whether this is the
+    behavior they want for a particular application. The main purpose of this setting is to capture
+    a large change in temperature distribution when running a snapshot at a different power/flow
+    condition than the original state being loaded from the database.
     """
 
     never = 0
diff --git a/armi/physics/neutronics/parameters.py b/armi/physics/neutronics/parameters.py
index ef5bcabb..618a73da 100644
--- a/armi/physics/neutronics/parameters.py
+++ b/armi/physics/neutronics/parameters.py
@@ -204,15 +204,6 @@ def _getNeutronicsBlockParams():
             default=None,
         )
 
-        pb.defParam(
-            "betad",
-            units=units.UNITLESS,
-            description="Delayed neutron beta",
-            location=ParamLocation.AVERAGE,
-            saveToDB=True,
-            default=None,
-        )
-
         pb.defParam(
             "chi",
             units=units.UNITLESS,
@@ -222,15 +213,6 @@ def _getNeutronicsBlockParams():
             default=None,
         )
 
-        pb.defParam(
-            "chid",
-            units=units.UNITLESS,
-            description="Energy distribution of delayed fission neutrons",
-            location=ParamLocation.AVERAGE,
-            saveToDB=True,
-            default=None,
-        )
-
         pb.defParam(
             "linPow",
             units=f"{units.WATTS}/{units.METERS}",
@@ -747,12 +729,4 @@ def _getNeutronicsCoreParams():
             location=ParamLocation.AVERAGE,
         )
 
-        pb.defParam(
-            "refKeff",
-            units=units.UNITLESS,
-            description="Reference unperturbed keff",
-            default=0.0,
-            location=ParamLocation.AVERAGE,
-        )
-
     return pDefs
diff --git a/armi/physics/neutronics/plugin.py b/armi/physics/neutronics/plugin.py
new file mode 100644
index 00000000..fbb4a343
--- /dev/null
+++ b/armi/physics/neutronics/plugin.py
@@ -0,0 +1,169 @@
+# Copyright 2025 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.
+
+"""
+A boilerplate entry for a neutronics physics plugin.
+
+The ARMI Framework comes with a neutronics plugin that introduces two independent interfaces:
+
+:py:mod:`~armi.physics.neutronics.fissionProductModel`
+    Handles fission product modeling
+
+:py:mod:`~armi.physics.neutronics.crossSectionGroupManager`
+    Handles the management of different cross section "groups"
+"""
+
+import numpy as np
+
+from armi import plugins, runLog
+from armi.physics.neutronics.const import CONF_CROSS_SECTION
+from armi.utils import tabulate
+
+
+class NeutronicsPlugin(plugins.ArmiPlugin):
+    """The built-in neutronics plugin with a few capabilities and a lot of state parameter definitions."""
+
+    @staticmethod
+    @plugins.HOOKIMPL
+    def exposeInterfaces(cs):
+        """Collect and expose all of the interfaces that live under the built-in neutronics package."""
+        from armi.physics.neutronics import crossSectionGroupManager
+        from armi.physics.neutronics.fissionProductModel import fissionProductModel
+
+        interfaceInfo = []
+        for mod in (crossSectionGroupManager, fissionProductModel):
+            interfaceInfo += plugins.collectInterfaceDescriptions(mod, cs)
+
+        return interfaceInfo
+
+    @staticmethod
+    @plugins.HOOKIMPL
+    def defineParameters():
+        """Define parameters for the plugin."""
+        from armi.physics.neutronics import parameters as neutronicsParameters
+
+        return neutronicsParameters.getNeutronicsParameterDefinitions()
+
+    @staticmethod
+    @plugins.HOOKIMPL
+    def defineParameterRenames():
+        return {"buGroup": "envGroup", "buGroupNum": "envGroupNum"}
+
+    @staticmethod
+    @plugins.HOOKIMPL
+    def defineEntryPoints():
+        """Define entry points for the plugin."""
+        from armi.physics.neutronics import diffIsotxs
+
+        entryPoints = [diffIsotxs.CompareIsotxsLibraries]
+
+        return entryPoints
+
+    @staticmethod
+    @plugins.HOOKIMPL
+    def defineSettings():
+        """Define settings for the plugin."""
+        from armi.physics.neutronics import crossSectionSettings
+        from armi.physics.neutronics import settings as neutronicsSettings
+        from armi.physics.neutronics.fissionProductModel import (
+            fissionProductModelSettings,
+        )
+
+        settings = [
+            crossSectionSettings.XSSettingDef(
+                CONF_CROSS_SECTION,
+            )
+        ]
+        settings += neutronicsSettings.defineSettings()
+        settings += fissionProductModelSettings.defineSettings()
+
+        return settings
+
+    @staticmethod
+    @plugins.HOOKIMPL
+    def defineSettingsValidators(inspector):
+        """Implementation of settings inspections for neutronics settings."""
+        from armi.physics.neutronics.fissionProductModel.fissionProductModelSettings import (
+            getFissionProductModelSettingValidators,
+        )
+        from armi.physics.neutronics.settings import getNeutronicsSettingValidators
+
+        settingsValidators = getNeutronicsSettingValidators(inspector)
+        settingsValidators.extend(getFissionProductModelSettingValidators(inspector))
+        return settingsValidators
+
+    @staticmethod
+    @plugins.HOOKIMPL
+    def onProcessCoreLoading(core, cs, dbLoad):
+        """Called whenever a Core object is newly built."""
+        applyEffectiveDelayedNeutronFractionToCore(core, cs)
+
+    @staticmethod
+    @plugins.HOOKIMPL
+    def getReportContents(r, cs, report, stage, blueprint):
+        """Generates the Report Content for the Neutronics Report."""
+        from armi.physics.neutronics import reports
+
+        return reports.insertNeutronicsReport(r, cs, report, stage)
+
+
+def applyEffectiveDelayedNeutronFractionToCore(core, cs):
+    """Process the settings for the delayed neutron fraction and precursor decay constants."""
+    # Verify and set the core beta parameters based on the user-supplied settings
+    beta = cs["beta"]
+    decayConstants = cs["decayConstants"]
+
+    # If beta is interpreted as a float, then assign it to the total delayed neutron fraction
+    # parameter. Otherwise, setup the group-wise delayed neutron fractions and precursor decay
+    # constants.
+    reportTableData = []
+    if isinstance(beta, float):
+        core.p.beta = beta
+        reportTableData.append(("Total Delayed Neutron Fraction", core.p.beta))
+
+    elif isinstance(beta, list) and isinstance(decayConstants, list):
+        if len(beta) != len(decayConstants):
+            raise ValueError(
+                f"The values for `beta` ({beta}) and `decayConstants` "
+                f"({decayConstants}) are not consistent lengths."
+            )
+
+        core.p.beta = sum(beta)
+        core.p.betaComponents = np.array(beta)
+        core.p.betaDecayConstants = np.array(decayConstants)
+
+        reportTableData.append(("Total Delayed Neutron Fraction", core.p.beta))
+        for i, betaComponent in enumerate(core.p.betaComponents):
+            reportTableData.append(
+                (f"Group {i} Delayed Neutron Fractions", betaComponent)
+            )
+        for i, decayConstant in enumerate(core.p.betaDecayConstants):
+            reportTableData.append(
+                ("Group {i} Precursor Decay Constants", decayConstant)
+            )
+
+    # Report to the user the values were not applied.
+    if not reportTableData and (beta is not None or decayConstants is not None):
+        runLog.warning(
+            f"Delayed neutron fraction(s) - {beta} and decay constants"
+            f" - {decayConstants} have not been applied."
+        )
+    else:
+        runLog.extra(
+            tabulate.tabulate(
+                data=reportTableData,
+                headers=["Component", "Value"],
+                tableFmt="armi",
+            )
+        )
diff --git a/armi/reactor/blockParameters.py b/armi/reactor/blockParameters.py
index a30306f2..6fe47b5e 100644
--- a/armi/reactor/blockParameters.py
+++ b/armi/reactor/blockParameters.py
@@ -424,190 +424,6 @@ def getBlockParameterDefinitions():
             saveToDB=True,
         )
 
-    with pDefs.createBuilder(
-        default=0.0,
-        location=ParamLocation.AVERAGE,
-        categories=[
-            "reactivity coefficients",
-            "spatially dependent",
-            "mass normalized",
-        ],
-    ) as pb:
-
-        # FUEL COEFFICIENTS
-        pb.defParam(
-            "rxFuelDensityCoeffPerMass",
-            units=f"{units.REACTIVITY}/{units.KG}",
-            description="Fuel density coefficient",
-        )
-
-        pb.defParam(
-            "rxFuelTemperatureCoeffPerMass",
-            units=f"{units.REACTIVITY}/{units.KG}",
-            description="Fuel temperature coefficient",
-        )
-
-        pb.defParam(
-            "rxFuelVoidedTemperatureCoeffPerMass",
-            units=f"{units.REACTIVITY}/{units.KG}",
-            description="Fuel voided-coolant temperature coefficient",
-        )
-
-        # CLAD COEFFICIENTS
-        pb.defParam(
-            "rxCladDensityCoeffPerMass",
-            units=f"{units.REACTIVITY}/{units.KG}",
-            description="Clad density coefficient",
-        )
-
-        pb.defParam(
-            "rxCladTemperatureCoeffPerMass",
-            units=f"{units.REACTIVITY}/{units.KG}",
-            description="Clad temperature coefficient",
-        )
-
-        # STRUCTURE COEFFICIENTS
-        pb.defParam(
-            "rxStructureDensityCoeffPerMass",
-            units=f"{units.REACTIVITY}/{units.KG}",
-            description="Structure density coefficient",
-        )
-
-        pb.defParam(
-            "rxStructureTemperatureCoeffPerMass",
-            units=f"{units.REACTIVITY}/{units.KG}",
-            description="Structure temperature coefficient",
-        )
-
-        # COOLANT COEFFICIENTS
-        pb.defParam(
-            "rxCoolantDensityCoeffPerMass",
-            units=f"{units.REACTIVITY}/{units.KG}",
-            description="Coolant density coefficient",
-        )
-
-        pb.defParam(
-            "rxCoolantTemperatureCoeffPerMass",
-            units=f"{units.REACTIVITY}/{units.KG}",
-            description="Coolant temperature coefficient",
-        )
-
-    with pDefs.createBuilder(
-        default=0.0,
-        location=ParamLocation.VOLUME_INTEGRATED,
-        categories=[
-            "reactivity coefficients",
-            "spatially dependent",
-            "temperature normalized",
-        ],
-    ) as pb:
-
-        # FUEL COEFFICIENTS
-        pb.defParam(
-            "rxFuelDensityCoeffPerTemp",
-            units=f"{units.REACTIVITY}/{units.DEGK}",
-            description="Fuel density coefficient",
-        )
-
-        pb.defParam(
-            "rxFuelDopplerCoeffPerTemp",
-            units=f"{units.REACTIVITY}/{units.DEGK}",
-            description="Fuel Doppler coefficient",
-        )
-
-        pb.defParam(
-            "rxFuelVoidedDopplerCoeffPerTemp",
-            units=f"{units.REACTIVITY}/{units.DEGK}",
-            description="Fuel voided-coolant Doppler coefficient",
-        )
-
-        pb.defParam(
-            "rxFuelDopplerConstant",
-            units=f"{units.REACTIVITY}*{units.DEGK}^(n-1)",
-            description="Fuel Doppler constant",
-        )
-
-        pb.defParam(
-            "rxFuelVoidedDopplerConstant",
-            units=f"{units.REACTIVITY}*{units.DEGK}^(n-1)",
-            description="Fuel voided-coolant Doppler constant",
-        )
-
-        pb.defParam(
-            "rxStructureDopplerConstant",
-            units=f"{units.REACTIVITY}*{units.DEGK}^(n-1)",
-            description="Structure Doppler constant",
-        )
-
-        pb.defParam(
-            "rxCladDopplerConstant",
-            units=f"{units.REACTIVITY}*{units.DEGK}^(n-1)",
-            description="Clad Doppler constant",
-        )
-
-        pb.defParam(
-            "rxFuelTemperatureCoeffPerTemp",
-            units=f"{units.REACTIVITY}/{units.DEGK}",
-            description="Fuel temperature coefficient",
-        )
-
-        pb.defParam(
-            "rxFuelVoidedTemperatureCoeffPerTemp",
-            units=f"{units.REACTIVITY}/{units.DEGK}",
-            description="Fuel voided-coolant temperature coefficient",
-        )
-
-        # CLAD COEFFICIENTS
-        pb.defParam(
-            "rxCladDensityCoeffPerTemp",
-            units=f"{units.REACTIVITY}/{units.DEGK}",
-            description="Clad density coefficient",
-        )
-
-        pb.defParam(
-            "rxCladDopplerCoeffPerTemp",
-            units=f"{units.REACTIVITY}/{units.DEGK}",
-            description="Clad Doppler coefficient",
-        )
-
-        pb.defParam(
-            "rxCladTemperatureCoeffPerTemp",
-            units=f"{units.REACTIVITY}/{units.DEGK}",
-            description="Clad temperature coefficient",
-        )
-
-        # STRUCTURE COEFFICIENTS
-        pb.defParam(
-            "rxStructureDensityCoeffPerTemp",
-            units=f"{units.REACTIVITY}/{units.DEGK}",
-            description="Structure density coefficient",
-        )
-
-        pb.defParam(
-            "rxStructureDopplerCoeffPerTemp",
-            units=f"{units.REACTIVITY}/{units.DEGK}",
-            description="Structure Doppler coefficient",
-        )
-
-        pb.defParam(
-            "rxStructureTemperatureCoeffPerTemp",
-            units=f"{units.REACTIVITY}/{units.DEGK}",
-            description="Structure temperature coefficient",
-        )
-
-        # COOLANT COEFFICIENTS
-        pb.defParam(
-            "rxCoolantDensityCoeffPerTemp",
-            units=f"{units.REACTIVITY}/{units.DEGK}",
-            description="Coolant density coefficient",
-        )
-
-        pb.defParam(
-            "rxCoolantTemperatureCoeffPerTemp",
-            units=f"{units.REACTIVITY}/{units.DEGK}",
-            description="Coolant temperature coefficient",
-        )
-
     with pDefs.createBuilder(default=0.0) as pb:
 
         pb.defParam(
@@ -628,20 +444,6 @@ def getBlockParameterDefinitions():
             categories=[parameters.Category.retainOnReplacement],
         )
 
-        pb.defParam(
-            "blockBeta",
-            units=units.UNITLESS,
-            description="Beta in each block",
-            location=ParamLocation.AVERAGE,
-        )
-
-        pb.defParam(
-            "blockF",
-            units=f"1/{units.CM}^5/{units.SECONDS}^2",
-            description="Adjoint-weighted fission source in each block",
-            location=ParamLocation.AVERAGE,
-        )
-
         pb.defParam(
             "breedRatio",
             units=units.UNITLESS,
diff --git a/armi/reactor/blueprints/blockBlueprint.py b/armi/reactor/blueprints/blockBlueprint.py
index fe358c56..ceb90e94 100644
--- a/armi/reactor/blueprints/blockBlueprint.py
+++ b/armi/reactor/blueprints/blockBlueprint.py
@@ -140,6 +140,7 @@ class BlockBlueprint(yamlize.KeyedList):
 
         self._checkByComponentMaterialInput(materialInput)
 
+        allLatticeIds = set()
         for componentDesign in self:
             filteredMaterialInput, byComponentMatModKeys = self._filterMaterialInput(
                 materialInput, componentDesign
@@ -184,6 +185,30 @@ class BlockBlueprint(yamlize.KeyedList):
                         # learn mult from grid definition
                         c.setDimension("mult", len(c.spatialLocator))
 
+                idsInGrid = list(gridDesign.gridContents.values())
+                if componentDesign.latticeIDs:
+                    for latticeID in componentDesign.latticeIDs:
+                        allLatticeIds.add(str(latticeID))
+                        # the user has given this component latticeIDs. check that
+                        # each of the ids appears in the grid, otherwise
+                        # their blueprints are probably wrong
+                        if len([i for i in idsInGrid if i == str(latticeID)]) == 0:
+                            raise ValueError(
+                                f"latticeID {latticeID} in block blueprint '{self.name}' is expected "
+                                "to be present in the associated block grid. "
+                                "Check that the component's latticeIDs align with the block's grid."
+                            )
+
+        # for every id in grid, confirm that at least one component had it
+        if gridDesign:
+            idsInGrid = list(gridDesign.gridContents.values())
+            for idInGrid in idsInGrid:
+                if str(idInGrid) not in allLatticeIds:
+                    raise ValueError(
+                        f"ID {idInGrid} in grid {gridDesign.name} is not in any components of block {self.name}. "
+                        "All IDs in the grid must appear in at least one component."
+                    )
+
         # check that the block level mat mods use valid options in the same way
         # as we did for the by-component mods above
         validMatModOptions = self._getBlockwiseMaterialModifierOptions(
diff --git a/armi/reactor/reactorParameters.py b/armi/reactor/reactorParameters.py
index 5517c194..51fb5159 100644
--- a/armi/reactor/reactorParameters.py
+++ b/armi/reactor/reactorParameters.py
@@ -327,12 +327,6 @@ def defineCoreParameters():
             description="Max neutron flux in the core",
         )
 
-        pb.defParam(
-            "adjWeightedFisSrc",
-            units=f"1/{units.CM}^2/{units.SECONDS}^2",
-            description="Volume-integrated adjoint flux weighted fission source",
-        )
-
         pb.defParam(
             "maxDetailedDpaThisCycle",
             units=units.DPA,
@@ -459,18 +453,6 @@ def defineCoreParameters():
             default=None,
         )
 
-        pb.defParam(
-            "promptNeutronGenerationTime",
-            units=units.SECONDS,
-            description="Prompt neutron generation time",
-        )
-
-        pb.defParam(
-            "promptNeutronLifetime",
-            units=units.SECONDS,
-            description="Prompt neutron lifetime",
-        )
-
     with pDefs.createBuilder(
         default=0.0,
         location=ParamLocation.AVERAGE,
@@ -483,148 +465,12 @@ def defineCoreParameters():
             description="Fuel Axial Expansion Coefficient",
         )
 
-        pb.defParam(
-            "rxFuelAxialExpansionCoeffPerPercent",
-            units=f"{units.REACTIVITY}/{units.PERCENT}",
-            description="Fuel Axial Expansion Coefficient",
-        )
-
         pb.defParam(
             "rxGridPlateRadialExpansionCoeffPerTemp",
             units=f"{units.REACTIVITY}/{units.DEGK}",
             description="Grid Plate Radial Expansion Coefficient",
         )
 
-        pb.defParam(
-            "rxAclpRadialExpansionCoeffPerTemp",
-            units=f"{units.REACTIVITY}/{units.DEGK}",
-            description="ACLP Radial Expansion Coefficient",
-        )
-
-        pb.defParam(
-            "rxControlRodDrivelineExpansionCoeffPerTemp",
-            units=f"{units.REACTIVITY}/{units.DEGK}",
-            description="control rod driveline expansion coefficient",
-        )
-
-        pb.defParam(
-            "rxCoreWideCoolantVoidWorth",
-            units=f"{units.REACTIVITY}",
-            description="Core-Wide Coolant Void Worth",
-        )
-
-        pb.defParam(
-            "rxSpatiallyDependentCoolantVoidWorth",
-            units=f"{units.REACTIVITY}",
-            description="Spatially-Dependent Coolant Void Worth",
-        )
-
-        # FUEL COEFFICIENTS
-        pb.defParam(
-            "rxFuelDensityCoeffPerTemp",
-            units=f"{units.REACTIVITY}/{units.DEGK}",
-            description="Fuel Density Coefficient",
-        )
-
-        pb.defParam(
-            "rxFuelDopplerCoeffPerTemp",
-            units=f"{units.REACTIVITY}/{units.DEGK}",
-            description="Fuel Doppler Coefficient",
-        )
-
-        pb.defParam(
-            "rxFuelDopplerConstant",
-            units=f"{units.REACTIVITY}*{units.DEGK}^(n-1)",
-            description="Fuel Doppler Constant",
-        )
-
-        pb.defParam(
-            "rxFuelVoidedDopplerCoeffPerTemp",
-            units=f"{units.REACTIVITY}/{units.DEGK}",
-            description="Fuel Voided-Coolant Doppler Coefficient",
-        )
-
-        pb.defParam(
-            "rxFuelVoidedDopplerConstant",
-            units=f"{units.REACTIVITY}*{units.DEGK}^(n-1)",
-            description="Fuel Voided-Coolant Doppler Constant",
-        )
-
-        pb.defParam(
-            "rxFuelTemperatureCoeffPerTemp",
-            units=f"{units.REACTIVITY}/{units.DEGK}",
-            description="Fuel Temperature Coefficient",
-        )
-
-        pb.defParam(
-            "rxFuelVoidedTemperatureCoeffPerTemp",
-            units=f"{units.REACTIVITY}/{units.DEGK}",
-            description="Fuel Voided-Coolant Temperature Coefficient",
-        )
-
-        # CLAD COEFFICIENTS
-        pb.defParam(
-            "rxCladDensityCoeffPerTemp",
-            units=f"{units.REACTIVITY}/{units.DEGK}",
-            description="Clad Density Coefficient",
-        )
-
-        pb.defParam(
-            "rxCladDopplerCoeffPerTemp",
-            units=f"{units.REACTIVITY}/{units.DEGK}",
-            description="Clad Doppler Coefficient",
-        )
-
-        pb.defParam(
-            "rxCladDopplerConstant",
-            units=f"{units.REACTIVITY}*{units.DEGK}^(n-1)",
-            description="Clad Doppler Constant",
-        )
-
-        pb.defParam(
-            "rxCladTemperatureCoeffPerTemp",
-            units=f"{units.REACTIVITY}/{units.DEGK}",
-            description="Clad Temperature Coefficient",
-        )
-
-        # STRUCTURE COEFFICIENTS
-        pb.defParam(
-            "rxStructureDensityCoeffPerTemp",
-            units=f"{units.REACTIVITY}/{units.DEGK}",
-            description="Structure Density Coefficient",
-        )
-
-        pb.defParam(
-            "rxStructureDopplerCoeffPerTemp",
-            units=f"{units.REACTIVITY}/{units.DEGK}",
-            description="Structure Doppler Coefficient",
-        )
-
-        pb.defParam(
-            "rxStructureDopplerConstant",
-            units=f"{units.REACTIVITY}*{units.DEGK}^(n-1)",
-            description="Structure Doppler Constant",
-        )
-
-        pb.defParam(
-            "rxStructureTemperatureCoeffPerTemp",
-            units=f"{units.REACTIVITY}/{units.DEGK}",
-            description="Structure Temperature Coefficient",
-        )
-
-        # COOLANT COEFFICIENTS
-        pb.defParam(
-            "rxCoolantDensityCoeffPerTemp",
-            units=f"{units.REACTIVITY}/{units.DEGK}",
-            description="Coolant Density Coefficient",
-        )
-
-        pb.defParam(
-            "rxCoolantTemperatureCoeffPerTemp",
-            units=f"{units.REACTIVITY}/{units.DEGK}",
-            description="Coolant Temperature Coefficient",
-        )
-
     with pDefs.createBuilder(
         location=ParamLocation.AVERAGE, categories=["equilibrium"]
     ) as pb:


Test patch:
diff --git a/armi/bookkeeping/db/tests/test_database3.py b/armi/bookkeeping/db/tests/test_database3.py
index d5777216..9d974d96 100644
--- a/armi/bookkeeping/db/tests/test_database3.py
+++ b/armi/bookkeeping/db/tests/test_database3.py
@@ -18,6 +18,7 @@ import shutil
 import subprocess
 import unittest
 from glob import glob
+from unittest.mock import Mock, patch
 
 import h5py
 import numpy as np
@@ -30,7 +31,10 @@ from armi.reactor import parameters
 from armi.reactor.excoreStructure import ExcoreCollection, ExcoreStructure
 from armi.reactor.reactors import Core, Reactor
 from armi.reactor.spentFuelPool import SpentFuelPool
-from armi.settings.fwSettings.globalSettings import CONF_SORT_REACTOR
+from armi.settings.fwSettings.globalSettings import (
+    CONF_GROW_TO_FULL_CORE_AFTER_LOAD,
+    CONF_SORT_REACTOR,
+)
 from armi.testing import loadTestReactor, reduceTestReactorRings
 from armi.tests import TEST_ROOT, mockRunLogs
 from armi.utils import getPreviousTimeNode, safeCopy
@@ -188,6 +192,29 @@ class TestDatabase(unittest.TestCase):
         self.assertIn((2, 0), hist["chargeTime"].keys())
         self.assertEqual(hist["chargeTime"][(2, 0)], 2)
 
+    def test_fullCoreOnDbLoad(self):
+        """Test we can expand a reactor to full core when loading from DB via settings."""
+        self.assertFalse(self.r.core.isFullCore)
+        self.db.writeToDB(self.r)
+        cs = self.db.loadCS()
+        cs = cs.modified(newSettings={CONF_GROW_TO_FULL_CORE_AFTER_LOAD: True})
+        r: Reactor = self.db.load(0, 0, cs=cs)
+        self.assertTrue(r.core.isFullCore)
+
+    def test_dontExpandIfFullCoreInDB(self):
+        """Test that a full core reactor in the database is not expanded further."""
+        self.assertFalse(self.r.core.isFullCore)
+        self.db.writeToDB(self.r)
+        cs = self.db.loadCS()
+        cs = cs.modified(newSettings={CONF_GROW_TO_FULL_CORE_AFTER_LOAD: True})
+        mockGrow = Mock()
+        with (
+            patch("armi.reactor.cores.Core.isFullCore", Mock(return_value=True)),
+            patch("armi.reactor.cores.Core.growToFullCore", mockGrow),
+        ):
+            self.db.load(0, 0, cs=cs)
+        mockGrow.assert_not_called()
+
 
 class TestDatabaseSmaller(unittest.TestCase):
     """Tests for the Database class, that can use a smaller test reactor."""
diff --git a/armi/operators/tests/test_operatorSnapshots.py b/armi/operators/tests/test_operatorSnapshots.py
index cb07aaab..5d4dc2e1 100644
--- a/armi/operators/tests/test_operatorSnapshots.py
+++ b/armi/operators/tests/test_operatorSnapshots.py
@@ -14,13 +14,17 @@
 
 """Tests for operator snapshots."""
 import unittest
+from pathlib import Path
+from unittest.mock import Mock
 
 from armi import settings
 from armi.bookkeeping.db.databaseInterface import DatabaseInterface
 from armi.operators import getOperatorClassFromSettings
 from armi.operators.runTypes import RunTypes
 from armi.operators.snapshots import OperatorSnapshots
-from armi.reactor.tests import test_reactors
+from armi.settings.fwSettings.globalSettings import CONF_GROW_TO_FULL_CORE_AFTER_LOAD
+from armi.testing import loadTestReactor, reduceTestReactorRings
+from armi.tests import TEST_ROOT
 
 
 class TestOperatorSnapshots(unittest.TestCase):
@@ -32,7 +36,7 @@ class TestOperatorSnapshots(unittest.TestCase):
         newSettings["branchVerbosity"] = "important"
         newSettings["nCycles"] = 1
         newSettings["dumpSnapshot"] = ["000000", "008000", "016005"]
-        o1, self.r = test_reactors.loadTestReactor(
+        o1, self.r = loadTestReactor(
             customSettings=newSettings,
             inputFileName="smallestTestReactor/armiRunSmallest.yaml",
         )
@@ -57,8 +61,8 @@ class TestOperatorSnapshots(unittest.TestCase):
     def test_mainOperate(self):
         # Mock some tooling that we aren't testing
         self.o.interactBOL = lambda: None
-        self.o.getInterface = (
-            lambda s: self.dbi if s == "database" else super().getInterface(s)
+        self.o.getInterface = lambda s: (
+            self.dbi if s == "database" else super().getInterface(s)
         )
 
         self.assertEqual(self.r.core.p.power, 0.0)
@@ -100,3 +104,50 @@ class TestOperatorSnapshotsSettings(unittest.TestCase):
         cs = cs.modified(newSettings={"runType": RunTypes.SNAPSHOTS})
         clazz = getOperatorClassFromSettings(cs)
         self.assertEqual(clazz, OperatorSnapshots)
+
+
+class TestOperatorSnapshotFullCoreExpansion(unittest.TestCase):
+    """Test that a snapshot operator can do full core analysis with a 1/3 core DB."""
+
+    DB_PATH = Path("test_operator_snapshot_full_core_expansion.h5")
+
+    @classmethod
+    def setUpClass(cls):
+        o, cls.symmetricReactor = loadTestReactor(TEST_ROOT)
+        reduceTestReactorRings(cls.symmetricReactor, o.cs, maxNumRings=2)
+        dbi: DatabaseInterface = next(
+            filter(lambda i: isinstance(i, DatabaseInterface), o.interfaces)
+        )
+        dbi.initDB(cls.DB_PATH)
+        dbi.writeDBEveryNode()
+        dbi.closeDB()
+
+        cls.snapshotSettings: settings.Settings = o.cs.modified(
+            newSettings={
+                "runType": RunTypes.SNAPSHOTS,
+                "reloadDBName": str(cls.DB_PATH),
+            }
+        )
+
+    def test_fullCoreFromThirdCore(self):
+        self.assertFalse(self.symmetricReactor.core.isFullCore)
+        cs = self.snapshotSettings.modified(
+            newSettings={
+                CONF_GROW_TO_FULL_CORE_AFTER_LOAD: True,
+                "dumpSnapshot": ["0000"],
+            }
+        )
+        o = getOperatorClassFromSettings(cs)(cs)
+        self.assertIsInstance(o, OperatorSnapshots)
+        o.r = self.symmetricReactor
+        # Just want Database interface not history tracker not reporting not etc.
+        o.addInterface(DatabaseInterface(o.r, o.cs))
+        # Mock interactAllBOC so we don't do iteract every nodes
+        # We just want to trigger the re-attachment of the loaded reactor
+        o.interactAllBOC = Mock(return_value=True)
+        o.operate()
+        self.assertTrue(o.r.core.isFullCore)
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.DB_PATH.unlink()
diff --git a/armi/reactor/blueprints/tests/test_blockBlueprints.py b/armi/reactor/blueprints/tests/test_blockBlueprints.py
index 6215bee4..970921a8 100644
--- a/armi/reactor/blueprints/tests/test_blockBlueprints.py
+++ b/armi/reactor/blueprints/tests/test_blockBlueprints.py
@@ -129,13 +129,41 @@ grids:
          - - -  1 1 1 1
            - - 1 1 2 1 1
             - 1 1 1 1 1 1
-             1 3 1 2 1 3 1
+             1 2 1 2 1 2 1
               1 1 1 1 1 1
                1 1 2 1 1
                 1 1 1 1
 
 """
 
+FULL_BP_ERRANT_ID = (
+    FULL_BP.split("lattice map:")[0]
+    + """lattice map: |
+         - - -  1 1 1 1
+           - - 1 1 1 1 1
+            - 1 1 1 1 1 1
+             1 1 1 1 1 1 1
+              1 1 1 1 1 1
+               1 1 1 1 1
+                1 1 1 1
+
+"""
+)
+
+FULL_BP_NO_COMP = (
+    FULL_BP.split("lattice map:")[0]
+    + """lattice map: |
+         - - -  1 1 1 1
+           - - 1 1 1 1 1
+            - 1 1 1 1 1 1
+             1 3 1 1 1 3 1
+              1 1 1 1 1 1
+               1 1 1 1 1
+                1 1 1 1
+
+"""
+)
+
 FULL_BP_GRID = (
     FULL_BP.split("lattice map:")[0]
     + """grid contents:
@@ -276,7 +304,7 @@ class TestGriddedBlock(unittest.TestCase):
         gridDesign = bDesign._getGridDesign(self.blueprints)
         grid = gridDesign.construct()
         locators = gridDesign.getLocators(grid, ["2"])
-        self.assertEqual(len(locators), 3)
+        self.assertEqual(len(locators), 5)
         self.assertIs(grid[locators[0].getCompleteIndices()], locators[0])
 
     def test_blockLattice(self):
@@ -297,6 +325,37 @@ class TestGriddedBlock(unittest.TestCase):
                 seen = True
         self.assertTrue(seen)
 
+    def test_componentsNotInLattice(self):
+        """
+        Ensure that we catch cases when a component is expected to be in the grid,
+        but is not. In this case, latticeID "2" is not in the lattice.
+        """
+        with self.assertRaises(ValueError) as ee:
+            with io.StringIO(FULL_BP_ERRANT_ID) as stream:
+                self.blueprints = blueprints.Blueprints.load(stream)
+                self.blueprints._prepConstruction(self.cs)
+
+            self.assertIn(
+                "Check that the component's latticeIDs align with the block's grid.",
+                ee.args[0],
+            )
+
+    def test_latticeNotInComponents(self):
+        """
+        Ensure that we catch cases when a latticeID listed in the grid is not present
+        in any of the components on the block. In this case, latticeID "2" is not
+        in the lattice.
+        """
+        with self.assertRaises(ValueError) as ee:
+            with io.StringIO(FULL_BP_NO_COMP) as stream:
+                self.blueprints = blueprints.Blueprints.load(stream)
+                self.blueprints._prepConstruction(self.cs)
+
+            self.assertIn(
+                "All IDs in the grid must appear in at least one component.",
+                ee.args[0],
+            )
+
     def test_nonLatticeComponentHasRightMult(self):
         """Make sure non-grid components in blocks with grids get the right multiplicity."""
         aDesign = self.blueprints.assemDesigns.bySpecifier["IC"]
diff --git a/armi/reactor/blueprints/tests/test_gridBlueprints.py b/armi/reactor/blueprints/tests/test_gridBlueprints.py
index 6591008d..d0f6a375 100644
--- a/armi/reactor/blueprints/tests/test_gridBlueprints.py
+++ b/armi/reactor/blueprints/tests/test_gridBlueprints.py
@@ -354,7 +354,7 @@ class TestGridBlueprintsSection(unittest.TestCase):
             before, after = outText.split("lattice map:")
             self.assertGreater(len(before), 100)
             self.assertGreater(len(after), 20)
-            self.assertIn("1 3 1 2 1 3 1", after, msg="lattice map not showing up")
+            self.assertIn("1 2 1 2 1 2 1", after, msg="lattice map not showing up")
             self.assertNotIn(
                 "- -3", after, msg="grid contents are showing up when they shouldn't"
             )
diff --git a/armi/tests/c5g7/c5g7-blueprints.yaml b/armi/tests/c5g7/c5g7-blueprints.yaml
index f2428b0d..0fdb2345 100644
--- a/armi/tests/c5g7/c5g7-blueprints.yaml
+++ b/armi/tests/c5g7/c5g7-blueprints.yaml
@@ -172,10 +172,6 @@ blocks:
             widthInner: 21.42
             widthOuter: 21.42
             mult: 1.0
-            latticeIDs: [FC]
-            # latticeIDs is needed to ensure that the center of the bounding
-            # Component shares the same center/origin as the rest of the
-            # components in the block. See #332."
 # end-block-uo2
     mox: &block_mox
         grid name: MOX grid