Repository: terrapower/armi
Pull request #1334 (2023-06-29 17:44:27)

Adding XS files SHA1 hashes to welcome text
## What is the change?

The SHA1 hashes have been added for the XS control files, in the welcome text.

## Why is the change being made?

This is part of #1325, and the idea is that we are recording the SHA1 hashes for all the other files referenced in the master settings files, but not these. So, adding them here would allow for greater reproducibility of ARMI-based simulations.

---

## Checklist

- [X] This PR has only one purpose or idea.
- [X] Tests have been added/updated to verify that the new/changed code works.

<!-- Check the code quality -->

- [X] The code style follows [good practices](https://terrapower.github.io/armi/developer/standards_and_practices.html).
- [X] The commit message(s) follow [good practices](https://terrapower.github.io/armi/developer/tooling.html).

<!-- Check the project-level cruft -->

- [X] The [release notes](https://terrapower.github.io/armi/release/index.html) (location `doc/release/0.X.rst`) are up-to-date with any important changes.
- [X] The documentation is still up-to-date in the `doc` folder.
- [X] The dependencies are still up-to-date in `setup.py`.



Patch:
diff --git a/armi/bookkeeping/report/reportingUtils.py b/armi/bookkeeping/report/reportingUtils.py
index 35f337e1..ad0fed51 100644
--- a/armi/bookkeeping/report/reportingUtils.py
+++ b/armi/bookkeeping/report/reportingUtils.py
@@ -136,6 +136,17 @@ def writeWelcomeHeaders(o, cs):
             )
             inputInfo.append((label, fName, shaHash))
 
+        # bonus: grab the files stored in the crossSectionControl section
+        for fluxSection, fluxData in cs["crossSectionControl"].items():
+            if fluxData.xsFileLocation is not None:
+                label = f"crossSectionControl-{fluxSection}"
+                fName = fluxData.xsFileLocation
+                if isinstance(fName, list):
+                    fName = fName[0]
+                if fName and os.path.exists(fName):
+                    shaHash = getFileSHA1Hash(fName, digits=10)
+                    inputInfo.append((label, fName, shaHash))
+
         return inputInfo
 
     def _writeInputFileInformation(cs):
@@ -247,7 +258,7 @@ def writeTightCouplingConvergenceSummary(convergenceSummary):
 
 
 def writeAssemblyMassSummary(r):
-    r"""Print out things like Assembly weights to the runLog.
+    """Print out things like Assembly weights to the runLog.
 
     Parameters
     ----------
@@ -386,7 +397,7 @@ def _makeTotalAssemblyMassSummary(massSum):
 
 
 def writeCycleSummary(core):
-    r"""Prints a cycle summary to the runLog.
+    """Prints a cycle summary to the runLog.
 
     Parameters
     ----------
@@ -464,14 +475,13 @@ def setNeutronBalancesReport(core):
 
 
 def summarizePinDesign(core):
-    r"""Prints out some information about the pin assembly/duct design.
+    """Prints out some information about the pin assembly/duct design.
 
     Handles multiple types of dimensions simplistically by taking the average.
 
     Parameters
     ----------
     core : armi.reactor.reactors.Core
-
     """
     designInfo = collections.defaultdict(list)
 
@@ -546,7 +556,7 @@ def summarizePinDesign(core):
 
 
 def summarizePowerPeaking(core):
-    r"""Prints reactor Fz, Fxy, Fq.
+    """Prints reactor Fz, Fxy, Fq.
 
     Parameters
     ----------
@@ -579,7 +589,7 @@ def summarizePowerPeaking(core):
 
 
 def summarizePower(core):
-    r"""Provide an edit showing where the power is based on assembly types.
+    """Provide an edit showing where the power is based on assembly types.
 
     Parameters
     ----------
@@ -858,7 +868,7 @@ def _getComponentInputDimensions(cDesign):
 
 
 def makeCoreAndAssemblyMaps(r, cs, generateFullCoreMap=False, showBlockAxMesh=True):
-    r"""Create core and assembly design plots.
+    """Create core and assembly design plots.
 
     Parameters
     ----------
diff --git a/armi/physics/neutronics/crossSectionSettings.py b/armi/physics/neutronics/crossSectionSettings.py
index 2cc9fbe4..92c58019 100644
--- a/armi/physics/neutronics/crossSectionSettings.py
+++ b/armi/physics/neutronics/crossSectionSettings.py
@@ -427,6 +427,7 @@ class XSModelingOptions:
         The minimum number density for nuclides included in driver material for a 1D
         lattice physics model.
 
+    Notes
     -----
     Not all default attributes may be useful for your specific application and you may
     require other types of configuration options. These are provided as examples since
diff --git a/doc/release/0.2.rst b/doc/release/0.2.rst
index 7e39e76e..89947e00 100644
--- a/doc/release/0.2.rst
+++ b/doc/release/0.2.rst
@@ -8,6 +8,7 @@ Release Date: TBD
 
 What's new in ARMI
 ------------------
+#. Added SHA1 hashes of XS control files to the welcome text. (`PR#1334 <https://github.com/terrapower/armi/pull/1334>`_)
 #. TBD
 
 Bug fixes


Test patch:
diff --git a/armi/bookkeeping/report/tests/test_report.py b/armi/bookkeeping/report/tests/test_report.py
index 21f3d423..d1dc80c6 100644
--- a/armi/bookkeeping/report/tests/test_report.py
+++ b/armi/bookkeeping/report/tests/test_report.py
@@ -14,6 +14,7 @@
 
 """Really basic tests of the report Utils."""
 import logging
+import os
 import unittest
 
 from armi import runLog, settings
@@ -27,6 +28,7 @@ from armi.bookkeeping.report.reportingUtils import (
     summarizePowerPeaking,
     writeAssemblyMassSummary,
     writeCycleSummary,
+    writeWelcomeHeaders,
 )
 from armi.reactor.tests.test_reactors import loadTestReactor
 from armi.tests import mockRunLogs
@@ -131,6 +133,32 @@ class TestReport(unittest.TestCase):
             summarizePowerPeaking(r.core)
             self.assertEqual(len(mock.getStdout()), 0)
 
+    def test_writeWelcomeHeaders(self):
+        o, r = loadTestReactor()
+
+        # grab a random file (that exist in the working dir)
+        files = os.listdir(os.getcwd())
+        files = [f for f in files if f.endswith(".py") or f.endswith(".txt")]
+        self.assertGreater(len(files), 0)
+        randoFile = files[0]
+
+        # pass that random file into the settings
+        o.cs["crossSectionControl"]["DA"].xsFileLocation = randoFile
+
+        with mockRunLogs.BufferLog() as mock:
+            # we should start with a clean slate
+            self.assertEqual("", mock.getStdout())
+            runLog.LOG.startLog("test_writeWelcomeHeaders")
+            runLog.LOG.setVerbosity(logging.INFO)
+
+            writeWelcomeHeaders(o, o.cs)
+
+            # assert our random file (and a lot of other stuff) is in the welcome
+            self.assertIn("Case Info", mock.getStdout())
+            self.assertIn("Input File Info", mock.getStdout())
+            self.assertIn("crossSectionControl-DA", mock.getStdout())
+            self.assertIn(randoFile, mock.getStdout())
+
 
 class TestReportInterface(unittest.TestCase):
     def test_printReports(self):
diff --git a/armi/cli/tests/test_runEntryPoint.py b/armi/cli/tests/test_runEntryPoint.py
index e043fd62..7a09dd16 100644
--- a/armi/cli/tests/test_runEntryPoint.py
+++ b/armi/cli/tests/test_runEntryPoint.py
@@ -40,11 +40,8 @@ class TestInitializationEntryPoints(unittest.TestCase):
         """Tests the initialization of all subclasses of `EntryPoint`."""
         entryPoints = getEntireFamilyTree(EntryPoint)
 
-        # Note that this is a hard coded number that should be incremented
-        # if a new ARMI framework entry point is added. This is a bit hacky,
-        # but will help demonstrate that entry point classes can be initialized
-        # and the `addOptions` and public API is tested.
-        self.assertEqual(len(entryPoints), 18)
+        # Comparing to a minimum number of entry points, in case more are added.
+        self.assertGreater(len(entryPoints), 16)
 
         for e in entryPoints:
             entryPoint = e()