Repository: terrapower/armi
Pull request #2142 (2025-05-22 23:15:46)

Quieting warnings from Block.getComponent
## What is the change? Why is it being made?

Currently, there is a mountain of warnings that are printed to the log from `getComponent` for blocks that don't have wire/clad due to a variety of methods. Those methods all attempt to grab the wire or clad component with `getComponent`, then check if the return value was `None`. The initial call to `getComponent` doesn't use the `quiet` argument though so it prints a warning to log that says it is returning None which the method is already handling. This change adds the quiet flag in select spots to reduce the unnecessary warning output.  


## SCR Information

<!-- MANDATORY: uncomment one-and-only-one of these -->
Change Type: trivial

<!-- MANDATORY: Describe the change in one sentence -->
One-Sentence Description: Reducing warning output from `getComponent`

<!-- MANDATORY: Describe any impact on the requirements, all on one line -->
One-line Impact on Requirements: None


---

## 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).
- N/A [ ] [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`.


john-science: I don't really want to be in the business of putting `quiet` in the method declaration of every method in ARMI. That would get messy and cumbersome.

Happily, we already have another tool in ARMI that does this. All `runLog` statements can use `single=True`, so the log statement only gets printed once. And we use this widely throughout ARMI:

https://github.com/terrapower/armi/blob/e4d34d37ae7cf32585f2125a5163844616bb446d/armi/reactor/cores.py#L425-L429

https://github.com/terrapower/armi/blob/e4d34d37ae7cf32585f2125a5163844616bb446d/armi/reactor/blocks.py#L792-L795

https://github.com/terrapower/armi/blob/e4d34d37ae7cf32585f2125a5163844616bb446d/armi/reactor/blocks.py#L2006-L2009

I think this is a better approach, because it only happens AT the log message, and doesn't clutter up the ARMI API. 

Worst case scenario, you need to tweak the log statement a little bit, so it is fully duplicate.

Thoughts?!?

Patch:
diff --git a/armi/reactor/assemblies.py b/armi/reactor/assemblies.py
index 0761cded..7aa000c9 100644
--- a/armi/reactor/assemblies.py
+++ b/armi/reactor/assemblies.py
@@ -262,15 +262,14 @@ class Assembly(composites.Composite):
         )
 
     def coords(self):
-        """Return the location of the assembly in the plane using cartesian global
-        coordinates.
+        """Return the location of the assembly in the plane using cartesian global coordinates.
 
         .. impl:: Assembly coordinates are retrievable.
             :id: I_ARMI_ASSEM_POSI1
             :implements: R_ARMI_ASSEM_POSI
 
-            In this method, the spatialLocator of an Assembly is leveraged to return
-            its physical (x,y) coordinates in cm.
+            In this method, the spatialLocator of an Assembly is leveraged to return its physical
+            (x,y) coordinates in cm.
         """
         x, y, _z = self.spatialLocator.getGlobalCoordinates()
         return (x, y)
@@ -285,10 +284,8 @@ class Assembly(composites.Composite):
         try:
             return self[0].getArea()
         except IndexError:
-            runLog.warning(
-                "{} has no blocks and therefore no area. Assuming 1.0".format(self)
-            )
-            return 1.0
+            runLog.warning(f"{self} has no blocks and therefore no area.")
+            return None
 
     def getVolume(self):
         """Calculate the total assembly volume in cm^3."""
@@ -300,11 +297,11 @@ class Assembly(composites.Composite):
 
         Notes
         -----
-        If there is no plenum blocks in the assembly, a plenum volume of 0.0 is returned
+        If there is no plenum blocks in the assembly, a plenum volume of 0.0 is returned.
 
         Warning
         -------
-        This is a bit design-specific for pinned assemblies
+        This is a bit design-specific for pinned assemblies.
         """
         plenumVolume = 0.0
         for b in self.iterChildrenWithFlags(Flags.PLENUM):
@@ -349,8 +346,8 @@ class Assembly(composites.Composite):
                     "".format(b, b.getHeight(), refB, refB.getHeight())
                 )
             else:
-                # b is larger than refB. Split b up by splitting it into several smaller
-                # blocks of refBs
+                # b is larger than refB. Split b up by splitting it into several smaller blocks of
+                # refBs
                 heightToChop = b.getHeight()
                 heightChopped = 0.0
                 while (
@@ -364,16 +361,12 @@ class Assembly(composites.Composite):
                     heightChopped += refB.getHeight()
                     newBlocks += 1
                     runLog.important(
-                        "Added a new block {0} of height {1}".format(
-                            newB, newB.getHeight()
-                        )
+                        f"Added a new block {newB} of height {newB.getHeight()}"
                     )
-                    runLog.important(
-                        "Chopped {0} of {1}".format(heightChopped, heightToChop)
-                    )
-                newBlocks -= (
-                    1  # subtract one because we eliminated the original b completely.
-                )
+                    runLog.important(f"Chopped {heightChopped} of {heightToChop}")
+
+                # subtract one because we eliminated the original b completely.
+                newBlocks -= 1
 
         self.removeAll()
         self.spatialGrid = grids.AxialGrid.fromNCells(len(newBlockStack))
@@ -388,20 +381,19 @@ class Assembly(composites.Composite):
         Parameters
         ----------
         centers : bool, optional
-            Return centers instead of tops. If centers and zeroesAtFuel the zero point
-            will be center of first fuel.
+            Return centers instead of tops. If centers and zeroesAtFuel the zero point will be
+            center of first fuel.
 
         zeroAtFuel : bool, optional
-            If true will make the (bottom or center depending on centers) of the
-            first fuel block be the zero point instead of the bottom of the first block.
+            If true will make the (bottom or center depending on centers) of the first fuel block be
+            the zero point instead of the bottom of the first block.
 
         See Also
         --------
-        armi.reactor.assemblies.Assembly.makeAxialSnapList : makes index-based lookup of
-        axial mesh
+        armi.reactor.assemblies.Assembly.makeAxialSnapList : makes index-based lookup of axial mesh
 
-        armi.reactor.reactors.Reactor.findAllAxialMeshPoints : gets a global list of all
-        of these, plus finer res.
+        armi.reactor.reactors.Reactor.findAllAxialMeshPoints : gets a global list of all of these,
+        plus finer res.
         """
         bottom = 0.0
         meshVals = []
diff --git a/armi/reactor/blocks.py b/armi/reactor/blocks.py
index d9350a86..fc0e58c1 100644
--- a/armi/reactor/blocks.py
+++ b/armi/reactor/blocks.py
@@ -1985,9 +1985,13 @@ class HexBlock(Block):
     def verifyBlockDims(self):
         """Perform some checks on this type of block before it is assembled."""
         try:
-            wireComp = self.getComponent(Flags.WIRE)
+            wireComp = self.getComponent(
+                Flags.WIRE, quiet=True
+            )  # Quiet because None case is checked for below
             ductComps = self.getComponents(Flags.DUCT)
-            cladComp = self.getComponent(Flags.CLAD)
+            cladComp = self.getComponent(
+                Flags.CLAD, quiet=True
+            )  # Quiet because None case is checked for below
         except ValueError:
             # there are probably more that one clad/wire, so we really dont know what this block looks like
             runLog.info(
@@ -1997,10 +2001,7 @@ class HexBlock(Block):
             return
 
         # check wire wrap in contact with clad
-        if (
-            self.getComponent(Flags.CLAD) is not None
-            and self.getComponent(Flags.WIRE) is not None
-        ):
+        if cladComp is not None and wireComp is not None:
             wwCladGap = self.getWireWrapCladGap(cold=True)
             if round(wwCladGap, 6) != 0.0:
                 runLog.warning(
@@ -2045,7 +2046,9 @@ class HexBlock(Block):
             Returns the diameteral gap between the outer most pins in a hex pack to the duct inner
             face to face in cm.
         """
-        wire = self.getComponent(Flags.WIRE)
+        wire = self.getComponent(
+            Flags.WIRE, quiet=True
+        )  # Quiet because None case is checked for below
         ducts = sorted(self.getChildrenWithFlags(Flags.DUCT))
         duct = None
         if any(ducts):
@@ -2058,7 +2061,9 @@ class HexBlock(Block):
                 # has no ip and is circular on inside so following
                 # code will not work
                 duct = None
-        clad = self.getComponent(Flags.CLAD)
+        clad = self.getComponent(
+            Flags.CLAD, quiet=True
+        )  # Quiet because None case is checked for below
         if any(c is None for c in (duct, wire, clad)):
             return None
 
@@ -2232,8 +2237,8 @@ class HexBlock(Block):
 
     def hasPinPitch(self):
         """Return True if the block has enough information to calculate pin pitch."""
-        return (self.getComponent(Flags.CLAD) is not None) and (
-            self.getComponent(Flags.WIRE) is not None
+        return (self.getComponent(Flags.CLAD, quiet=True) is not None) and (
+            self.getComponent(Flags.WIRE, quiet=True) is not None
         )
 
     def getPinPitch(self, cold=False):
@@ -2254,8 +2259,12 @@ class HexBlock(Block):
             pin pitch in cm
         """
         try:
-            clad = self.getComponent(Flags.CLAD)
-            wire = self.getComponent(Flags.WIRE)
+            clad = self.getComponent(
+                Flags.CLAD, quiet=True
+            )  # Quiet because None case is checked for below
+            wire = self.getComponent(
+                Flags.WIRE, quiet=True
+            )  # Quiet because None case is checked for below
         except ValueError:
             raise ValueError(
                 f"Block {self} has multiple clad and wire components, so pin pitch is not well-"
diff --git a/armi/reactor/composites.py b/armi/reactor/composites.py
index 4f3bbc8f..2bdfc957 100644
--- a/armi/reactor/composites.py
+++ b/armi/reactor/composites.py
@@ -152,11 +152,9 @@ class FlagSerializer(parameters.Serializer):
 
         if version != cls.version:
             raise ValueError(
-                "The FlagSerializer version used to pack the data ({}) does not match "
-                "the current version ({})! This database either needs to be migrated, "
-                "or on-the-fly inter-version conversion needs to be implemented.".format(
-                    version, cls.version
-                )
+                f"The FlagSerializer version used to pack the data ({version}) does not match "
+                f"the current version ({cls.version})! This database either needs to be migrated, "
+                "or on-the-fly inter-version conversion needs to be implemented."
             )
 
         flagSetIn = set(flagOrderPassed)
@@ -362,8 +360,8 @@ class ArmiObject(metaclass=CompositeModelType):
             runLog.error(f"could not compare {self} and {other}")
             raise ValueError(
                 "Composite grids must be the same to compare:\n"
-                "This grid: {}\n"
-                "Other grid: {}".format(self.spatialGrid, other.spatialGrid)
+                f"This grid: {self.spatialGrid}\n"
+                f"Other grid: {other.spatialGrid}"
             )
         try:
             t1 = tuple(reversed(self.spatialLocator.getCompleteIndices()))
@@ -371,9 +369,7 @@ class ArmiObject(metaclass=CompositeModelType):
             return t1 < t2
         except ValueError:
             runLog.error(
-                "failed to compare {} and {}".format(
-                    self.spatialLocator, other.spatialLocator
-                )
+                f"failed to compare {self.spatialLocator} and {other.spatialLocator}"
             )
             raise
 
@@ -424,7 +420,7 @@ class ArmiObject(metaclass=CompositeModelType):
             c.parent = self
 
     def __repr__(self):
-        return "<{}: {}>".format(self.__class__.__name__, self.name)
+        return f"<{self.__class__.__name__}: {self.name}>"
 
     def __format__(self, spec):
         return format(str(self), spec)
@@ -971,8 +967,7 @@ class ArmiObject(metaclass=CompositeModelType):
                 nuclideNames.extend(self._getNuclidesFromSpecifier(ns))
         else:
             raise TypeError(
-                "nucSpec={0} is an invalid specifier. It is a {1}"
-                "".format(nucSpec, type(nucSpec))
+                f"nucSpec={nucSpec} is an invalid specifier. It is a {type(nucSpec)}"
             )
 
         # expand elementals if appropriate.
@@ -1053,9 +1048,7 @@ class ArmiObject(metaclass=CompositeModelType):
         rho = self.density()
         if not rho:
             raise ValueError(
-                "Cannot set mass fractions on {} because the mass density is zero.".format(
-                    self
-                )
+                f"Cannot set mass fractions on {self} because the mass density is zero."
             )
         oldMassFracs = self.getMassFracs()
         totalFracSet = 0.0
@@ -1149,9 +1142,7 @@ class ArmiObject(metaclass=CompositeModelType):
         self.clearCache()  # don't keep densities around or anything.
         if val > 1.0 or val < 0:
             raise ValueError(
-                "Invalid mass fraction {0} for {1}/{2} in {3}".format(
-                    val, nuclideToAdjust, elementToAdjust, self.getName()
-                )
+                f"Invalid mass fraction {val} for {nuclideToAdjust}/{elementToAdjust} in {self.getName()}"
             )
         if not nuclideToAdjust and not elementToAdjust:
             raise TypeError(
@@ -1218,9 +1209,7 @@ class ArmiObject(metaclass=CompositeModelType):
         # error checking.
         if abs(newA - val) > 1e-10:
             runLog.error(
-                "Adjust Mass fraction did not adjust {0} from {1} to {2}. It got to {3}".format(
-                    adjustNuclides, A, val, newA
-                )
+                f"Adjust Mass fraction did not adjust {adjustNuclides} from {A} to {val}. It got to {newA}"
             )
             raise RuntimeError("Failed to adjust mass fraction.")
 
@@ -1381,8 +1370,8 @@ class ArmiObject(metaclass=CompositeModelType):
             )
             if lfpMass:
                 raise RuntimeError(
-                    "Composite {} is attempting to expand lumped fission products, but does not have "
-                    "an lfpCollection.".format(self)
+                    f"Composite {self} is attempting to expand lumped fission products, but does not have "
+                    "an lfpCollection."
                 )
         return numberDensities
 
@@ -1489,10 +1478,8 @@ class ArmiObject(metaclass=CompositeModelType):
             activeVolumeFrac = 1.0
             if val:
                 raise ValueError(
-                    "The nuclide {} does not exist in any children of {}; "
-                    "cannot set its number density to {}. The nuclides here are: {}".format(
-                        nucName, self, val, self.getNuclides()
-                    )
+                    f"The nuclide {nucName} does not exist in any children of {self}; "
+                    f"cannot set its number density to {val}. The nuclides here are: {self.getNuclides()}"
                 )
         else:
             activeVolumeFrac = sum(
@@ -1713,7 +1700,7 @@ class ArmiObject(metaclass=CompositeModelType):
             except AttributeError:
                 coreMult = self.parent.powerMultiplier
             if not coreMult:
-                raise ValueError("powerMultiplier is equal to {}".format(coreMult))
+                raise ValueError(f"powerMultiplier is equal to {coreMult}")
         else:
             coreMult = 1.0
 
@@ -1791,9 +1778,7 @@ class ArmiObject(metaclass=CompositeModelType):
                     if weight < 0:
                         # Just for conservatism, do not allow negative weights.
                         raise ValueError(
-                            "Weighting value ({0},{1}) cannot be negative.".format(
-                                weightingParam, weight
-                            )
+                            f"Weighting value ({weightingParam},{weight}) cannot be negative."
                         )
                 else:
                     weight = 1.0
@@ -1808,9 +1793,8 @@ class ArmiObject(metaclass=CompositeModelType):
                     total += child.p[param] * weight
         if not weightSum:
             raise ValueError(
-                "Cannot calculate {0}-weighted average of {1} in {2}. "
-                "Weights sum to zero. typeSpec is {3}"
-                "".format(weightingParam, param, self, typeSpec)
+                f"Cannot calculate {weightingParam}-weighted average of {param} in {self}. "
+                f"Weights sum to zero. typeSpec is {typeSpec}"
             )
         return total / weightSum
 
@@ -2114,8 +2098,8 @@ class ArmiObject(metaclass=CompositeModelType):
         """
         if average:
             raise NotImplementedError(
-                "{} class has no method for producing average MG flux -- try"
-                "using blocks".format(self.__class__)
+                f"{self.__class__} class has no method for producing average MG flux -- try"
+                "using blocks"
             )
 
         volume = volume or self.getVolume()
@@ -2311,9 +2295,7 @@ class ArmiObject(metaclass=CompositeModelType):
         if nComp == 0:
             return None
         elif nComp > 1:
-            raise ValueError(
-                "More than one component named '{}' in {}".format(self, name)
-            )
+            raise ValueError(f"More than one component named '{self}' in {name}")
         else:
             return components[0]
 
@@ -2571,9 +2553,7 @@ class Composite(ArmiObject):
         """Add one new child."""
         if obj in self:
             raise RuntimeError(
-                "Cannot add {0} because it has already been added to {1}.".format(
-                    obj, self
-                )
+                f"Cannot add {obj} because it has already been added to {self}."
             )
         obj.parent = self
         self._children.append(obj)
@@ -2588,8 +2568,8 @@ class Composite(ArmiObject):
         """Move to specific location in parent. Often in a grid."""
         if locator.grid.armiObject is not self.parent:
             raise ValueError(
-                "Cannot move {} to a location in  {}, which is not its parent ({})."
-                "".format(self, locator.grid.armiObject, self.parent)
+                f"Cannot move {self} to a location in  {locator.grid.armiObject}"
+                ", which is not its parent ({self.parent})."
             )
         self.spatialLocator = locator
 
@@ -2597,9 +2577,7 @@ class Composite(ArmiObject):
         """Insert an object into the list of children at a particular index."""
         if obj in self._children:
             raise RuntimeError(
-                "Cannot insert {0} because it has already been added to {1}.".format(
-                    obj, self
-                )
+                f"Cannot insert {obj} because it has already been added to {self}."
             )
         obj.parent = self
         self._children.insert(index, obj)
@@ -2845,7 +2823,7 @@ class Composite(ArmiObject):
         )
         allComps = [c for c in genItems if hasattr(c, "p")]
         sendBuf = [c.p.getSyncData() for c in allComps]
-        runLog.debug("syncMpiState has {} comps".format(len(allComps)))
+        runLog.debug(f"syncMpiState has {len(allComps)} comps")
 
         try:
             context.MPI_COMM.barrier()  # sync up
@@ -2856,7 +2834,7 @@ class Composite(ArmiObject):
             msg = ["Failure while trying to allgather."]
             for ci, compData in enumerate(sendBuf):
                 if compData is not None:
-                    msg += ["sendBuf[{}]: {}".format(ci, compData)]
+                    msg += [f"sendBuf[{ci}]: {compData}"]
             runLog.error("\n".join(msg))
             raise
 
@@ -2867,9 +2845,7 @@ class Composite(ArmiObject):
 
         if len(compsPerNode) != 1:
             raise ValueError(
-                "The workers have different reactor sizes! comp lengths: {}".format(
-                    compsPerNode
-                )
+                f"The workers have different reactor sizes! comp lengths: {compsPerNode}"
             )
 
         for ci, comp in enumerate(allComps):
@@ -2903,10 +2879,8 @@ class Composite(ArmiObject):
 
         self._markSynchronized()
         runLog.extra(
-            "Synchronized reactor over MPI in {:.4f} seconds, {:.4f} seconds in MPI "
-            "allgather. count:{}".format(
-                timeit.default_timer() - startTime, allGatherTime, syncCount
-            )
+            f"Synchronized reactor over MPI in {timeit.default_timer() - startTime:.4f} seconds"
+            f", {allGatherTime:.4f} seconds in MPI allgather. count:{syncCount}"
         )
 
         return syncCount
@@ -3347,7 +3321,7 @@ def getReactionRateDict(nucName, lib, xsSuffix, mgFlux, nDens):
     Assume there is no n3n cross section in ISOTXS
     """
     nucLabel = nuclideBases.byName[nucName].label
-    key = "{}{}".format(nucLabel, xsSuffix)
+    key = f"{nucLabel}{xsSuffix}"
     libNuc = lib[key]
     rxnRates = {"n3n": 0}
     for rxName, mgXSs in [
diff --git a/doc/developer/documenting.rst b/doc/developer/documenting.rst
index 09d4b81b..f01d6c35 100644
--- a/doc/developer/documenting.rst
+++ b/doc/developer/documenting.rst
@@ -5,25 +5,29 @@ Documenting ARMI
 ****************
 
 ARMI uses the `Sphinx <https://www.sphinx-doc.org/en/master/>`_ documentation system to compile the 
-web-based documentation from in-code docstrings and hand-created 
+ARMI documentation into HTML and PDF from in-code docstrings and hand-created
 `ReStructedText files <https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html>`_.
 This provides several benefits:
  
-* We can revise and track the documentation in lock-step with the code itself, in the same 
-  source code repository
-* We can make use of hyperlinked cross-references that stay up to date as the code is expanded 
-  or refactored.
-* We can run specific code tests during documentation building to ensure the documentation 
-  examples remain valid
+* We can revise and track the documentation in lock-step with the code itself, in the same source
+  code repository
+* We can make use of hyperlinked cross-references that stay up to date as the code is expanded or
+  refactored.
+* We can run specific code tests during documentation building to ensure the documentation examples
+  remain valid
 * We can auto-generate class diagrams based on the latest status of the code
+* Every Pull Request (PR) generates HTML and PDF versions of the documentation for the PR Author and
+  Reviewer
 
 We use some special Sphinx plugins that run the tutorial jupyter notebooks during documentation
-build with the most up-to-date code. 
+build with the most up-to-date code.
 
-Building the documentation
+Building the Documentation
 ==========================
 Before building documentation, ensure that you have installed the documentation requirements into
-your ARMI virtual environment with::
+your ARMI virtual environment with:
+
+.. code-block:: bash
 
     pip install -e .[docs]
 
@@ -32,32 +36,30 @@ You also need to have the following utilities available in your PATH:
 * `Graphviz <https://graphviz.org/>`_
 * `Pandoc <https://pandoc.org/>`_
 
-If you want to build the documentation into a PDF using the Sphinx LaTeX
-builder, you also need:
+If you want to build the documentation into a PDF using the Sphinx LaTeX builder, you also need:
 
 * LaTeX (`MikTeX <https://miktex.org/>`_ on Windows)
 * `ImageMagick <https://imagemagick.org/>`_
 
-The documentation depends on at least one submodule as well, so you must be sure
-it is available in your source tree with::
+The documentation depends on at least one submodule as well, so you must be sure it is available in
+your source tree with:
+
+.. code-block:: bash
 
     git submodule update --init
 
 
-To build the ARMI documentation as html, go to the ``doc`` folder and run::
+To build the ARMI documentation as html, go to the ``doc`` folder and run:
+
+.. code-block:: bash
 
     make html
 
-This will invoke Sphinx and generate a series of html files in the
-``_build/html`` folder. Open up ``index.html`` to see the documentation from
-there. A copy of the documentation is hosted online at
+This will invoke Sphinx and generate a series of html files in the ``_build/html`` folder. Open up
+``index.html`` to see the documentation from there. A copy of the documentation is hosted online at
 https://terrapower.github.io/armi/.
 
-You can suggest a change to the built documentation by copying the ``_build``
-files to a clone of the `documentation repository
-<https://github.com/terrapower/terrapower.github.io>`_ with a command like::
-
-    rsync -ahv --delete _build/html/ path/to/terrapower.github.io/armi
+You can suggest a change to the documentation by opening an ARMI PR.
 
 Documentation for ARMI plugins
 ==============================
@@ -65,7 +67,10 @@ The following subsections apply to documentation for ARMI plugins.
 
 Linking to ARMI documentation from plugins
 ------------------------------------------
-ARMI plugin documentation can feature rich hyperlinks to the ARMI API documentation with the help of the `intersphinx Sphinx plugin <http://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html>`_. The ARMI plugin documentation config file should add ``"sphinx.ext.intersphinx",`` to its active Sphinx plugin list, and change the default config to read::
+ARMI plugin documentation can feature rich hyperlinks to the ARMI API documentation with the help
+of the `intersphinx Sphinx plugin <http://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html>`_.
+The ARMI plugin documentation config file should add ``"sphinx.ext.intersphinx",`` to its active
+Sphinx plugin list, and change the default config to read::
 
     intersphinx_mapping = {
         "python": ("https://docs.python.org/3", None),
@@ -80,7 +85,9 @@ Now you can link to the ARMI documentation with links like::
 
 Automatically building apidocs of namespace packages
 ----------------------------------------------------
-Activating the ``"sphinxcontrib.apidoc",`` `Sphinx plugin <https://github.com/sphinx-contrib/apidoc>`_ enables plugin API documentation to be built with the standard ``make html`` Sphinx workflow. If your ARMI plugin is a namespace package, the following extra config is required::
+Activating the ``"sphinxcontrib.apidoc",`` `Sphinx plugin <https://github.com/sphinx-contrib/apidoc>`_
+enables plugin API documentation to be built with the standard ``make html`` Sphinx workflow. If your
+ARMI plugin is a namespace package, the following extra config is required::
 
     apidoc_extra_args = ["--implicit-namespaces"]
 
@@ -88,19 +95,20 @@ Updating the Gallery
 ====================
 The `ARMI example gallery <https://terrapower.github.io/armi/gallery/index.html>`_ is a great way
 to quickly highlight neat features and uses of ARMI. To add a new item to the gallery, add your
-example code (including the required docstring) to the ``doc/gallery-src`` folder in the ARMI
-source tree. The example will be added to the gallery during the next documentation build.
+example code (including the required docstring) to the ``doc/gallery-src`` folder in the ARMI source
+tree. The example will be added to the gallery during the next documentation build.
 
-Using Jupyter notebooks
+Using Jupyter Notebooks
 =======================
-For interactive tutorials, it's convenient to build actual Jupyter notebooks and 
-commit them to the documentation to be rendered by Sphinx using the nbsphinx plugin.
-When this is done, notebooks without any output should be committed to the repository
-so that Sphinx actually executes the notebooks with the up-to-date code when the 
-documentation is built. To do this, you can clean the output with::
+For interactive tutorials, it's convenient to build actual Jupyter notebooks and commit them to the
+documentation to be rendered by Sphinx using the nbsphinx plugin. When this is done, notebooks
+without any output should be committed to the repository so that Sphinx actually executes the
+notebooks with the up-to-date code when the  documentation is built. To do this, you can clean the
+output with:
 
-    jupyter nbconvert --ClearOutputPreprocessor.enabled=True --inplace mynotebook.ipynb
+.. code-block:: bash
 
-This should clear the output and overwrite the file. If this doesn't work, you can clear
-all output cells in the notebook web interface itself before committing the file.
+    jupyter nbconvert --ClearOutputPreprocessor.enabled=True --inplace mynotebook.ipynb
 
+This should clear the output and overwrite the file. If this doesn't work, you can clear all output
+cells in the notebook web interface itself before committing the file.


Test patch:
diff --git a/armi/reactor/tests/test_assemblies.py b/armi/reactor/tests/test_assemblies.py
index 1a24f629..226fac16 100644
--- a/armi/reactor/tests/test_assemblies.py
+++ b/armi/reactor/tests/test_assemblies.py
@@ -399,7 +399,7 @@ class Assembly_TestCase(unittest.TestCase):
         """Tests area calculation for hex assembly."""
         # Default case: for assemblies with no blocks
         a = HexAssembly("TestAssem", assemNum=10)
-        self.assertEqual(a.getArea(), 1)
+        self.assertIsNone(a.getArea())
 
         # more realistic case: a hex block/assembly
         cur = self.assembly.getArea()