diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index 6107ecf3c..80826766b 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -44,6 +44,7 @@ jobs: mpiexec -n 2 --use-hwthread-cpus coverage run --rcfile=pyproject.toml -m pytest --cov=armi --cov-config=pyproject.toml --cov-report=lcov --cov-append --ignore=venv armi/tests/test_mpiParameters.py || true mpiexec -n 2 --use-hwthread-cpus coverage run --rcfile=pyproject.toml -m pytest --cov=armi --cov-config=pyproject.toml --cov-report=lcov --cov-append --ignore=venv armi/tests/test_mpiDirectoryChangers.py || true coverage combine --rcfile=pyproject.toml --keep -a + coverage report --rcfile=pyproject.toml -i --skip-empty --skip-covered --sort=cover --fail-under=90 - name: Publish to coveralls.io uses: coverallsapp/github-action@v2 with: diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index a13b67343..8fd1dde2d 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -4,6 +4,7 @@ on: push: branches: - main + pull_request: concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -37,6 +38,7 @@ jobs: git submodule update make html - name: deploy + if: github.ref == 'refs/heads/main' uses: JamesIves/github-pages-deploy-action@v4.6.1 with: token: ${{ secrets.ACCESS_TOKEN }} @@ -44,3 +46,10 @@ jobs: branch: main folder: doc/_build/html target-folder: armi + - name: Archive Docs from PR + if: github.ref != 'refs/heads/main' + uses: actions/upload-artifact@v4 + with: + name: pr-docs + path: doc/_build/html + retention-days: 5 diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index cc0167388..2adea167e 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -14,6 +14,8 @@ permissions: jobs: stale: + # This workflow is not designed to make sense on forks + if: github.repository == 'terrapower/armi' runs-on: ubuntu-24.04 steps: - uses: actions/stale@v8 diff --git a/.github/workflows/unittests.yaml b/.github/workflows/unittests.yaml index a25a1d68c..a7271814e 100644 --- a/.github/workflows/unittests.yaml +++ b/.github/workflows/unittests.yaml @@ -21,14 +21,15 @@ jobs: runs-on: ubuntu-24.04 strategy: matrix: - python: [3.9, '3.10', '3.11', '3.12'] + python: [3.9, '3.10', '3.11', '3.12', '3.13'] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} + allow-prereleases: true - name: Update package index run: sudo apt-get update - name: Install mpi libs @@ -37,6 +38,6 @@ jobs: run: | pip install -e .[memprof,mpi,test] pytest -n 4 armi - mpiexec -n 2 --use-hwthread-cpus coverage run --rcfile=pyproject.toml -m pytest --cov=armi --cov-config=pyproject.toml --ignore=venv armi/tests/test_mpiFeatures.py || true - mpiexec -n 2 --use-hwthread-cpus coverage run --rcfile=pyproject.toml -m pytest --cov=armi --cov-config=pyproject.toml --ignore=venv armi/tests/test_mpiParameters.py || true - mpiexec -n 2 --use-hwthread-cpus coverage run --rcfile=pyproject.toml -m pytest --cov=armi --cov-config=pyproject.toml --ignore=venv armi/utils/tests/test_directoryChangersMpi.py || true + mpiexec -n 2 --use-hwthread-cpus pytest armi/tests/test_mpiFeatures.py + mpiexec -n 2 --use-hwthread-cpus pytest armi/tests/test_mpiParameters.py + mpiexec -n 2 --use-hwthread-cpus pytest armi/utils/tests/test_directoryChangersMpi.py diff --git a/README.rst b/README.rst index 636ba0ae7..e82f2da5e 100644 --- a/README.rst +++ b/README.rst @@ -385,8 +385,9 @@ like ARMI somewhat recently. ARMI has been written to support specific engineering/design tasks. As such, polish in the GUIs and output is somewhat lacking. -Most of our code is in the ``camelCase`` style, which is not the normal style for -Python. This started in 2009 and we have stuck with the convention. +The ARMI framework uses the ``camelCase`` style, which is not the standard style for Python. As this +is an issue of style, it is not considered worth the API-breaking cost to our downstream users to +change it. License diff --git a/armi/bookkeeping/db/tests/test_comparedb3.py b/armi/bookkeeping/db/tests/test_comparedb3.py index a19b94950..3d56ee022 100644 --- a/armi/bookkeeping/db/tests/test_comparedb3.py +++ b/armi/bookkeeping/db/tests/test_comparedb3.py @@ -181,7 +181,7 @@ def test_compareDatabaseSim(self): dbs[1]._fullPath, timestepCompare=[(0, 0), (0, 1)], ) - self.assertEqual(len(diffs.diffs), 477) + self.assertEqual(len(diffs.diffs), 480) # Cycle length is only diff (x3) self.assertEqual(diffs.nDiffs(), 3) @@ -228,15 +228,17 @@ def test_diffSpecialData(self): refData4 = f4.create_dataset("numberDensities", data=a2) refData4.attrs["shapes"] = "2" refData4.attrs["numDens"] = a2 + refData4.attrs["specialFormatting"] = True f5 = h5py.File("test_diffSpecialData5.hdf5", "w") srcData5 = f5.create_dataset("numberDensities", data=a2) srcData5.attrs["shapes"] = "2" srcData5.attrs["numDens"] = a2 + srcData5.attrs["specialFormatting"] = True - # there should an exception - with self.assertRaises(Exception) as e: + # there should a log message + with mockRunLogs.BufferLog() as mock: _diffSpecialData(refData4, srcData5, out, dr) - self.assertIn("Unable to unpack special data for paramName", e) + self.assertIn("Unable to unpack special data for", mock.getStdout()) # make an H5 datasets that will add a np.inf diff because keys don't match f6 = h5py.File("test_diffSpecialData6.hdf5", "w") diff --git a/armi/bookkeeping/historyTracker.py b/armi/bookkeeping/historyTracker.py index 995bbb5ef..884cb0c0d 100644 --- a/armi/bookkeeping/historyTracker.py +++ b/armi/bookkeeping/historyTracker.py @@ -339,7 +339,7 @@ def writeAssemHistory(self, a, fName=""): out.write("\n\n\nAssembly info\n") out.write("{0} {1}\n".format(a.getName(), a.getType())) for b in blocks: - out.write('"{}" {} {}\n'.format(b.getType(), b.p.xsType, b.p.buGroup)) + out.write('"{}" {} {}\n'.format(b.getType(), b.p.xsType, b.p.envGroup)) def preloadBlockHistoryVals(self, names, keys, timesteps): """ diff --git a/armi/cases/case.py b/armi/cases/case.py index cadcbaac0..faebff6dd 100644 --- a/armi/cases/case.py +++ b/armi/cases/case.py @@ -855,7 +855,7 @@ def _copyInputsHelper( ------- destFilePath (or origFile) : str """ - sourceName = os.path.basename(sourcePath) + sourceName = pathlib.Path(sourcePath).name destFilePath = os.path.join(destPath, sourceName) try: pathTools.copyOrWarn(fileDescription, sourcePath, destFilePath) @@ -880,9 +880,10 @@ def copyInterfaceInputs( This function should now be able to handle the updating of: - a single file (relative or absolute) - - a list of files (relative or absolute), and + - a list of files (relative or absolute) - a file entry that has a wildcard processing into multiple files. Glob is used to offer support for wildcards. + - a directory and its contents If the file paths are absolute, do nothing. The case will be able to find the file. @@ -950,7 +951,7 @@ def copyInterfaceInputs( path = pathlib.Path(f) if not WILDCARD and not RELATIVE: try: - if path.is_absolute() and path.exists() and path.is_file(): + if path.is_absolute() and path.exists(): # Path is absolute, no settings modification or filecopy needed newFiles.append(path) continue diff --git a/armi/nucDirectory/nuclideBases.py b/armi/nucDirectory/nuclideBases.py index b4e9bc487..60490c3b9 100644 --- a/armi/nucDirectory/nuclideBases.py +++ b/armi/nucDirectory/nuclideBases.py @@ -49,6 +49,8 @@ class which is used to organize and store metadata about each nuclide. The * ``byLabel`` (keyed by label, e.g., ``U235``) * ``byMcc2Id`` (keyed by MC\ :sup:`2`-2 ID, e.g., ``U-2355``) * ``byMcc3Id`` (keyed by MC\ :sup:`2`-3 ID, e.g., ``U235_7``) + * ``byMcc3IdEndfbVII0`` (keyed by MC\ :sup:`2`-3 ID, e.g., ``U235_7``) + * ``byMcc3IdEndfbVII1`` (keyed by MC\ :sup:`2`-3 ID, e.g., ``U235_7``) * ``byMcnpId`` (keyed by MCNP ID, e.g., ``92235``) * ``byAAAZZZSId`` (keyed by AAAZZZS, e.g., ``2350920``) @@ -78,7 +80,7 @@ class which is used to organize and store metadata about each nuclide. The Retrieve U-235 by the MC2-3 ID: ->>> nuclideBases.byMcc3Id['U235_7'] +>>> nuclideBases.byMcc3IdEndfVII0['U235_7'] , HL:2.22160758861e+16, Abund:7.204000e-03> Retrieve U-235 by the MCNP ID: @@ -120,7 +122,9 @@ class which is used to organize and store metadata about each nuclide. The byDBName = {} byLabel = {} byMcc2Id = {} -byMcc3Id = {} +byMcc3Id = {} # for backwards compatibility. Identical to byMcc3IdEndfbVII1 +byMcc3IdEndfbVII0 = {} +byMcc3IdEndfbVII1 = {} byMcnpId = {} byAAAZZZSId = {} @@ -168,9 +172,17 @@ def getMcc2Id(self): return NotImplementedError def getMcc3Id(self): + """Return the MC2-3 nuclide identification label based on the ENDF/B-VII.1 cross section library.""" + return NotImplementedError + + def getMcc3IdEndfbVII0(self): """Return the MC2-3 nuclide identification label based on the ENDF/B-VII.0 cross section library.""" return NotImplementedError + def getMcc3IdEndfbVII1(self): + """Return the MC2-3 nuclide identification label based on the ENDF/B-VII.1 cross section library.""" + return NotImplementedError + def getSerpentId(self): """Get the Serpent nuclide identification label.""" raise NotImplementedError @@ -243,8 +255,16 @@ def getMcc2Id(self): return self._base.getMcc2Id() def getMcc3Id(self): + """Return the MC2-3 nuclide based on the ENDF/B-VII.1 cross section library.""" + return self.getMcc3IdEndfbVII1() + + def getMcc3IdEndfbVII0(self): """Return the MC2-3 nuclide based on the ENDF/B-VII.0 cross section library.""" - return self._base.getMcc3Id() + return self._base.getMcc3IdEndfbVII0() + + def getMcc3IdEndfbVII1(self): + """Return the MC2-3 nuclide based on the ENDF/B-VII.1 cross section library.""" + return self._base.getMcc3IdEndfbVII1() def getNaturalIsotopics(self): """Return the natural isotopics root :py:class:`~elements.Element`.""" @@ -312,7 +332,8 @@ def __init__( name, label, mcc2id=None, - mcc3id=None, + mcc3idEndfbVII0=None, + mcc3idEndfbVII1=None, ): """ Create an instance of an INuclide. @@ -348,7 +369,8 @@ def __init__( self.label = label self.nuSF = 0.0 self.mcc2id = mcc2id or "" - self.mcc3id = mcc3id or "" + self.mcc3idEndfbVII0 = mcc3idEndfbVII0 or "" + self.mcc3idEndfbVII1 = mcc3idEndfbVII1 or "" addGlobalNuclide(self) self.element.append(self) @@ -593,18 +615,36 @@ def getMcc2Id(self): return self.mcc2id def getMcc3Id(self): + """Return the MC2-3 nuclide identification label based on the ENDF/B-VII.1 cross section library.""" + return self.getMcc3IdEndfbVII1() + + def getMcc3IdEndfbVII0(self): """Return the MC2-3 nuclide identification label based on the ENDF/B-VII.0 cross section library. - .. impl:: Isotopes and isomers can be queried by MC2-3 ID. + .. impl:: Isotopes and isomers can be queried by MC2-3 ENDF/B-VII.0 ID. :id: I_ARMI_ND_ISOTOPES3 :implements: R_ARMI_ND_ISOTOPES - This method returns the ``mcc3id`` attribute of a + This method returns the ``mcc3idEndfbVII0`` attribute of a :py:class:`NuclideBase ` instance. This attribute is initially populated by reading from the mcc-nuclides.yaml file in the ARMI resources folder. """ - return self.mcc3id + return self.mcc3idEndfbVII0 + + def getMcc3IdEndfbVII1(self): + """Return the MC2-3 nuclide identification label based on the ENDF/B-VII.1 cross section library. + + .. impl:: Isotopes and isomers can be queried by MC2-3 ENDF/B-VII.1 ID. + :id: I_ARMI_ND_ISOTOPES7 + :implements: R_ARMI_ND_ISOTOPES + + This method returns the ``mcc3idEndfbVII1`` attribute of a + :py:class:`NuclideBase ` + instance. This attribute is initially populated by reading from the + mcc-nuclides.yaml file in the ARMI resources folder. + """ + return self.mcc3idEndfbVII1 def getMcnpId(self): """ @@ -783,8 +823,16 @@ def getMcc2Id(self): return self.mcc2id def getMcc3Id(self): + """Return the MC2-3 nuclide identification label based on the ENDF/B-VII.1 cross section library.""" + return self.getMcc3IdEndfbVII1() + + def getMcc3IdEndfbVII0(self): """Return the MC2-3 nuclide identification label based on the ENDF/B-VII.0 cross section library.""" - return self.mcc3id + return self.mcc3idEndfbVII0 + + def getMcc3IdEndfbVII1(self): + """Return the MC2-3 nuclide identification label based on the ENDF/B-VII.1 cross section library.""" + return self.mcc3idEndfbVII1 def getSerpentId(self): """Gets the SERPENT ID for this natural nuclide. @@ -871,8 +919,16 @@ def getMcc2Id(self): return self.mcc2id def getMcc3Id(self): + """Return the MC2-3 nuclide identification label based on the ENDF/B-VII.1 cross section library.""" + return self.getMcc3IdEndfbVII1() + + def getMcc3IdEndfbVII0(self): """Return the MC2-3 nuclide identification label based on the ENDF/B-VII.0 cross section library.""" - return self.mcc3id + return self.mcc3idEndfbVII0 + + def getMcc3IdEndfbVII1(self): + """Return the MC2-3 nuclide identification label based on the ENDF/B-VII.1 cross section library.""" + return self.mcc3idEndfbVII1 class LumpNuclideBase(INuclide): @@ -937,8 +993,16 @@ def getMcc2Id(self): return self.mcc2id def getMcc3Id(self): + """Return the MC2-3 nuclide identification label based on the ENDF/B-VII.1 cross section library.""" + return self.getMcc3IdEndfbVII1() + + def getMcc3IdEndfbVII0(self): """Return the MC2-3 nuclide identification label based on the ENDF/B-VII.0 cross section library.""" - return self.mcc3id + return self.mcc3idEndfbVII0 + + def getMcc3IdEndfbVII1(self): + """Return the MC2-3 nuclide identification label based on the ENDF/B-VII.1 cross section library.""" + return self.mcc3idEndfbVII1 def initReachableActiveNuclidesThroughBurnChain(numberDensityDict, activeNuclides): @@ -1156,15 +1220,15 @@ def factory(): Reads NIST, MC**2 and burn chain data files to instantiate the :py:class:`INuclides `. Also clears and fills in the :py:data:`~armi.nucDirectory.nuclideBases.instances`, - :py:data:`byName`, :py:attr:`byLabel`, and - :py:data:`byMcc3Id` module attributes. This method is automatically run upon + :py:data:`byName`, :py:attr:`byLabel`, :py:data:`byMcc3IdEndfbVII0`, and + :py:data:`byMcc3IdEndfbVII1` module attributes. This method is automatically run upon loading the module, hence it is not usually necessary to re-run it unless there is a change to the data files, which should not happen during run time, or a *bad* :py:class`INuclide` is created. Notes ----- - This may cannot be run more than once. NuclideBase instances are used throughout the ARMI + This cannot be run more than once. NuclideBase instances are used throughout the ARMI ecosystem and are even class attributes in some cases. Re-instantiating them would orphan any existing ones and break everything. """ @@ -1269,13 +1333,18 @@ def readMCCNuclideData(): This function reads the mcc-nuclides.yaml file from the ARMI resources folder. This file contains the MC\ :sup:`2`-2 ID (from ENDF/B-V.2) and MC\ :sup:`2`-3 ID - (from ENDF/B-VII.0) for all nuclides in MC\ :sup:`2`. The ``mcc2id`` and - ``mcc3id`` attributes of each :py:class:`NuclideBase + (from ENDF/B-VII.0) for all nuclides in MC\ :sup:`2`. The ``mcc2id``, + ``mcc3idEndfVII0``, and ``mcc3idEndfVII1`` attributes of each :py:class:`NuclideBase ` instance are updated as - the data is read, and the global dictionaries ``byMcc2Id`` and - ``byMcc3Id`` are populated with the nuclide bases keyed by their - corresponding ID for each code. + the data is read, and the global dictionaries ``byMcc2Id`` + ``byMcc3IdEndfVII0`` and ``byMcc3IdEndfVII1`` are populated with the nuclide bases + keyed by their corresponding ID for each code. """ + global byMcc2Id + global byMcc3Id + global byMcc3IdEndfbVII0 + global byMcc3IdEndfbVII1 + with open(os.path.join(context.RES, "mcc-nuclides.yaml"), "r") as f: yaml = YAML(typ="rt") nuclides = yaml.load(f) @@ -1283,13 +1352,20 @@ def readMCCNuclideData(): for n in nuclides: nb = byName[n] mcc2id = nuclides[n]["ENDF/B-V.2"] - mcc3id = nuclides[n]["ENDF/B-VII.0"] + mcc3idEndfbVII0 = nuclides[n]["ENDF/B-VII.0"] + mcc3idEndfbVII1 = nuclides[n]["ENDF/B-VII.1"] if mcc2id is not None: nb.mcc2id = mcc2id byMcc2Id[nb.getMcc2Id()] = nb - if mcc3id is not None: - nb.mcc3id = mcc3id - byMcc3Id[nb.getMcc3Id()] = nb + if mcc3idEndfbVII0 is not None: + nb.mcc3idEndfbVII0 = mcc3idEndfbVII0 + byMcc3IdEndfbVII0[nb.getMcc3IdEndfbVII0()] = nb + if mcc3idEndfbVII1 is not None: + nb.mcc3idEndfbVII1 = mcc3idEndfbVII1 + byMcc3IdEndfbVII1[nb.getMcc3IdEndfbVII1()] = nb + + # Have the byMcc3Id dictionary be VII.1 IDs. + byMcc3Id = byMcc3IdEndfbVII1 def updateNuclideBasesForSpecialCases(): @@ -1379,6 +1455,8 @@ def destroyGlobalNuclides(): global byLabel global byMcc2Id global byMcc3Id + global byMcc3IdEndfbVII0 + global byMcc3IdEndfbVII1 global byMcnpId global byAAAZZZSId @@ -1388,5 +1466,7 @@ def destroyGlobalNuclides(): byLabel.clear() byMcc2Id.clear() byMcc3Id.clear() + byMcc3IdEndfbVII1.clear() + byMcc3IdEndfbVII0.clear() byMcnpId.clear() byAAAZZZSId.clear() diff --git a/armi/nucDirectory/tests/test_nuclideBases.py b/armi/nucDirectory/tests/test_nuclideBases.py index 01ca11f08..0bfcafbb8 100644 --- a/armi/nucDirectory/tests/test_nuclideBases.py +++ b/armi/nucDirectory/tests/test_nuclideBases.py @@ -382,9 +382,9 @@ def test_curieDefinitionWithRa226(self): self.assertAlmostEqual(activity, 0.9885593, places=6) def test_loadMcc2Data(self): - """Tests consistency with the `mcc-nuclides.yaml` input and the nuclides in the data model. + """Tests consistency with the `mcc-nuclides.yaml` input and the ENDF/B-V.2 nuclides in the data model. - .. test:: Test that MCC v2 IDs can be queried by nuclides. + .. test:: Test that MCC v2 ENDF/B-V.2 IDs can be queried by nuclides. :id: T_ARMI_ND_ISOTOPES3 :tests: R_ARMI_ND_ISOTOPES """ @@ -402,14 +402,14 @@ def test_loadMcc2Data(self): self.assertEqual(len(nuclideBases.byMcc2Id), len(expectedNuclides)) - def test_loadMcc3Data(self): - """Tests consistency with the `mcc-nuclides.yaml` input and the nuclides in the data model. + def test_loadMcc3EndfVII0Data(self): + """Tests consistency with the `mcc-nuclides.yaml` input and the ENDF/B-VII.0 nuclides in the data model. - .. test:: Test that MCC v3 IDs can be queried by nuclides. + .. test:: Test that MCC v3 ENDF/B-VII.0 IDs can be queried by nuclides. :id: T_ARMI_ND_ISOTOPES4 :tests: R_ARMI_ND_ISOTOPES - .. test:: Test the MCC nuclide data that was read from file instead of code. + .. test:: Test the MCC ENDF/B-VII.0 nuclide data that was read from file instead of code. :id: T_ARMI_ND_DATA1 :tests: R_ARMI_ND_DATA """ @@ -420,13 +420,41 @@ def test_loadMcc3Data(self): [nuc for nuc in data.keys() if data[nuc]["ENDF/B-VII.0"] is not None] ) - for nuc, nb in nuclideBases.byMcc3Id.items(): + for nuc, nb in nuclideBases.byMcc3IdEndfbVII0.items(): self.assertIn(nb.name, expectedNuclides) - self.assertEqual(nb.getMcc3Id(), nb.mcc3id) + self.assertEqual(nb.getMcc3IdEndfbVII0(), nb.mcc3idEndfbVII0) + self.assertEqual(nb.getMcc3IdEndfbVII0(), nuc) + + # Subtract 1 nuclide due to DUMP2. + self.assertEqual(len(nuclideBases.byMcc3IdEndfbVII0), len(expectedNuclides) - 1) + + def test_loadMcc3EndfVII1Data(self): + """Tests consistency with the `mcc-nuclides.yaml` input and the ENDF/B-VII.1 nuclides in the data model. + + .. test:: Test that MCC v3 ENDF/B-VII.1 IDs can be queried by nuclides. + :id: T_ARMI_ND_ISOTOPES6 + :tests: R_ARMI_ND_ISOTOPES + + .. test:: Test the MCC ENDF/B-VII.1 nuclide data that was read from file instead of code. + :id: T_ARMI_ND_DATA2 + :tests: R_ARMI_ND_DATA + """ + with open(os.path.join(RES, "mcc-nuclides.yaml")) as f: + yaml = YAML(typ="rt") + data = yaml.load(f) + expectedNuclides = set( + [nuc for nuc in data.keys() if data[nuc]["ENDF/B-VII.1"] is not None] + ) + + for nuc, nb in nuclideBases.byMcc3IdEndfbVII1.items(): + self.assertIn(nb.name, expectedNuclides) + self.assertEqual(nb.getMcc3IdEndfbVII1(), nb.mcc3idEndfbVII1) + self.assertEqual(nb.getMcc3IdEndfbVII1(), nuc) + self.assertEqual(nb.getMcc3Id(), nb.mcc3idEndfbVII1) self.assertEqual(nb.getMcc3Id(), nuc) # Subtract 1 nuclide due to DUMP2. - self.assertEqual(len(nuclideBases.byMcc3Id), len(expectedNuclides) - 1) + self.assertEqual(len(nuclideBases.byMcc3IdEndfbVII1), len(expectedNuclides) - 1) class TestAAAZZZSId(unittest.TestCase): diff --git a/armi/nuclearDataIO/tests/library-file-generation/mc2v2-dlayxs.inp b/armi/nuclearDataIO/tests/library-file-generation/mc2v2-dlayxs.inp index d163aa19a..78636969a 100644 --- a/armi/nuclearDataIO/tests/library-file-generation/mc2v2-dlayxs.inp +++ b/armi/nuclearDataIO/tests/library-file-generation/mc2v2-dlayxs.inp @@ -272,7 +272,7 @@ DATASET=A.MCC2 22 CM2455 7 202.55000 22 CM2465 7 202.55000 22 CM2475 7 202.55000 -01 ARMI generated \\path\to\mc2\2.0\mc2.exe case for caseTitle mrtTWRP600v6rev3OS, block +01 ARMI generated \\path\to\mc2\2.0\mc2.exe case for caseTitle mrtTWRP600v6rev3OS, block DATASET=A.DLAY 01 CREATE VERSION VI DLAYXS FILE FROM FUELI 03 1 0 0 0 0 diff --git a/armi/physics/neutronics/__init__.py b/armi/physics/neutronics/__init__.py index a14f9ab8e..472ace732 100644 --- a/armi/physics/neutronics/__init__.py +++ b/armi/physics/neutronics/__init__.py @@ -64,6 +64,11 @@ def defineParameters(): return neutronicsParameters.getNeutronicsParameterDefinitions() + @staticmethod + @plugins.HOOKIMPL + def defineParameterRenames(): + return {"buGroup": "envGroup", "buGroupNum": "envGroupNum"} + @staticmethod @plugins.HOOKIMPL def defineEntryPoints(): diff --git a/armi/physics/neutronics/crossSectionGroupManager.py b/armi/physics/neutronics/crossSectionGroupManager.py index 9e8972c6f..36022c576 100644 --- a/armi/physics/neutronics/crossSectionGroupManager.py +++ b/armi/physics/neutronics/crossSectionGroupManager.py @@ -34,6 +34,7 @@ -------- csm = CrossSectionGroupManager() csm._setBuGroupBounds(cs['buGroups']) + csm._setTempGroupBounds(cs['tempGroups']) # or empty list csm._addXsGroupsFromBlocks(blockList) csm.createRepresentativeBlocks() representativeBlockList = csm.representativeBlocks.values() @@ -65,7 +66,7 @@ from armi.reactor.components import basicShapes from armi.reactor.converters.blockConverters import stripComponents from armi.reactor.flags import Flags -from armi.utils.units import TRACE_NUMBER_DENSITY +from armi.utils.units import TRACE_NUMBER_DENSITY, C_TO_K ORDER = interfaces.STACK_ORDER.BEFORE + interfaces.STACK_ORDER.CROSS_SECTIONS @@ -473,6 +474,12 @@ def _checkBlockSimilarity(self): return True +def getBlockNuclideTemperature(block, nuclide): + """Return the average temperature for 1 nuclide.""" + tempIntegratedVolume, volume = getBlockNuclideTemperatureAvgTerms(block, [nuclide]) + return tempIntegratedVolume / volume if volume > 0 else 0.0 + + def getBlockNuclideTemperatureAvgTerms(block, allNucNames): """ Compute terms (numerator, denominator) of average for this block. @@ -496,12 +503,13 @@ def getNumberDensitiesWithTrace(component, allNucNames): components, volFracs = zip(*block.getVolumeFractions()) # D = CxN matrix of number densities ndens = np.array([getNumberDensitiesWithTrace(c, allNucNames) for c in components]) - temperatures = np.array( - [c.temperatureInC for c in components] - ) # C-length temperature array - nvBlock = ( - ndens.T * np.array(volFracs) * vol - ) # multiply each component's values by volume frac, now NxC + + # C-length temperature array + temperatures = np.array([c.temperatureInC for c in components]) + + # multiply each component's values by volume frac, now NxC + nvBlock = ndens.T * np.array(volFracs) * vol + nvt = sum((nvBlock * temperatures).T) # N-length array summing over components. nv = sum(nvBlock.T) # N-length array return nvt, nv @@ -562,6 +570,7 @@ def _makeRepresentativeBlock(self): ) for nuc, aDensity in zip(allNucsNames, densities): c.setNumberDensity(nuc, aDensity) + self.calcAvgNuclideTemperatures() return repBlock @staticmethod @@ -914,11 +923,16 @@ class CrossSectionGroupManager(interfaces.Interface): def __init__(self, r, cs): interfaces.Interface.__init__(self, r, cs) - self._upperBuGroupBounds = None + self._buGroupBounds = [] + self._tempGroupBounds = [] self.representativeBlocks = collections.OrderedDict() self.avgNucTemperatures = {} - self._buGroupUpdatesEnabled = True + + # this turns off updates for when core changes are made, but dont want to re-evaluate XS + # for example if lattice physics was only once per cycle we might not want to re-evaluate groups + self._envGroupUpdatesEnabled = True self._setBuGroupBounds(self.cs["buGroups"]) + self._setTempGroupBounds(self.cs["tempGroups"]) self._unrepresentedXSIDs = [] def interactBOL(self): @@ -1044,13 +1058,13 @@ def clearRepresentativeBlocks(self): self.representativeBlocks = collections.OrderedDict() self.avgNucTemperatures = {} - def _setBuGroupBounds(self, upperBuGroupBounds): + def _setBuGroupBounds(self, buGroupBounds): """ Set the burnup group structure. Parameters ---------- - upperBuGroupBounds : list + buGroupBounds : list List of upper burnup values in percent. Raises @@ -1058,9 +1072,9 @@ def _setBuGroupBounds(self, upperBuGroupBounds): ValueError If the provided burnup groups are invalid """ - self._upperBuGroupBounds = upperBuGroupBounds lastBu = 0.0 - for upperBu in self._upperBuGroupBounds: + # validate structure + for upperBu in buGroupBounds: if upperBu <= 0 or upperBu > 100: raise ValueError( "Burnup group upper bound {0} is invalid".format(upperBu) @@ -1069,46 +1083,85 @@ def _setBuGroupBounds(self, upperBuGroupBounds): raise ValueError("Burnup groups must be ascending") lastBu = upperBu - def _updateBurnupGroups(self, blockList): + self._buGroupBounds = buGroupBounds + [float("inf")] + + def _setTempGroupBounds(self, tempGroupBounds): + """Set the temperature group structure.""" + lastTemp = -C_TO_K + # validate structure + for upperTemp in tempGroupBounds: + if upperTemp < -C_TO_K: + raise ValueError( + "Temperature boundary is below absolute zero {0}.format(upperTemp)" + ) + if upperTemp < lastTemp: + raise ValueError("Temp groups must be ascending") + lastTemp = upperTemp + self._tempGroupBounds = tempGroupBounds + [float("inf")] + + def _updateEnvironmentGroups(self, blockList): """ - Update the burnup group of each block based on its burnup. + Update the burnup group of each block based on its burnup and temperature . - If only one burnup group exists, then this is skipped so as to accomodate the possibility + If only one burnup group exists, then this is skipped so as to accommodate the possibility of 2-character xsGroup values (useful for detailed V&V models w/o depletion). See Also -------- armi.reactor.blocks.Block.getMicroSuffix """ - if self._buGroupUpdatesEnabled and len(self._upperBuGroupBounds) > 1: - runLog.debug("Updating burnup groups of {0} blocks".format(len(blockList))) - for block in blockList: - bu = block.p.percentBu - for buGroupIndex, upperBu in enumerate(self._upperBuGroupBounds): - if bu <= upperBu: - block.p.buGroupNum = buGroupIndex - break - else: - raise ValueError("no bu group found for bu={0}".format(bu)) - else: + if not self._envGroupUpdatesEnabled: runLog.debug( "Skipping burnup group update of {0} blocks because it is disabled" "".format(len(blockList)) ) + return + + numBuGroups = len(self._buGroupBounds) + if numBuGroups == 1 and len(self._tempGroupBounds) == 1: + # dont set block.p.envGroupNum since all 1 group and we want to support 2 char xsGroup + return + runLog.debug("Updating env groups of {0} blocks".format(len(blockList))) + for block in blockList: + bu = block.p.percentBu + for buIndex, upperBu in enumerate(self._buGroupBounds): + if bu <= upperBu: + buGroupVal = buIndex + tempGroupVal = 0 + isotope = self._initializeXsID(block.getMicroSuffix()).xsTempIsotope + if isotope and len(self._tempGroupBounds) > 1: + # if statement saves this somewhat expensive calc if we are not doing temp groups + tempC = getBlockNuclideTemperature(block, isotope) + for tempIndex, upperTemp in enumerate(self._tempGroupBounds): + if tempC <= upperTemp: + tempGroupVal = tempIndex + break + # this ordering groups like-temperatures together in group number + block.p.envGroupNum = tempGroupVal * numBuGroups + buGroupVal + break def _addXsGroupsFromBlocks(self, blockCollectionsByXsGroup, blockList): """ - Build all the cross section groups based on their XS type and BU group. + Build all the cross section groups based on their XS type and Env group. - Also ensures that their BU group is up to date with their burnup. + Also ensures that their Env group is up to date with their environment. """ - self._updateBurnupGroups(blockList) + self._updateEnvironmentGroups(blockList) for b in blockList: xsID = b.getMicroSuffix() xsSettings = self._initializeXsID(xsID) + if ( + self.cs["tempGroups"] + and xsSettings.blockRepresentation == MEDIAN_BLOCK_COLLECTION + ): + runLog.warning( + "Median block currently only consider median burnup block, and " + "not median temperature block in group" + ) blockCollectionType = blockCollectionFactory( xsSettings, self.r.blueprints.allNuclidesInProblem ) + group = blockCollectionsByXsGroup.get(xsID, blockCollectionType) group.append(b) blockCollectionsByXsGroup[xsID] = group @@ -1227,9 +1280,8 @@ def createRepresentativeBlocks(self): self.avgNucTemperatures[xsID] = collection.avgNucTemperatures else: runLog.debug( - "No candidate blocks for {} will apply different burnup group".format( - xsID - ) + "No candidate blocks in group for {} (with a valid representative block flag). " + "Will apply different environment group".format(xsID) ) self._unrepresentedXSIDs.append(xsID) @@ -1409,22 +1461,23 @@ def getNextAvailableXsTypes(self, howMany=1, excludedXSTypes=None): ) return availableXsTypes[:howMany] - def _getUnrepresentedBlocks(self, blockCollectionsByXsGroup): + def _getMissingBlueprintBlocks(self, blockCollectionsByXsGroup): """ - Gets all blocks with suffixes not yet represented (for blocks in assemblies in the blueprints but not the core). + Gets all blocks with suffixes not yet represented. + (for blocks in assemblies in the blueprints but not in the core). Notes ----- Certain cases (ZPPR validation cases) need to run cross sections for assemblies not in the core to get by region cross sections and flux factors. """ - unrepresentedBlocks = [] + missingBlueprintBlocks = [] for a in self.r.blueprints.assemblies.values(): for b in a: if b.getMicroSuffix() not in blockCollectionsByXsGroup: b2 = copy.deepcopy(b) - unrepresentedBlocks.append(b2) - return unrepresentedBlocks + missingBlueprintBlocks.append(b2) + return missingBlueprintBlocks def makeCrossSectionGroups(self): """Make cross section groups for all blocks in reactor and unrepresented blocks from blueprints.""" @@ -1432,41 +1485,56 @@ def makeCrossSectionGroups(self): bCollectXSGroup = self._addXsGroupsFromBlocks( bCollectXSGroup, self.r.core.getBlocks() ) + + # add blocks that are defined in blueprints, but not in core bCollectXSGroup = self._addXsGroupsFromBlocks( - bCollectXSGroup, self._getUnrepresentedBlocks(bCollectXSGroup) + bCollectXSGroup, self._getMissingBlueprintBlocks(bCollectXSGroup) ) blockCollectionsByXsGroup = collections.OrderedDict( sorted(bCollectXSGroup.items()) ) return blockCollectionsByXsGroup + def _getAlternateEnvGroup(self, missingXsType): + """Get a substitute block to use since there are no blocks with flags for xs gen.""" + for otherXsID in self.representativeBlocks: + repType, repEnvGroup = otherXsID + if repType == missingXsType: + return repEnvGroup + def _modifyUnrepresentedXSIDs(self, blockCollectionsByXsGroup): """ Adjust the xsID of blocks in the groups that are not represented. Try to just adjust the burnup group up to something that is represented - (can happen to structure in AA when only AB, AC, AD still remain). + (can happen to structure in AA when only AB, AC, AD still remain, + but if some fresh AA happened to be added it might be needed). """ + # No blocks in in this ID had a valid representative block flag (such as `fuel` for default), + # so nothing valid to run lattice physics on... for xsID in self._unrepresentedXSIDs: - missingXsType, _missingBuGroup = xsID - for otherXsID in self.representativeBlocks: # order gets closest BU - repType, repBuGroup = otherXsID - if repType == missingXsType: - nonRepBlocks = blockCollectionsByXsGroup.get(xsID) - if nonRepBlocks: - runLog.extra( - "Changing XSID of {0} blocks from {1} to {2}" - "".format(len(nonRepBlocks), xsID, otherXsID) + missingXsType, _missingEnvGroup = xsID + nonRepBlocks = blockCollectionsByXsGroup.get(xsID) + if nonRepBlocks: + newEnvGroup = self._getAlternateEnvGroup(missingXsType) + if newEnvGroup: + # there were no blocks flagged to xs gen even though there were some not suitable for + # generation in the group so can't make XS and use different. + runLog.warning( + "Changing XSID of {0} blocks from {1} to {2}" + "".format( + len(nonRepBlocks), xsID, missingXsType[0] + newEnvGroup ) - for b in nonRepBlocks: - b.p.buGroup = repBuGroup - break - else: - runLog.warning( - "No representative blocks with XS type {0} exist in the core. " - "These XS cannot be generated and must exist in the working " - "directory or the run will fail.".format(xsID) - ) + ) + for b in nonRepBlocks: + b.p.envGroup = newEnvGroup + else: + runLog.warning( + "No representative blocks with XS type {0} exist in the core. " + "There were also no similar blocks to use. " + "These XS cannot be generated and must exist in the working " + "directory or the run will fail.".format(xsID) + ) def _summarizeGroups(self, blockCollectionsByXsGroup): """Summarize current contents of the XS groups.""" @@ -1481,9 +1549,20 @@ def _summarizeGroups(self, blockCollectionsByXsGroup): xsIDGroup = self._getXsIDGroup(xsID) if xsIDGroup == self._REPR_GROUP: reprBlock = self.representativeBlocks.get(xsID) + xsSettings = self._initializeXsID(reprBlock.getMicroSuffix()) + temp = self.avgNucTemperatures[xsID].get( + xsSettings.xsTempIsotope, "N/A" + ) runLog.extra( - "XS ID {} contains {:4d} blocks, represented by: {:65s}".format( - xsID, len(blocks), reprBlock + ( + "XS ID {} contains {:4d} blocks, with avg burnup {} " + "and avg fuel temp {}, represented by: {:65s}" + ).format( + xsID, + len(blocks), + reprBlock.p.percentBu, + temp, + reprBlock, ) ) elif xsIDGroup == self._NON_REPR_GROUP: @@ -1511,31 +1590,31 @@ def _getXsIDGroup(self, xsID): return self._NON_REPR_GROUP return None - def disableBuGroupUpdates(self): + def disableEnvGroupUpdates(self): """ - Turn off updating bu groups based on burnup. + Turn off updating Env groups based on environment. Useful during reactivity coefficient calculations to be consistent with ref. run. See Also -------- - enableBuGroupUpdates + enableEnvGroupUpdates """ - runLog.extra("Burnup group updating disabled") - wasEnabled = self._buGroupUpdatesEnabled - self._buGroupUpdatesEnabled = False + runLog.extra("Environment xs group updating disabled") + wasEnabled = self._envGroupUpdatesEnabled + self._envGroupUpdatesEnabled = False return wasEnabled - def enableBuGroupUpdates(self): + def enableEnvGroupUpdates(self): """ - Turn on updating bu groups based on burnup. + Turn on updating Env groups based on environment. See Also -------- - disableBuGroupUpdates + disableEnvGroupUpdates """ - runLog.extra("Burnup group updating enabled") - self._buGroupUpdatesEnabled = True + runLog.extra("Environment xs group updating enabled") + self._envGroupUpdatesEnabled = True def getNucTemperature(self, xsID, nucName): """ diff --git a/armi/physics/neutronics/crossSectionSettings.py b/armi/physics/neutronics/crossSectionSettings.py index 14a4bc537..a683f7d7f 100644 --- a/armi/physics/neutronics/crossSectionSettings.py +++ b/armi/physics/neutronics/crossSectionSettings.py @@ -60,6 +60,7 @@ CONF_MIN_DRIVER_DENSITY = "minDriverDensity" CONF_DUCT_HETEROGENEOUS = "ductHeterogeneous" CONF_TRACE_ISOTOPE_THRESHOLD = "traceIsotopeThreshold" +CONF_XS_TEMP_ISOTOPE = "xsTempIsotope" class XSGeometryTypes(Enum): @@ -121,6 +122,7 @@ def getStr(cls, typeSpec: Enum): CONF_XS_EXECUTE_EXCLUSIVE, CONF_XS_PRIORITY, CONF_XS_MAX_ATOM_NUMBER, + CONF_XS_TEMP_ISOTOPE, }, XSGeometryTypes.getStr(XSGeometryTypes.ONE_DIMENSIONAL_SLAB): { CONF_XSID, @@ -134,6 +136,7 @@ def getStr(cls, typeSpec: Enum): CONF_XS_PRIORITY, CONF_XS_MAX_ATOM_NUMBER, CONF_MIN_DRIVER_DENSITY, + CONF_XS_TEMP_ISOTOPE, }, XSGeometryTypes.getStr(XSGeometryTypes.ONE_DIMENSIONAL_CYLINDER): { CONF_XSID, @@ -155,6 +158,7 @@ def getStr(cls, typeSpec: Enum): CONF_MIN_DRIVER_DENSITY, CONF_DUCT_HETEROGENEOUS, CONF_TRACE_ISOTOPE_THRESHOLD, + CONF_XS_TEMP_ISOTOPE, }, XSGeometryTypes.getStr(XSGeometryTypes.TWO_DIMENSIONAL_HEX): { CONF_XSID, @@ -171,6 +175,7 @@ def getStr(cls, typeSpec: Enum): CONF_XS_PRIORITY, CONF_XS_MAX_ATOM_NUMBER, CONF_MIN_DRIVER_DENSITY, + CONF_XS_TEMP_ISOTOPE, }, } @@ -203,6 +208,7 @@ def getStr(cls, typeSpec: Enum): vol.Optional(CONF_COMPONENT_AVERAGING): bool, vol.Optional(CONF_DUCT_HETEROGENEOUS): bool, vol.Optional(CONF_TRACE_ISOTOPE_THRESHOLD): vol.Coerce(float), + vol.Optional(CONF_XS_TEMP_ISOTOPE): str, } ) @@ -259,19 +265,21 @@ def __getitem__(self, xsID): if xsID in self: return dict.__getitem__(self, xsID) + # exact key not present so give lowest env group key, eg AA or BA as the source for + # settings since users do not typically provide all combinations of second chars explicitly xsType = xsID[0] - buGroup = xsID[1] + envGroup = xsID[1] existingXsOpts = [ xsOpt for xsOpt in self.values() - if xsOpt.xsType == xsType and xsOpt.buGroup < buGroup + if xsOpt.xsType == xsType and xsOpt.envGroup < envGroup ] if not any(existingXsOpts): return self._getDefault(xsID) else: - return sorted(existingXsOpts, key=lambda xsOpt: xsOpt.buGroup)[0] + return sorted(existingXsOpts, key=lambda xsOpt: xsOpt.envGroup)[0] def setDefaults(self, blockRepresentation, validBlockTypes): """ @@ -469,6 +477,10 @@ class XSModelingOptions: model. The setting takes a float value that represents the number density cutoff for isotopes to be considered "trace". If no value is provided, the default is 0.0. + xsTempIsotope: str + The isotope whose temperature is interrogated when placing a block in a temperature cross section group. + See `tempGroups`. "U238" is default since it tends to be dominant doppler isotope in most reactors. + Notes ----- Not all default attributes may be useful for your specific application and you may @@ -503,6 +515,7 @@ def __init__( minDriverDensity=0.0, ductHeterogeneous=False, traceIsotopeThreshold=0.0, + xsTempIsotope="U238", ): self.xsID = xsID self.geometry = geometry @@ -531,6 +544,7 @@ def __init__( # these are related to execution self.xsExecuteExclusive = xsExecuteExclusive self.xsPriority = xsPriority + self.xsTempIsotope = xsTempIsotope def __repr__(self): if self.xsIsPregenerated: @@ -551,7 +565,7 @@ def xsType(self): return self.xsID[0] @property - def buGroup(self): + def envGroup(self): """Return the single-char burnup group indicator.""" return self.xsID[1] diff --git a/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py b/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py index 68da4be98..c52ed664a 100644 --- a/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py +++ b/armi/physics/neutronics/latticePhysics/latticePhysicsWriter.py @@ -76,7 +76,7 @@ class LatticePhysicsWriter(interfaces.InputWriter): DEPLETABLE = "Depletable" + 4 * _SPACE UNDEPLETABLE = "Non-Depletable" REPRESENTED = "Represented" + 2 * _SPACE - UNREPRESENTED = "Unrepresented" + INF_DILUTE = "Inf Dilute" def __init__( self, @@ -307,7 +307,7 @@ def _getAllNuclidesByCategory(self, component=None): if nucName in objNuclides: nucCategory += self.REPRESENTED + self._SEPARATOR else: - nucCategory += self.UNREPRESENTED + self._SEPARATOR + nucCategory += self.INF_DILUTE + self._SEPARATOR if nucName in depletableNuclides: nucCategory += self.DEPLETABLE diff --git a/armi/physics/neutronics/parameters.py b/armi/physics/neutronics/parameters.py index 78268b8ba..fc84a5df7 100644 --- a/armi/physics/neutronics/parameters.py +++ b/armi/physics/neutronics/parameters.py @@ -655,8 +655,6 @@ def _getNeutronicsBlockParams(): categories=[parameters.Category.neutronics], ) - pb.defParam("powerDecay", units=units.WATTS, description="Total decay power") - pb.defParam( "powerGamma", units=units.WATTS, diff --git a/armi/physics/neutronics/tests/test_crossSectionManager.py b/armi/physics/neutronics/tests/test_crossSectionManager.py index c78c4bdcc..ec86ab3ea 100644 --- a/armi/physics/neutronics/tests/test_crossSectionManager.py +++ b/armi/physics/neutronics/tests/test_crossSectionManager.py @@ -95,6 +95,22 @@ def test_createRepresentativeBlock(self): avgB = self.bc.createRepresentativeBlock() self.assertAlmostEqual(avgB.p.percentBu, 50.0) + def test_getBlockNuclideTemperature(self): + # doesn't have to be in median block tests, but this is a simpler test + nuc = "U235" + testBlock = self.blockList[0] + amt, amtWeightedTemp = 0, 0 + for c in testBlock: + dens = c.getNumberDensity(nuc) + if dens > 0: + thisAmt = dens * c.getVolume() + amt += thisAmt + amtWeightedTemp += thisAmt * c.temperatureInC + avgTemp = amtWeightedTemp / amt + self.assertAlmostEqual( + avgTemp, crossSectionGroupManager.getBlockNuclideTemperature(testBlock, nuc) + ) + class TestBlockCollectionAverage(unittest.TestCase): @classmethod @@ -403,7 +419,7 @@ def test_ComponentAverageRepBlock(self): assert "AC" in xsgm.representativeBlocks, ( "Assemblies not in the core should still have XS groups" - "see getUnrepresentedBlocks()" + "see _getMissingBlueprintBlocks()" ) @@ -421,7 +437,8 @@ def setUp(self): sodiumDensity = {"NA23": 0.022166571826233578} steelDensity = { "C": 0.0007685664978992269, - "V": 0.0002718224847461385, + "V50": 6.795562118653462e-07, + "V51": 0.0002711429285342731, "SI28": 0.0003789374369638149, "SI29": 1.924063709833714e-05, "SI30": 1.268328992580968e-05, @@ -763,29 +780,36 @@ def setUp(self): self.csm._setBuGroupBounds([3, 10, 30, 100]) self.csm.interactBOL() - def test_enableBuGroupUpdates(self): - self.csm._buGroupUpdatesEnabled = False - self.csm.enableBuGroupUpdates() - self.assertTrue(self.csm.enableBuGroupUpdates) - - def test_disableBuGroupUpdates(self): - self.csm._buGroupUpdatesEnabled = False - res = self.csm.disableBuGroupUpdates() - self.assertFalse(res) + def test_enableEnvGroupUpdates(self): + self.csm._envGroupUpdatesEnabled = False + self.csm.enableEnvGroupUpdates() + self.assertTrue(self.csm._envGroupUpdatesEnabled) + # test flipping again keeps true + self.csm.enableEnvGroupUpdates() + self.assertTrue(self.csm._envGroupUpdatesEnabled) + + def test_disableEnvGroupUpdates(self): + self.csm._envGroupUpdatesEnabled = True + wasEnabled = self.csm.disableEnvGroupUpdates() + self.assertTrue(wasEnabled) + self.assertFalse(self.csm._envGroupUpdatesEnabled) + wasEnabled = self.csm.disableEnvGroupUpdates() + self.assertFalse(wasEnabled) + self.assertFalse(self.csm._envGroupUpdatesEnabled) def test_updateBurnupGroups(self): self.blockList[1].p.percentBu = 3.1 self.blockList[2].p.percentBu = 10.0 - self.csm._updateBurnupGroups(self.blockList) + self.csm._updateEnvironmentGroups(self.blockList) - self.assertEqual(self.blockList[0].p.buGroup, "A") - self.assertEqual(self.blockList[1].p.buGroup, "B") - self.assertEqual(self.blockList[2].p.buGroup, "B") - self.assertEqual(self.blockList[-1].p.buGroup, "D") + self.assertEqual(self.blockList[0].p.envGroup, "A") + self.assertEqual(self.blockList[1].p.envGroup, "B") + self.assertEqual(self.blockList[2].p.envGroup, "B") + self.assertEqual(self.blockList[-1].p.envGroup, "D") def test_setBuGroupBounds(self): - self.assertAlmostEqual(self.csm._upperBuGroupBounds[2], 30.0) + self.assertAlmostEqual(self.csm._buGroupBounds[2], 30.0) with self.assertRaises(ValueError): self.csm._setBuGroupBounds([3, 10, 300]) @@ -796,6 +820,14 @@ def test_setBuGroupBounds(self): with self.assertRaises(ValueError): self.csm._setBuGroupBounds([1, 5, 3]) + def test_setTempGroupBounds(self): + # negative temps in C are allowed + self.csm._setTempGroupBounds([-5, 3, 10, 300]) + self.assertAlmostEqual(self.csm._tempGroupBounds[2], 10.0) + + with self.assertRaises(ValueError): + self.csm._setTempGroupBounds([1, 5, 3]) + def test_addXsGroupsFromBlocks(self): blockCollectionsByXsGroup = {} blockCollectionsByXsGroup = self.csm._addXsGroupsFromBlocks( @@ -810,7 +842,7 @@ def test_calcWeightedBurnup(self): self.blockList[3].p.percentBu = 1.5 for b in self.blockList[4:]: b.p.percentBu = 0.0 - self.csm._updateBurnupGroups(self.blockList) + self.csm._updateEnvironmentGroups(self.blockList) blockCollectionsByXsGroup = {} blockCollectionsByXsGroup = self.csm._addXsGroupsFromBlocks( blockCollectionsByXsGroup, self.blockList @@ -1077,6 +1109,85 @@ def test_copyPregeneratedFiles(self): self.assertTrue(os.path.exists("rzmflxYA")) +class TestCrossSectionGroupManagerWithTempGrouping(unittest.TestCase): + def setUp(self): + cs = settings.Settings() + cs["tempGroups"] = [300, 400, 500] + self.blockList = makeBlocks(11) + buAndTemps = ( + (1, 340), + (2, 150), + (6, 410), + (10.5, 290), + (2.5, 360), + (4, 460), + (15, 370), + (16, 340), + (15, 700), + (14, 720), + ) + for b, env in zip(self.blockList, buAndTemps): + bu, temp = env + comps = b.getComponents(Flags.FUEL) + assert len(comps) == 1 + c = next(iter(comps)) + c.setTemperature(temp) + b.p.percentBu = bu + core = self.blockList[0].core + + def getBlocks(includeAll=True): + return self.blockList + + # this sets XSGM to only analyze the blocks in the block list. + core.getBlocks = getBlocks + + self.csm = CrossSectionGroupManager(self.blockList[0].core.r, cs) + self.csm._setBuGroupBounds([3, 10, 30, 100]) + self.csm.interactBOL() + + def test_updateEnvironmentGroups(self): + self.csm.createRepresentativeBlocks() + BL = self.blockList + loners = [BL[1], BL[3]] + + self.assertNotEqual(loners[0].getMicroSuffix(), loners[1].getMicroSuffix()) + sameGroups = [(BL[0], BL[4]), (BL[2], BL[5]), (BL[6], BL[7]), (BL[8], BL[9])] + + # check that likes have like and different are different + for group in sameGroups: + b1, b2 = group + xsSuffix = b1.getMicroSuffix() + self.assertEqual(xsSuffix, b2.getMicroSuffix()) + for group in sameGroups: + newb1, newb2 = group + if b1 is newb1: + continue + self.assertNotEqual(xsSuffix, newb1.getMicroSuffix()) + self.assertNotEqual(xsSuffix, newb2.getMicroSuffix()) + for lone in loners: + self.assertNotEqual(xsSuffix, lone.getMicroSuffix()) + self.assertNotEqual(loners[0].getMicroSuffix(), loners[1].getMicroSuffix()) + + # calculated based on the average of buAndTemps + expectedIDs = ["AF", "AA", "AL", "AC", "AH", "AR"] + expectedTemps = [ + (340 + 360) / 2, + 150, + (410 + 460) / 2, + 290, + (370 + 340) / 2, + (700 + 720) / 2, + ] + expectedBurnups = (1.75, 2, 5, 10.5, 15.5, 14.5) + for xsID, expectedTemp, expectedBurnup in zip( + expectedIDs, expectedTemps, expectedBurnups + ): + b = self.csm.representativeBlocks[xsID] + thisTemp = self.csm.avgNucTemperatures[xsID]["U238"] + self.assertAlmostEqual(thisTemp, expectedTemp) + self.assertAlmostEqual(b.p.percentBu, expectedBurnup) + + class TestXSNumberConverters(unittest.TestCase): def test_conversion(self): label = crossSectionGroupManager.getXSTypeLabelFromNumber(65) diff --git a/armi/physics/neutronics/tests/test_crossSectionSettings.py b/armi/physics/neutronics/tests/test_crossSectionSettings.py index 2106ba9b7..17499e806 100644 --- a/armi/physics/neutronics/tests/test_crossSectionSettings.py +++ b/armi/physics/neutronics/tests/test_crossSectionSettings.py @@ -121,7 +121,7 @@ def test_homogeneousXsDefaultSettingAssignment(self): self.assertEqual(xsModel["YA"].ductHeterogeneous, False) self.assertEqual(xsModel["YA"].traceIsotopeThreshold, 0.0) - def test_setDefaultSettingsByLowestBuGroupHomogeneous(self): + def test_setDefaultSettingsByLowestEnvGroupHomogeneous(self): # Initialize some micro suffix in the cross sections cs = settings.Settings() xs = XSSettings() @@ -147,7 +147,7 @@ def test_setDefaultSettingsByLowestBuGroupHomogeneous(self): self.assertNotIn("JB", xs) self.assertNotEqual(xs["JD"], xs["JB"]) - def test_setDefaultSettingsByLowestBuGroupOneDimensional(self): + def test_setDefaultSettingsByLowestEnvGroupOneDimensional(self): # Initialize some micro suffix in the cross sections cs = settings.Settings() xsModel = XSSettings() diff --git a/armi/plugins.py b/armi/plugins.py index 59b953000..fdeb4cd66 100644 --- a/armi/plugins.py +++ b/armi/plugins.py @@ -274,12 +274,12 @@ def beforeReactorConstruction(cs) -> None: Function to call before the reactor is constructed. .. impl:: Plugins can inject code before reactor initialization. - :id: I_ARMI_PLUGIN_BEFORE_REACTOR_HOOK - :implements: R_ARMI_PLUGIN_BEFORE_REACTOR_HOOK + :id: I_ARMI_SETTINGS_BEFORE_REACTOR_HOOK + :implements: R_ARMI_SETTINGS_BEFORE_REACTOR_HOOK - This method allows for plugin developers to implement code after settings - are loaded but before the reactor is constructed. This hook is called - in :py:func:`armi.reactor.reactors.factory`. + This method allows for plugin developers to implement code after settings are loaded but + before the reactor is constructed. This hook is called in + :py:func:`armi.reactor.reactors.factory`. """ @staticmethod diff --git a/armi/reactor/blockParameters.py b/armi/reactor/blockParameters.py index 4e8e399cf..820ebac9f 100644 --- a/armi/reactor/blockParameters.py +++ b/armi/reactor/blockParameters.py @@ -21,7 +21,7 @@ from armi.reactor.parameters import NoDefault, Parameter, ParamLocation from armi.reactor.parameters.parameterDefinitions import isNumpyArray from armi.utils import units -from armi.utils.units import ASCII_LETTER_A +from armi.utils.units import ASCII_LETTER_A, ASCII_LETTER_Z, ASCII_LETTER_a def getBlockParameterDefinitions(): @@ -196,53 +196,66 @@ def getBlockParameterDefinitions(): with pDefs.createBuilder(default=0.0, location=ParamLocation.AVERAGE) as pb: - def buGroup(self, buGroupChar): - if isinstance(buGroupChar, (int, float)): - intValue = int(buGroupChar) + def envGroup(self, envGroupChar): + if isinstance(envGroupChar, (int, float)): + intValue = int(envGroupChar) runLog.warning( - f"Attempting to set `b.p.buGroup` to int value ({buGroupChar}). Possibly loading from old database", + f"Attempting to set `b.p.envGroup` to int value ({envGroupChar})." + "Possibly loading from old database", single=True, - label="bu group as int " + str(intValue), + label="env group as int " + str(intValue), ) - self.buGroupNum = intValue + self.envGroupNum = intValue return - elif not isinstance(buGroupChar, six.string_types): + elif not isinstance(envGroupChar, six.string_types): raise Exception( - f"Wrong type for buGroupChar {buGroupChar}: {type(buGroupChar)}" + f"Wrong type for envGroupChar {envGroupChar}: {type(envGroupChar)}" ) - buGroupNum = ord(buGroupChar) - ASCII_LETTER_A - self._p_buGroup = buGroupChar - self._p_buGroupNum = buGroupNum - buGroupNumDef = parameters.ALL_DEFINITIONS["buGroupNum"] - buGroupNumDef.assigned = parameters.SINCE_ANYTHING + if envGroupChar.islower(): + # if lower case find the distance from lowercase a and add the span of A to Z + lowerCaseOffset = ASCII_LETTER_Z - ASCII_LETTER_A + 1 # 26 + envGroupNum = ord(envGroupChar) - ASCII_LETTER_a + lowerCaseOffset + else: + envGroupNum = ord(envGroupChar) - ASCII_LETTER_A + self._p_envGroup = envGroupChar + self._p_envGroupNum = envGroupNum + envGroupNumDef = parameters.ALL_DEFINITIONS["envGroupNum"] + envGroupNumDef.assigned = parameters.SINCE_ANYTHING pb.defParam( - "buGroup", + "envGroup", units=units.UNITLESS, - description="The burnup group letter of this block", + description="The environment group letter of this block", default="A", - setter=buGroup, + setter=envGroup, ) - def buGroupNum(self, buGroupNum): - if buGroupNum > 26: + def envGroupNum(self, envGroupNum): + # support capital and lowercase alpha chars (52= 26*2) + if envGroupNum > 52: raise RuntimeError( - "Invalid bu group number ({}): too many groups. 26 is the max.".format( - buGroupNum + "Invalid env group number ({}): too many groups. 52 is the max.".format( + envGroupNum ) ) - self._p_buGroupNum = buGroupNum - self._p_buGroup = chr(buGroupNum + ASCII_LETTER_A) - buGroupDef = parameters.ALL_DEFINITIONS["buGroup"] - buGroupDef.assigned = parameters.SINCE_ANYTHING - - pb.defParam( - "buGroupNum", + self._p_envGroupNum = envGroupNum + lowerCaseOffset = ASCII_LETTER_Z - ASCII_LETTER_A + if envGroupNum > lowerCaseOffset: + envGroupNum = envGroupNum - (lowerCaseOffset + 1) + self._p_envGroup = chr(envGroupNum + ASCII_LETTER_a) + else: + self._p_envGroup = chr(envGroupNum + ASCII_LETTER_A) + envGroupDef = parameters.ALL_DEFINITIONS["envGroup"] + envGroupDef.assigned = parameters.SINCE_ANYTHING + + pb.defParam( + "envGroupNum", units=units.UNITLESS, - description="An integer representation of the burnup group, linked to buGroup.", + description="An integer representation of the environment group " + "(burnup/temperature/etc.). linked to envGroup.", default=0, - setter=buGroupNum, + setter=envGroupNum, ) pb.defParam( diff --git a/armi/reactor/blocks.py b/armi/reactor/blocks.py index 4485b4b3a..b20122a21 100644 --- a/armi/reactor/blocks.py +++ b/armi/reactor/blocks.py @@ -127,11 +127,11 @@ def __init__(self, name: str, height: float = 1.0): def __repr__(self): # be warned, changing this might break unit tests on input file generations - return "<{type} {name} at {loc} XS: {xs} BU GP: {bu}>".format( + return "<{type} {name} at {loc} XS: {xs} ENV GP: {env}>".format( type=self.getType(), name=self.getName(), xs=self.p.xsType, - bu=self.p.buGroup, + env=self.p.envGroup, loc=self.getLocation(), ) @@ -403,26 +403,28 @@ def getMicroSuffix(self): Notes ----- - The single-letter use for xsType and buGroup limit users to 26 groups of each. - ARMI will allow 2-letter xsType designations if and only if the `buGroups` - setting has length 1 (i.e. no burnup groups are defined). This is useful for + The single-letter use for xsType and envGroup limit users to 52 groups of each. + ARMI will allow 2-letter xsType designations if and only if the `envGroup` + setting has length 1 (i.e. no burnup/temp groups are defined). This is useful for high-fidelity XS modeling of V&V models such as the ZPPRs. """ - bu = self.p.buGroup - if not bu: + env = self.p.envGroup + if not env: raise RuntimeError( - "Cannot get MicroXS suffix because {0} in {1} does not have a burnup group" + "Cannot get MicroXS suffix because {0} in {1} does not have a environment(env) group" "".format(self, self.parent) ) xsType = self.p.xsType if len(xsType) == 1: - return xsType + bu - elif len(xsType) == 2 and ord(bu) > ord("A"): + return xsType + env + elif len(xsType) == 2 and ord(env) != ord("A"): + # default is "A" so if we got an off default 2 char, there is no way to resolve. raise ValueError( - "Use of multiple burnup groups is not allowed with multi-character xs groups!" + "Use of non-default env groups is not allowed with multi-character xs groups!" ) else: + # ignore env group, multi Char XS type to support assigning 2 chars in blueprints return xsType def getHeight(self): @@ -1885,7 +1887,7 @@ def createHomogenizedCopy(self, pinSpatialLocators=False): # assign macros and LFP b.macros = self.macros b._lumpedFissionProducts = self._lumpedFissionProducts - b.p.buGroup = self.p.buGroup + b.p.envGroup = self.p.envGroup hexComponent = Hexagon( "homogenizedHex", diff --git a/armi/reactor/blueprints/isotopicOptions.py b/armi/reactor/blueprints/isotopicOptions.py index dfb6af5d9..506be7b5b 100644 --- a/armi/reactor/blueprints/isotopicOptions.py +++ b/armi/reactor/blueprints/isotopicOptions.py @@ -552,10 +552,14 @@ def eleExpandInfoBasedOnCodeENDF(cs): ) ) - elif cs[CONF_XS_KERNEL] in ["", "SERPENT", "MC2v3", "MC2v3-PARTISN"]: + elif cs[CONF_XS_KERNEL] == "SERPENT": elementalsToKeep.update(endf70Elementals) expansionStrings.update(mc2Expansions) + elif cs[CONF_XS_KERNEL] in ["", "MC2v3", "MC2v3-PARTISN"]: + elementalsToKeep.update(endf71Elementals) + expansionStrings.update(mc2Expansions) + elif cs[CONF_XS_KERNEL] == "DRAGON": # Users need to use default nuclear lib name. This is documented. dragLib = cs["dragonDataPath"] diff --git a/armi/reactor/blueprints/tests/test_customIsotopics.py b/armi/reactor/blueprints/tests/test_customIsotopics.py index 4c341b45b..e22f0a508 100644 --- a/armi/reactor/blueprints/tests/test_customIsotopics.py +++ b/armi/reactor/blueprints/tests/test_customIsotopics.py @@ -471,7 +471,7 @@ def test_expandedNatural(self): self.assertNotIn("FE51", c.getNumberDensities()) # un-natural self.assertNotIn("FE", c.getNumberDensities()) - def test_unrepresentedAreOnlyNatural(self): + def test_infDiluteAreOnlyNatural(self): """Make sure nuclides specified as In-Problem but not actually in any material are only natural isotopics.""" self.assertIn("AL27", self.bp.allNuclidesInProblem) self.assertNotIn("AL26", self.bp.allNuclidesInProblem) diff --git a/armi/reactor/components/componentParameters.py b/armi/reactor/components/componentParameters.py index 14292b126..92bde7dcc 100644 --- a/armi/reactor/components/componentParameters.py +++ b/armi/reactor/components/componentParameters.py @@ -15,6 +15,7 @@ """Component parameter definitions.""" from armi.reactor import parameters from armi.reactor.parameters import ParamLocation +from armi.reactor.parameters.parameterDefinitions import isNumpyArray from armi.utils import units @@ -35,7 +36,7 @@ def getComponentParameterDefinitions(): pb.defParam( "mult", units=units.UNITLESS, - description="The multiplicity of this component, i.e. how many of them there are. ", + description="The multiplicity of this component, i.e. how many of them there are.", default=1, ) @@ -63,6 +64,20 @@ def getComponentParameterDefinitions(): description="Number densities of each nuclide.", ) + pb.defParam( + "detailedNDens", + setter=isNumpyArray("detailedNDens"), + units=f"atoms/(bn*{units.CM})", + description=( + "High-fidelity number density vector with up to thousands of nuclides. " + "Used in high-fi depletion runs where low-fi depletion may also be occurring. " + "This param keeps the hi-fi and low-fi depletion values from interfering. " + "See core.p.detailedNucKeys for keys." + ), + saveToDB=True, + default=None, + ) + pb.defParam( "percentBu", units=f"{units.PERCENT_FIMA}", @@ -98,7 +113,7 @@ def getComponentParameterDefinitions(): pb.defParam( "customIsotopicsName", units=units.UNITLESS, - description="Label of isotopics applied to this component. ", + description="Label of isotopics applied to this component.", ) pb.defParam( @@ -111,7 +126,7 @@ def getComponentParameterDefinitions(): pb.defParam( "zrFrac", units=units.UNITLESS, - description="Original Zr frac of this, used for material properties. ", + description="Original Zr frac of this, used for material properties.", ) pb.defParam( diff --git a/armi/reactor/composites.py b/armi/reactor/composites.py index 3ae44b786..7f09d8656 100644 --- a/armi/reactor/composites.py +++ b/armi/reactor/composites.py @@ -1575,6 +1575,9 @@ def changeNDensByFactor(self, factor): nuc: val * factor for nuc, val in self.getNumberDensities().items() } self.setNumberDensities(densitiesScaled) + # Update detailedNDens + if self.p.detailedNDens is not None: + self.p.detailedNDens *= factor def clearNumberDensities(self): """ diff --git a/armi/reactor/converters/axialExpansionChanger/axialExpansionChanger.py b/armi/reactor/converters/axialExpansionChanger/axialExpansionChanger.py index 86f881874..b38a77226 100644 --- a/armi/reactor/converters/axialExpansionChanger/axialExpansionChanger.py +++ b/armi/reactor/converters/axialExpansionChanger/axialExpansionChanger.py @@ -348,11 +348,7 @@ def axiallyExpandAssembly(self): c.zbottom = self.linked.linkedBlocks[b].lower.p.ztop c.ztop = c.zbottom + c.height # update component number densities - newNumberDensities = { - nuc: c.getNumberDensity(nuc) / growFrac - for nuc in c.getNuclides() - } - c.setNumberDensities(newNumberDensities) + c.changeNDensByFactor(1.0 / growFrac) # redistribute block boundaries if on the target component if self.expansionData.isTargetComponent(c): b.p.ztop = c.ztop @@ -393,7 +389,7 @@ def manageCoreMesh(self, r): if not self._detailedAxialExpansion: # loop through again now that the reference is adjusted and adjust the non-fuel assemblies. for a in r.core.getAssemblies(): - a.setBlockMesh(r.core.refAssem.getAxialMesh()) + a.setBlockMesh(r.core.refAssem.getAxialMesh(), conserveMassFlag="auto") oldMesh = r.core.p.axialMesh r.core.updateAxialMesh() diff --git a/armi/reactor/converters/geometryConverters.py b/armi/reactor/converters/geometryConverters.py index 83944dc2c..e035c4e9a 100644 --- a/armi/reactor/converters/geometryConverters.py +++ b/armi/reactor/converters/geometryConverters.py @@ -822,9 +822,9 @@ def _createRadialThetaZone( # Assign the new block cross section type and burn up group newBlock.setType(newBlockType) - newXsType, newBuGroup = self._createBlendedXSID(newBlock) + newXsType, newEnvGroup = self._createBlendedXSID(newBlock) newBlock.p.xsType = newXsType - newBlock.p.buGroup = newBuGroup + newBlock.p.envGroup = newEnvGroup # Update the block dimensions and set the block densities newComponent.updateDims() # ugh. @@ -989,12 +989,12 @@ def _getHomogenizedBlockType(self, numHexBlockByType): def _createBlendedXSID(self, newBlock): """Generate the blended XS id using the most common XS id in the hexIdList.""" ids = [hexBlock.getMicroSuffix() for hexBlock in self.blockMap[newBlock]] - xsTypeList, buGroupList = zip(*ids) + xsTypeList, envGroupList = zip(*ids) xsType, _count = collections.Counter(xsTypeList).most_common(1)[0] - buGroup, _count = collections.Counter(buGroupList).most_common(1)[0] + envGroup, _count = collections.Counter(envGroupList).most_common(1)[0] - return xsType, buGroup + return xsType, envGroup def _writeRadialThetaZoneHeader( self, radIdx, lowerRing, upperRing, thIdx, lowerTheta, upperTheta diff --git a/armi/reactor/converters/tests/test_axialExpansionChanger.py b/armi/reactor/converters/tests/test_axialExpansionChanger.py index 98cc6ea08..a8a4708b9 100644 --- a/armi/reactor/converters/tests/test_axialExpansionChanger.py +++ b/armi/reactor/converters/tests/test_axialExpansionChanger.py @@ -14,6 +14,7 @@ """Test axialExpansionChanger.""" import collections +import copy import os import unittest from statistics import mean @@ -28,15 +29,15 @@ from armi.reactor.components.basicShapes import Circle, Hexagon, Rectangle from armi.reactor.components.complexShapes import Helix from armi.reactor.converters.axialExpansionChanger import ( - AxialExpansionChanger, AssemblyAxialLinkage, + AxialExpansionChanger, ExpansionData, getSolidComponents, iterSolidComponents, ) from armi.reactor.converters.axialExpansionChanger.assemblyAxialLinkage import ( - areAxiallyLinked, AxialLink, + areAxiallyLinked, ) from armi.reactor.flags import Flags from armi.reactor.tests.test_reactors import loadTestReactor, reduceTestReactorRings @@ -270,6 +271,7 @@ def test_thermalExpansionContractionConservation_simple(self): a = buildTestAssemblyWithFakeMaterial(name="HT9") origMesh = a.getAxialMesh()[:-1] origMasses, origNDens = self._getComponentMassAndNDens(a) + origDetailedNDens = self._setComponentDetailedNDens(a, origNDens) axialExpChngr = AxialExpansionChanger(detailedAxialExpansion=True) tempGrid = linspace(0.0, a.getHeight()) @@ -284,16 +286,20 @@ def test_thermalExpansionContractionConservation_simple(self): # Set new isothermal temp and expand tempField = array([temp] * len(tempGrid)) oldMasses, oldNDens = self._getComponentMassAndNDens(a) + oldDetailedNDens = self._getComponentDetailedNDens(a) axialExpChngr.performThermalAxialExpansion(a, tempGrid, tempField) newMasses, newNDens = self._getComponentMassAndNDens(a) + newDetailedNDens = self._getComponentDetailedNDens(a) self._checkMass(oldMasses, newMasses) self._checkNDens(oldNDens, newNDens, totGrowthFrac) + self._checkDetailedNDens(oldDetailedNDens, newDetailedNDens, totGrowthFrac) # make sure that the assembly returned to the original state for orig, new in zip(origMesh, a.getAxialMesh()): self.assertAlmostEqual(orig, new, places=12) self._checkMass(origMasses, newMasses) self._checkNDens(origNDens, newNDens, 1.0) + self._checkDetailedNDens(origDetailedNDens, newDetailedNDens, 1.0) def test_thermalExpansionContractionConservation_complex(self): """Thermally expand and then contract to ensure original state is recovered. @@ -416,6 +422,17 @@ def _checkNDens(self, prevNDen, newNDens, ratio): if prev: self.assertAlmostEqual(prev / new, ratio, msg=f"{prev} / {new}") + def _checkDetailedNDens(self, prevDetailedNDen, newDetailedNDens, ratio): + """Check whether the detailedNDens of two input dictionaries containing the + detailedNDens arrays for all components of an assembly are conserved. + """ + for prevComp, newComp in zip( + prevDetailedNDen.values(), newDetailedNDens.values() + ): + for prev, new in zip(prevComp, newComp): + if prev: + self.assertAlmostEqual(prev / new, ratio, msg=f"{prev} / {new}") + @staticmethod def _getComponentMassAndNDens(a): masses = {} @@ -426,6 +443,30 @@ def _getComponentMassAndNDens(a): nDens[c] = c.getNumberDensities() return masses, nDens + @staticmethod + def _setComponentDetailedNDens(a, nDens): + """Returns a dictionary that contains detailedNDens for all components in an + assembly object input which are set to the corresponding component number densities + from a number density dictionary input. + """ + detailedNDens = {} + for b in a: + for c in getSolidComponents(b): + c.p.detailedNDens = copy.deepcopy([val for val in nDens[c].values()]) + detailedNDens[c] = c.p.detailedNDens + return detailedNDens + + @staticmethod + def _getComponentDetailedNDens(a): + """Returns a dictionary containing all solid components and their corresponding + detailedNDens from an assembly object input. + """ + detailedNDens = {} + for b in a: + for c in getSolidComponents(b): + detailedNDens[c] = copy.deepcopy(c.p.detailedNDens) + return detailedNDens + def test_targetComponentMassConservation(self): """Tests mass conservation for target components.""" self.expandAssemForMassConservationTest() @@ -571,20 +612,65 @@ def setUp(self): reduceTestReactorRings(self.r, o.cs, 3) self.oldAxialMesh = self.r.core.p.axialMesh + self.componentLst = [] + for b in self.r.core.refAssem: + if b.hasFlags([Flags.FUEL, Flags.PLENUM]): + self.componentLst.extend(getSolidComponents(b)) # expand refAssem by 1.01 L1/L0 - componentLst = [c for b in self.r.core.refAssem for c in b] - expansionGrowthFracs = 1.01 + zeros(len(componentLst)) + expansionGrowthFracs = 1.01 + zeros(len(self.componentLst)) + ( + self.origDetailedNDens, + self.origVolumes, + ) = self._getComponentDetailedNDensAndVol(self.componentLst) self.axialExpChngr.performPrescribedAxialExpansion( - self.r.core.refAssem, componentLst, expansionGrowthFracs, setFuel=True + self.r.core.refAssem, self.componentLst, expansionGrowthFracs, setFuel=True ) def test_manageCoreMesh(self): self.axialExpChngr.manageCoreMesh(self.r) newAxialMesh = self.r.core.p.axialMesh - # skip first and last entries as they do not change - for old, new in zip(self.oldAxialMesh[1:-1], newAxialMesh[1:-1]): + # the top and bottom and top of the grid plate block are not expected to change + for old, new in zip(self.oldAxialMesh[2:-1], newAxialMesh[2:-1]): self.assertLess(old, new) + def test_componentConservation(self): + self.axialExpChngr.manageCoreMesh(self.r) + newDetailedNDens, newVolumes = self._getComponentDetailedNDensAndVol( + self.componentLst + ) + for c in newVolumes.keys(): + self._checkMass( + self.origDetailedNDens[c], + self.origVolumes[c], + newDetailedNDens[c], + newVolumes[c], + c, + ) + + def _getComponentDetailedNDensAndVol(self, componentLst): + """Returns a tuple containing dictionaries of detailedNDens and volumes of + all components from a component list input. + """ + detailedNDens = {} + volumes = {} + for c in componentLst: + c.p.detailedNDens = [val for val in c.getNumberDensities().values()] + detailedNDens[c] = copy.deepcopy(c.p.detailedNDens) + volumes[c] = c.getVolume() + return (detailedNDens, volumes) + + def _checkMass(self, origDetailedNDens, origVolume, newDetailedNDens, newVolume, c): + for prevMass, newMass in zip( + origDetailedNDens * origVolume, newDetailedNDens * newVolume + ): + if c.parent.hasFlags(Flags.FUEL): + self.assertAlmostEqual( + prevMass, newMass, delta=1e-12, msg=f"{c}, {c.parent}" + ) + else: + # should not conserve mass here as it is structural material above active fuel + self.assertAlmostEqual(newMass / prevMass, 0.99, msg=f"{c}, {c.parent}") + class TestExceptions(AxialExpansionTestBase, unittest.TestCase): """Verify exceptions are caught.""" diff --git a/armi/reactor/tests/test_assemblies.py b/armi/reactor/tests/test_assemblies.py index 56c21d91a..51045bd44 100644 --- a/armi/reactor/tests/test_assemblies.py +++ b/armi/reactor/tests/test_assemblies.py @@ -210,7 +210,7 @@ def setUp(self): self.blockParams = { "height": self.height, "bondRemoved": 0.0, - "buGroupNum": 0, + "envGroupNum": 0, "buLimit": 35, "buRate": 0.0, "eqRegion": -1, @@ -229,7 +229,7 @@ def setUp(self): self.blockSettings = { "axMesh": 1, "bondBOL": 0.0028698019026172574, - "buGroup": "A", + "envGroup": "A", "height": 14.4507, "molesHmAtBOL": 65.8572895758245, "nHMAtBOL": 0.011241485251783766, @@ -692,7 +692,7 @@ def test_getBlockData(self): "fastFluence": 1.01, "fastFluencePeak": 50.0, "power": 10000.0, - "buGroup": 4, + "envGroup": 4, "residence": 3.145, "eqRegion": -1, "id": 299.0, diff --git a/armi/reactor/tests/test_blocks.py b/armi/reactor/tests/test_blocks.py index 0671d07b3..232359bb4 100644 --- a/armi/reactor/tests/test_blocks.py +++ b/armi/reactor/tests/test_blocks.py @@ -43,6 +43,8 @@ from armi.utils import hexagon, units from armi.utils.directoryChangers import TemporaryDirectoryChanger from armi.utils.units import MOLES_PER_CC_TO_ATOMS_PER_BARN_CM +from armi.utils.units import ASCII_LETTER_A, ASCII_LETTER_Z, ASCII_LETTER_a + NUM_PINS_IN_TEST_BLOCK = 217 @@ -564,18 +566,44 @@ def test_getXsType(self): ref = "BB" self.assertEqual(cur, ref) - def test_27b_setBuGroup(self): + def test_27b_setEnvGroup(self): type_ = "A" - self.block.p.buGroup = type_ - cur = self.block.p.buGroupNum - ref = ord(type_) - 65 + self.block.p.envGroup = type_ + cur = self.block.p.envGroupNum + ref = ord(type_) - ASCII_LETTER_A + self.assertEqual(cur, ref) + + typeNumber = 25 # this is Z due to 0 based numbers + self.block.p.envGroupNum = typeNumber + cur = self.block.p.envGroup + ref = chr(typeNumber + ASCII_LETTER_A) + self.assertEqual(cur, ref) + self.assertEqual(cur, "Z") + + before_a = ASCII_LETTER_a - 1 + type_ = "a" + self.block.p.envGroup = type_ + cur = self.block.p.envGroupNum + ref = ord(type_) - (before_a) + (ASCII_LETTER_Z - ASCII_LETTER_A) + self.assertEqual(cur, ref) + + typeNumber = 26 # this is a due to 0 based numbers + self.block.p.envGroupNum = typeNumber + cur = self.block.p.envGroup + self.assertEqual(cur, "a") + + type_ = "z" + self.block.p.envGroup = type_ + cur = self.block.p.envGroupNum + ref = ord(type_) - before_a + (ASCII_LETTER_Z - ASCII_LETTER_A) self.assertEqual(cur, ref) - typeNumber = 25 - self.block.p.buGroupNum = typeNumber - cur = self.block.p.buGroup - ref = chr(typeNumber + 65) + typeNumber = 26 * 2 - 1 # 2x letters in alpha with 0 based index + self.block.p.envGroupNum = typeNumber + cur = self.block.p.envGroup + ref = chr((typeNumber - 26) + ASCII_LETTER_a) self.assertEqual(cur, ref) + self.assertEqual(cur, "z") def test_setZeroHeight(self): """Test that demonstrates that a block's height can be set to zero.""" @@ -1088,7 +1116,7 @@ def test_getMicroSuffix(self): self.block.p.xsType = "RS" self.assertEqual(self.block.getMicroSuffix(), "RS") - self.block.p.buGroup = "X" + self.block.p.envGroup = "X" self.block.p.xsType = "AB" with self.assertRaises(ValueError): self.block.getMicroSuffix() diff --git a/armi/reactor/tests/test_components.py b/armi/reactor/tests/test_components.py index 46b936091..7cad72c91 100644 --- a/armi/reactor/tests/test_components.py +++ b/armi/reactor/tests/test_components.py @@ -697,9 +697,11 @@ def test_getNumberDensities(self): def test_changeNumberDensities(self): """Test that demonstates that the number densities on a component can be modified.""" self.component.p.numberDensities = {"NA23": 1.0} + self.component.p.detailedNDens = [1.0] self.assertEqual(self.component.getNumberDensity("NA23"), 1.0) self.component.changeNDensByFactor(3.0) self.assertEqual(self.component.getNumberDensity("NA23"), 3.0) + self.assertEqual(self.component.p.detailedNDens[0], 3.0) def test_fuelMass(self): nominalMass = self.component.getMass() diff --git a/armi/resources/mcc-nuclides.yaml b/armi/resources/mcc-nuclides.yaml index a4d68fdb7..0c617fbd1 100644 --- a/armi/resources/mcc-nuclides.yaml +++ b/armi/resources/mcc-nuclides.yaml @@ -1,1239 +1,1783 @@ # This file contains the nuclides that are defined by the MC2-2 and MC2-3 # codes. The MC2-2 code base uses ENDF/B-V.2 and the MC2-3 code base uses -# ENDF/B-VII.0. This file can be amended in the future for MC2-3 as the -# code base changes, but the nuclides that MC2-3 models are consistent with -# the data that is supplied by ENDF/B-VII.0. See: Appendix B of ANL/NE-11/41 Rev.3 +# ENDF/B-VII.0 or ENDF/B-VII.1. This file can be amended in the future for +# MC2-3 as the code base changes, but the nuclides that MC2-3 models are +# consistent with the data that is supplied by ENDF/B-VII.0. +# See: Appendix B of ANL/NE-11/41 Rev.3 for V.2 and VII.0 isotopes # Public Link: https://publications.anl.gov/anlpubs/2018/10/147840.pdf. AC225: ENDF/B-V.2: null ENDF/B-VII.0: AC2257 + ENDF/B-VII.1: AC2257 AC226: ENDF/B-V.2: null ENDF/B-VII.0: AC2267 + ENDF/B-VII.1: AC2267 AC227: ENDF/B-V.2: null ENDF/B-VII.0: AC2277 + ENDF/B-VII.1: AC2277 AG107: ENDF/B-V.2: AG1075 ENDF/B-VII.0: AG1077 + ENDF/B-VII.1: AG1077 AG109: ENDF/B-V.2: AG1095 ENDF/B-VII.0: AG1097 + ENDF/B-VII.1: AG1097 AG110M: ENDF/B-V.2: null ENDF/B-VII.0: AG10M7 + ENDF/B-VII.1: AG10M7 AG111: ENDF/B-V.2: AG1115 ENDF/B-VII.0: AG1117 + ENDF/B-VII.1: AG1117 AL27: ENDF/B-V.2: AL27 5 ENDF/B-VII.0: AL27_7 + ENDF/B-VII.1: AL27_7 +AM240: + ENDF/B-V.2: null + ENDF/B-VII.0: null + ENDF/B-VII.1: AM2407 AM241: ENDF/B-V.2: AM2415 ENDF/B-VII.0: AM2417 + ENDF/B-VII.1: AM2417 AM242G: ENDF/B-V.2: AM2425 ENDF/B-VII.0: AM2427 + ENDF/B-VII.1: AM2427 AM242M: ENDF/B-V.2: AM242M ENDF/B-VII.0: AM42M7 + ENDF/B-VII.1: AM42M7 AM243: ENDF/B-V.2: AM243V ENDF/B-VII.0: AM2437 + ENDF/B-VII.1: AM2437 AM244: ENDF/B-V.2: null ENDF/B-VII.0: AM2447 + ENDF/B-VII.1: AM2447 AM244M: ENDF/B-V.2: null ENDF/B-VII.0: AM44M7 + ENDF/B-VII.1: AM44M7 AR36: ENDF/B-V.2: null ENDF/B-VII.0: AR36_7 + ENDF/B-VII.1: AR36_7 AR38: ENDF/B-V.2: null ENDF/B-VII.0: AR38_7 + ENDF/B-VII.1: AR38_7 AR40: ENDF/B-V.2: null ENDF/B-VII.0: AR40_7 + ENDF/B-VII.1: AR40_7 +AS74: + ENDF/B-V.2: null + ENDF/B-VII.0: null + ENDF/B-VII.1: AS74_7 AS75: ENDF/B-V.2: AS75 5 ENDF/B-VII.0: AS75_7 + ENDF/B-VII.1: AS75_7 AU197: ENDF/B-V.2: AU1975 ENDF/B-VII.0: AU1977 + ENDF/B-VII.1: AU1977 B10: ENDF/B-V.2: B-10 5 ENDF/B-VII.0: B10__7 + ENDF/B-VII.1: B10__7 B11: ENDF/B-V.2: B-11 5 ENDF/B-VII.0: B11__7 + ENDF/B-VII.1: B11__7 BA130: ENDF/B-V.2: null ENDF/B-VII.0: BA1307 + ENDF/B-VII.1: BA1307 BA132: ENDF/B-V.2: null ENDF/B-VII.0: BA1327 + ENDF/B-VII.1: BA1327 BA133: ENDF/B-V.2: null ENDF/B-VII.0: BA1337 + ENDF/B-VII.1: BA1337 BA134: ENDF/B-V.2: BA1345 ENDF/B-VII.0: BA1347 + ENDF/B-VII.1: BA1347 BA135: ENDF/B-V.2: BA1355 ENDF/B-VII.0: BA1357 + ENDF/B-VII.1: BA1357 BA136: ENDF/B-V.2: BA1365 ENDF/B-VII.0: BA1367 + ENDF/B-VII.1: BA1367 BA137: ENDF/B-V.2: BA1375 ENDF/B-VII.0: BA1377 + ENDF/B-VII.1: BA1377 BA138: ENDF/B-V.2: BA1385 ENDF/B-VII.0: BA1387 + ENDF/B-VII.1: BA1387 BA140: ENDF/B-V.2: BA1405 ENDF/B-VII.0: BA1407 + ENDF/B-VII.1: BA1407 BE7: ENDF/B-V.2: null ENDF/B-VII.0: BE7__7 + ENDF/B-VII.1: BE7__7 BE9: ENDF/B-V.2: BE-9 3 ENDF/B-VII.0: BE9__7 + ENDF/B-VII.1: BE9__7 BI209: ENDF/B-V.2: BI2095 ENDF/B-VII.0: BI2097 + ENDF/B-VII.1: BI2097 +BK245: + ENDF/B-V.2: null + ENDF/B-VII.0: null + ENDF/B-VII.1: BK2457 +BK246: + ENDF/B-V.2: null + ENDF/B-VII.0: null + ENDF/B-VII.1: BK2467 +BK247: + ENDF/B-V.2: null + ENDF/B-VII.0: null + ENDF/B-VII.1: BK2477 +BK248: + ENDF/B-V.2: null + ENDF/B-VII.0: null + ENDF/B-VII.1: BK2487 BK249: ENDF/B-V.2: BK2495 ENDF/B-VII.0: BK2497 + ENDF/B-VII.1: BK2497 BK250: ENDF/B-V.2: null ENDF/B-VII.0: BK2507 + ENDF/B-VII.1: BK2507 BR79: ENDF/B-V.2: BR79 5 ENDF/B-VII.0: BR79_7 + ENDF/B-VII.1: BR79_7 BR81: ENDF/B-V.2: BR81 5 ENDF/B-VII.0: BR81_7 + ENDF/B-VII.1: BR81_7 C: ENDF/B-V.2: C 5 ENDF/B-VII.0: C____7 + ENDF/B-VII.1: C____7 CA: ENDF/B-V.2: CA 5 ENDF/B-VII.0: null + ENDF/B-VII.1: null CA40: ENDF/B-V.2: null ENDF/B-VII.0: CA40_7 + ENDF/B-VII.1: CA40_7 CA42: ENDF/B-V.2: null ENDF/B-VII.0: CA42_7 + ENDF/B-VII.1: CA42_7 CA43: ENDF/B-V.2: null ENDF/B-VII.0: CA43_7 + ENDF/B-VII.1: CA43_7 CA44: ENDF/B-V.2: null ENDF/B-VII.0: CA44_7 + ENDF/B-VII.1: CA44_7 CA46: ENDF/B-V.2: null ENDF/B-VII.0: CA46_7 + ENDF/B-VII.1: CA46_7 CA48: ENDF/B-V.2: null ENDF/B-VII.0: CA48_7 + ENDF/B-VII.1: CA48_7 CD: ENDF/B-V.2: CD 5 ENDF/B-VII.0: null + ENDF/B-VII.1: null CD106: ENDF/B-V.2: CD1065 ENDF/B-VII.0: CD1067 + ENDF/B-VII.1: CD1067 CD108: ENDF/B-V.2: CD1085 ENDF/B-VII.0: CD1087 + ENDF/B-VII.1: CD1087 CD110: ENDF/B-V.2: CD1105 ENDF/B-VII.0: CD1107 + ENDF/B-VII.1: CD1107 CD111: ENDF/B-V.2: CD1115 ENDF/B-VII.0: CD1117 + ENDF/B-VII.1: CD1117 CD112: ENDF/B-V.2: CD1125 ENDF/B-VII.0: CD1127 + ENDF/B-VII.1: CD1127 CD113: ENDF/B-V.2: CD1135 ENDF/B-VII.0: CD1137 + ENDF/B-VII.1: CD1137 CD114: ENDF/B-V.2: CD1145 ENDF/B-VII.0: CD1147 + ENDF/B-VII.1: CD1147 CD115M: ENDF/B-V.2: CD115M ENDF/B-VII.0: CD15M7 + ENDF/B-VII.1: CD15M7 CD116: ENDF/B-V.2: CD1165 ENDF/B-VII.0: CD1167 + ENDF/B-VII.1: CD1167 CE136: ENDF/B-V.2: null ENDF/B-VII.0: CE1367 + ENDF/B-VII.1: CE1367 CE138: ENDF/B-V.2: null ENDF/B-VII.0: CE1387 + ENDF/B-VII.1: CE1387 CE139: ENDF/B-V.2: null ENDF/B-VII.0: CE1397 + ENDF/B-VII.1: CE1397 CE140: ENDF/B-V.2: CE1405 ENDF/B-VII.0: CE1407 + ENDF/B-VII.1: CE1407 CE141: ENDF/B-V.2: CE1415 ENDF/B-VII.0: CE1417 + ENDF/B-VII.1: CE1417 CE142: ENDF/B-V.2: CE1425 ENDF/B-VII.0: CE1427 + ENDF/B-VII.1: CE1427 CE143: ENDF/B-V.2: CE1435 ENDF/B-VII.0: CE1437 + ENDF/B-VII.1: CE1437 CE144: ENDF/B-V.2: CE1445 ENDF/B-VII.0: CE1447 + ENDF/B-VII.1: CE1447 +CF246: + ENDF/B-V.2: null + ENDF/B-VII.0: null + ENDF/B-VII.1: CF2467 +CF248: + ENDF/B-V.2: null + ENDF/B-VII.0: null + ENDF/B-VII.1: CF2487 CF249: ENDF/B-V.2: CF2495 ENDF/B-VII.0: CF2497 + ENDF/B-VII.1: CF2497 CF250: ENDF/B-V.2: CF2505 ENDF/B-VII.0: CF2507 + ENDF/B-VII.1: CF2507 CF251: ENDF/B-V.2: CF2515 ENDF/B-VII.0: CF2517 + ENDF/B-VII.1: CF2517 CF252: ENDF/B-V.2: CF2525 ENDF/B-VII.0: CF2527 + ENDF/B-VII.1: CF2527 CF253: ENDF/B-V.2: CF2535 ENDF/B-VII.0: CF2537 + ENDF/B-VII.1: CF2537 CF254: ENDF/B-V.2: null ENDF/B-VII.0: CF2547 + ENDF/B-VII.1: CF2547 CL: ENDF/B-V.2: CL 5 ENDF/B-VII.0: null + ENDF/B-VII.1: null CL35: ENDF/B-V.2: CL35_7 ENDF/B-VII.0: CL35_7 + ENDF/B-VII.1: CL35_7 CL37: ENDF/B-V.2: CL37_7 ENDF/B-VII.0: CL37_7 + ENDF/B-VII.1: CL37_7 +CM240: + ENDF/B-V.2: null + ENDF/B-VII.0: null + ENDF/B-VII.1: CM2407 CM241: ENDF/B-V.2: CM2415 ENDF/B-VII.0: CM2417 + ENDF/B-VII.1: CM2417 CM242: ENDF/B-V.2: CM2425 ENDF/B-VII.0: CM2427 + ENDF/B-VII.1: CM2427 CM243: ENDF/B-V.2: CM2435 ENDF/B-VII.0: CM2437 + ENDF/B-VII.1: CM2437 CM244: ENDF/B-V.2: CM2445 ENDF/B-VII.0: CM2447 + ENDF/B-VII.1: CM2447 CM245: ENDF/B-V.2: CM2455 ENDF/B-VII.0: CM2457 + ENDF/B-VII.1: CM2457 CM246: ENDF/B-V.2: CM2465 ENDF/B-VII.0: CM2467 + ENDF/B-VII.1: CM2467 CM247: ENDF/B-V.2: CM2475 ENDF/B-VII.0: CM2477 + ENDF/B-VII.1: CM2477 CM248: ENDF/B-V.2: CM2485 ENDF/B-VII.0: CM2487 + ENDF/B-VII.1: CM2487 CM249: ENDF/B-V.2: null ENDF/B-VII.0: CM2497 + ENDF/B-VII.1: CM2497 CM250: ENDF/B-V.2: null ENDF/B-VII.0: CM2507 + ENDF/B-VII.1: CM2507 CO58: ENDF/B-V.2: null ENDF/B-VII.0: CO58_7 + ENDF/B-VII.1: CO58_7 CO58M: ENDF/B-V.2: null ENDF/B-VII.0: CO58M7 + ENDF/B-VII.1: CO58M7 CO59: ENDF/B-V.2: CO59 5 ENDF/B-VII.0: CO59_7 + ENDF/B-VII.1: CO59_7 CR: ENDF/B-V.2: CR S ENDF/B-VII.0: null + ENDF/B-VII.1: null CR50: ENDF/B-V.2: null ENDF/B-VII.0: CR50_7 + ENDF/B-VII.1: CR50_7 CR52: ENDF/B-V.2: null ENDF/B-VII.0: CR52_7 + ENDF/B-VII.1: CR52_7 CR53: ENDF/B-V.2: null ENDF/B-VII.0: CR53_7 + ENDF/B-VII.1: CR53_7 CR54: ENDF/B-V.2: null ENDF/B-VII.0: CR54_7 + ENDF/B-VII.1: CR54_7 CS133: ENDF/B-V.2: CS1335 ENDF/B-VII.0: CS1337 + ENDF/B-VII.1: CS1337 CS134: ENDF/B-V.2: CS1345 ENDF/B-VII.0: CS1347 + ENDF/B-VII.1: CS1347 CS135: ENDF/B-V.2: CS1355 ENDF/B-VII.0: CS1357 + ENDF/B-VII.1: CS1357 CS136: ENDF/B-V.2: CS1365 ENDF/B-VII.0: CS1367 + ENDF/B-VII.1: CS1367 CS137: ENDF/B-V.2: CS1375 ENDF/B-VII.0: CS1377 + ENDF/B-VII.1: CS1377 CU: ENDF/B-V.2: CU 5 ENDF/B-VII.0: null + ENDF/B-VII.1: null CU63: ENDF/B-V.2: null ENDF/B-VII.0: CU63_7 + ENDF/B-VII.1: CU63_7 CU65: ENDF/B-V.2: null ENDF/B-VII.0: CU65_7 + ENDF/B-VII.1: CU65_7 DUMP1: ENDF/B-V.2: DUMMY1 ENDF/B-VII.0: DUMMY + ENDF/B-VII.1: DUMMY DUMP2: ENDF/B-V.2: DUMMY2 ENDF/B-VII.0: DUMMY + ENDF/B-VII.1: DUMMY DY156: ENDF/B-V.2: null ENDF/B-VII.0: DY1567 + ENDF/B-VII.1: DY1567 DY158: ENDF/B-V.2: null ENDF/B-VII.0: DY1587 + ENDF/B-VII.1: DY1587 DY160: ENDF/B-V.2: DY1605 ENDF/B-VII.0: DY1607 + ENDF/B-VII.1: DY1607 DY161: ENDF/B-V.2: DY1615 ENDF/B-VII.0: DY1617 + ENDF/B-VII.1: DY1617 DY162: ENDF/B-V.2: DY1625 ENDF/B-VII.0: DY1627 + ENDF/B-VII.1: DY1627 DY163: ENDF/B-V.2: DY1635 ENDF/B-VII.0: DY1637 + ENDF/B-VII.1: DY1637 DY164: ENDF/B-V.2: DY1645 ENDF/B-VII.0: DY1647 + ENDF/B-VII.1: DY1647 ER162: ENDF/B-V.2: null ENDF/B-VII.0: ER1627 + ENDF/B-VII.1: ER1627 ER164: ENDF/B-V.2: null ENDF/B-VII.0: ER1647 + ENDF/B-VII.1: ER1647 ER166: ENDF/B-V.2: ER1665 ENDF/B-VII.0: ER1667 + ENDF/B-VII.1: ER1667 ER167: ENDF/B-V.2: ER1675 ENDF/B-VII.0: ER1677 + ENDF/B-VII.1: ER1677 ER168: ENDF/B-V.2: null ENDF/B-VII.0: ER1687 + ENDF/B-VII.1: ER1687 ER170: ENDF/B-V.2: null ENDF/B-VII.0: ER1707 + ENDF/B-VII.1: ER1707 +ES251: + ENDF/B-V.2: null + ENDF/B-VII.0: null + ENDF/B-VII.1: ES2517 +ES252: + ENDF/B-V.2: null + ENDF/B-VII.0: null + ENDF/B-VII.1: ES2527 ES253: ENDF/B-V.2: ES2535 ENDF/B-VII.0: ES2537 + ENDF/B-VII.1: ES2537 ES254: ENDF/B-V.2: null ENDF/B-VII.0: ES2547 + ENDF/B-VII.1: ES2547 +ES254M: + ENDF/B-V.2: null + ENDF/B-VII.0: null + ENDF/B-VII.1: ES54M7 ES255: ENDF/B-V.2: null ENDF/B-VII.0: ES2557 + ENDF/B-VII.1: ES2557 EU151: ENDF/B-V.2: EU1515 ENDF/B-VII.0: EU1517 + ENDF/B-VII.1: EU1517 EU152: ENDF/B-V.2: EU1525 ENDF/B-VII.0: EU1527 + ENDF/B-VII.1: EU1527 EU153: ENDF/B-V.2: EU1535 ENDF/B-VII.0: EU1537 + ENDF/B-VII.1: EU1537 EU154: ENDF/B-V.2: EU1545 ENDF/B-VII.0: EU1547 + ENDF/B-VII.1: EU1547 EU155: ENDF/B-V.2: EU1555 ENDF/B-VII.0: EU1557 + ENDF/B-VII.1: EU1557 EU156: ENDF/B-V.2: EU1565 ENDF/B-VII.0: EU1567 + ENDF/B-VII.1: EU1567 EU157: ENDF/B-V.2: EU1575 ENDF/B-VII.0: EU1577 + ENDF/B-VII.1: EU1577 F19: ENDF/B-V.2: F-19 5 ENDF/B-VII.0: F19__7 + ENDF/B-VII.1: F19__7 FE: ENDF/B-V.2: FE SV ENDF/B-VII.0: null + ENDF/B-VII.1: null FE54: ENDF/B-V.2: null ENDF/B-VII.0: FE54_7 + ENDF/B-VII.1: FE54_7 FE56: ENDF/B-V.2: null ENDF/B-VII.0: FE56_7 + ENDF/B-VII.1: FE56_7 FE57: ENDF/B-V.2: null ENDF/B-VII.0: FE57_7 + ENDF/B-VII.1: FE57_7 FE58: ENDF/B-V.2: null ENDF/B-VII.0: FE58_7 + ENDF/B-VII.1: FE58_7 FM255: ENDF/B-V.2: null ENDF/B-VII.0: FM2557 + ENDF/B-VII.1: FM2557 GA: ENDF/B-V.2: GA 5 ENDF/B-VII.0: null + ENDF/B-VII.1: null GA69: ENDF/B-V.2: null ENDF/B-VII.0: GA69_7 + ENDF/B-VII.1: GA69_7 GA71: ENDF/B-V.2: null ENDF/B-VII.0: GA71_7 + ENDF/B-VII.1: GA71_7 GD152: ENDF/B-V.2: GD1525 ENDF/B-VII.0: GD1527 + ENDF/B-VII.1: GD1527 GD153: ENDF/B-V.2: null ENDF/B-VII.0: GD1537 + ENDF/B-VII.1: GD1537 GD154: ENDF/B-V.2: GD1545 ENDF/B-VII.0: GD1547 + ENDF/B-VII.1: GD1547 GD155: ENDF/B-V.2: GD1555 ENDF/B-VII.0: GD1557 + ENDF/B-VII.1: GD1557 GD156: ENDF/B-V.2: GD1565 ENDF/B-VII.0: GD1567 + ENDF/B-VII.1: GD1567 GD157: ENDF/B-V.2: GD1575 ENDF/B-VII.0: GD1577 + ENDF/B-VII.1: GD1577 GD158: ENDF/B-V.2: GD1585 ENDF/B-VII.0: GD1587 + ENDF/B-VII.1: GD1587 GD160: ENDF/B-V.2: GD1605 ENDF/B-VII.0: GD1607 + ENDF/B-VII.1: GD1607 GE70: ENDF/B-V.2: null ENDF/B-VII.0: GE70_7 + ENDF/B-VII.1: GE70_7 GE72: ENDF/B-V.2: GE72 5 ENDF/B-VII.0: GE72_7 + ENDF/B-VII.1: GE72_7 GE73: ENDF/B-V.2: GE73 5 ENDF/B-VII.0: GE73_7 + ENDF/B-VII.1: GE73_7 GE74: ENDF/B-V.2: GE74 5 ENDF/B-VII.0: GE74_7 + ENDF/B-VII.1: GE74_7 GE76: ENDF/B-V.2: GE76 5 ENDF/B-VII.0: GE76_7 + ENDF/B-VII.1: GE76_7 H1: ENDF/B-V.2: HYDRGN ENDF/B-VII.0: H1___7 + ENDF/B-VII.1: H1___7 H2: ENDF/B-V.2: H-2 5 ENDF/B-VII.0: H2___7 + ENDF/B-VII.1: H2___7 H3: ENDF/B-V.2: H-3 5 ENDF/B-VII.0: H3___7 + ENDF/B-VII.1: H3___7 HE3: ENDF/B-V.2: HE3 5 ENDF/B-VII.0: HE3__7 + ENDF/B-VII.1: HE3__7 HE4: ENDF/B-V.2: HE4 5 ENDF/B-VII.0: HE4__7 + ENDF/B-VII.1: HE4__7 HF: ENDF/B-V.2: HF 5 ENDF/B-VII.0: null + ENDF/B-VII.1: null HF174: ENDF/B-V.2: HF1745 ENDF/B-VII.0: HF1747 + ENDF/B-VII.1: HF1747 HF176: ENDF/B-V.2: HF1765 ENDF/B-VII.0: HF1767 + ENDF/B-VII.1: HF1767 HF177: ENDF/B-V.2: HF1775 ENDF/B-VII.0: HF1777 + ENDF/B-VII.1: HF1777 HF178: ENDF/B-V.2: HF1785 ENDF/B-VII.0: HF1787 + ENDF/B-VII.1: HF1787 HF179: ENDF/B-V.2: HF1795 ENDF/B-VII.0: HF1797 + ENDF/B-VII.1: HF1797 HF180: ENDF/B-V.2: HF1805 ENDF/B-VII.0: HF1807 + ENDF/B-VII.1: HF1807 HG196: ENDF/B-V.2: null ENDF/B-VII.0: HG1967 + ENDF/B-VII.1: HG1967 HG198: ENDF/B-V.2: null ENDF/B-VII.0: HG1987 + ENDF/B-VII.1: HG1987 HG199: ENDF/B-V.2: null ENDF/B-VII.0: HG1997 + ENDF/B-VII.1: HG1997 HG200: ENDF/B-V.2: null ENDF/B-VII.0: HG2007 + ENDF/B-VII.1: HG2007 HG201: ENDF/B-V.2: null ENDF/B-VII.0: HG2017 + ENDF/B-VII.1: HG2017 HG202: ENDF/B-V.2: null ENDF/B-VII.0: HG2027 + ENDF/B-VII.1: HG2027 HG204: ENDF/B-V.2: null ENDF/B-VII.0: HG2047 + ENDF/B-VII.1: HG2047 HO165: ENDF/B-V.2: HO1655 ENDF/B-VII.0: HO1657 + ENDF/B-VII.1: HO1657 HO166M: ENDF/B-V.2: null ENDF/B-VII.0: HO66M7 + ENDF/B-VII.1: HO66M7 I127: ENDF/B-V.2: I-1275 ENDF/B-VII.0: I127_7 + ENDF/B-VII.1: I127_7 I129: ENDF/B-V.2: I-1295 ENDF/B-VII.0: I129_7 + ENDF/B-VII.1: I129_7 I130: ENDF/B-V.2: I-1305 ENDF/B-VII.0: I130_7 + ENDF/B-VII.1: I130_7 I131: ENDF/B-V.2: I-1315 ENDF/B-VII.0: I131_7 + ENDF/B-VII.1: I131_7 I135: ENDF/B-V.2: I-1355 ENDF/B-VII.0: I135_7 + ENDF/B-VII.1: I135_7 IN113: ENDF/B-V.2: IN1135 ENDF/B-VII.0: IN1137 + ENDF/B-VII.1: IN1137 IN115: ENDF/B-V.2: IN1155 ENDF/B-VII.0: IN1157 + ENDF/B-VII.1: IN1157 IR191: ENDF/B-V.2: null ENDF/B-VII.0: IR1917 + ENDF/B-VII.1: IR1917 IR193: ENDF/B-V.2: null ENDF/B-VII.0: IR1937 + ENDF/B-VII.1: IR1937 K: ENDF/B-V.2: K 5 ENDF/B-VII.0: null + ENDF/B-VII.1: null K39: ENDF/B-V.2: null ENDF/B-VII.0: K39__7 + ENDF/B-VII.1: K39__7 K40: ENDF/B-V.2: null ENDF/B-VII.0: K40__7 + ENDF/B-VII.1: K40__7 K41: ENDF/B-V.2: null ENDF/B-VII.0: K41__7 + ENDF/B-VII.1: K41__7 KR78: ENDF/B-V.2: KR78 5 ENDF/B-VII.0: KR78_7 + ENDF/B-VII.1: KR78_7 KR80: ENDF/B-V.2: KR80 5 ENDF/B-VII.0: KR80_7 + ENDF/B-VII.1: KR80_7 KR82: ENDF/B-V.2: KR82 5 ENDF/B-VII.0: KR82_7 + ENDF/B-VII.1: KR82_7 KR83: ENDF/B-V.2: KR83 5 ENDF/B-VII.0: KR83_7 + ENDF/B-VII.1: KR83_7 KR84: ENDF/B-V.2: KR84 5 ENDF/B-VII.0: KR84_7 + ENDF/B-VII.1: KR84_7 KR85: ENDF/B-V.2: KR85 5 ENDF/B-VII.0: KR85_7 + ENDF/B-VII.1: KR85_7 KR86: ENDF/B-V.2: KR86 5 ENDF/B-VII.0: KR86_7 + ENDF/B-VII.1: KR86_7 LA138: ENDF/B-V.2: null ENDF/B-VII.0: LA1387 + ENDF/B-VII.1: LA1387 LA139: ENDF/B-V.2: LA1395 ENDF/B-VII.0: LA1397 + ENDF/B-VII.1: LA1397 LA140: ENDF/B-V.2: LA1405 ENDF/B-VII.0: LA1407 + ENDF/B-VII.1: LA1407 LI6: ENDF/B-V.2: LI-6 5 ENDF/B-VII.0: LI6__7 + ENDF/B-VII.1: LI6__7 LI7: ENDF/B-V.2: LI-7 V ENDF/B-VII.0: LI7__7 + ENDF/B-VII.1: LI7__7 LU175: ENDF/B-V.2: LU1755 ENDF/B-VII.0: LU1757 + ENDF/B-VII.1: LU1757 LU176: ENDF/B-V.2: LU1765 ENDF/B-VII.0: LU1767 + ENDF/B-VII.1: LU1767 MG: ENDF/B-V.2: MG 5 ENDF/B-VII.0: null + ENDF/B-VII.1: null MG24: ENDF/B-V.2: null ENDF/B-VII.0: MG24_7 + ENDF/B-VII.1: MG24_7 MG25: ENDF/B-V.2: null ENDF/B-VII.0: MG25_7 + ENDF/B-VII.1: MG25_7 MG26: ENDF/B-V.2: null ENDF/B-VII.0: MG26_7 + ENDF/B-VII.1: MG26_7 MN55: ENDF/B-V.2: MN55 S ENDF/B-VII.0: MN55_7 + ENDF/B-VII.1: MN55_7 MO: ENDF/B-V.2: MO S ENDF/B-VII.0: null + ENDF/B-VII.1: null MO100: ENDF/B-V.2: MO1005 ENDF/B-VII.0: MO1007 + ENDF/B-VII.1: MO1007 MO92: ENDF/B-V.2: MO92 5 ENDF/B-VII.0: MO92_7 + ENDF/B-VII.1: MO92_7 MO94: ENDF/B-V.2: MO94 5 ENDF/B-VII.0: MO94_7 + ENDF/B-VII.1: MO94_7 MO95: ENDF/B-V.2: MO95 5 ENDF/B-VII.0: MO95_7 + ENDF/B-VII.1: MO95_7 MO96: ENDF/B-V.2: MO96 5 ENDF/B-VII.0: MO96_7 + ENDF/B-VII.1: MO96_7 MO97: ENDF/B-V.2: MO97 5 ENDF/B-VII.0: MO97_7 + ENDF/B-VII.1: MO97_7 MO98: ENDF/B-V.2: MO98 5 ENDF/B-VII.0: MO98_7 + ENDF/B-VII.1: MO98_7 MO99: ENDF/B-V.2: MO99 5 ENDF/B-VII.0: MO99_7 + ENDF/B-VII.1: MO99_7 N14: ENDF/B-V.2: N-14 5 ENDF/B-VII.0: N14__7 + ENDF/B-VII.1: N14__7 N15: ENDF/B-V.2: N-15 5 ENDF/B-VII.0: N15__7 + ENDF/B-VII.1: N15__7 NA22: ENDF/B-V.2: null ENDF/B-VII.0: NA22_7 + ENDF/B-VII.1: NA22_7 NA23: ENDF/B-V.2: NA23 S ENDF/B-VII.0: NA23_7 + ENDF/B-VII.1: NA23_7 NB93: ENDF/B-V.2: NB93 5 ENDF/B-VII.0: NB93_7 + ENDF/B-VII.1: NB93_7 NB94: ENDF/B-V.2: NB94 5 ENDF/B-VII.0: NB94_7 + ENDF/B-VII.1: NB94_7 NB95: ENDF/B-V.2: NB95 5 ENDF/B-VII.0: NB95_7 + ENDF/B-VII.1: NB95_7 ND142: ENDF/B-V.2: ND1425 ENDF/B-VII.0: ND1427 + ENDF/B-VII.1: ND1427 ND143: ENDF/B-V.2: ND1435 ENDF/B-VII.0: ND1437 + ENDF/B-VII.1: ND1437 ND144: ENDF/B-V.2: ND1445 ENDF/B-VII.0: ND1447 + ENDF/B-VII.1: ND1447 ND145: ENDF/B-V.2: ND1455 ENDF/B-VII.0: ND1457 + ENDF/B-VII.1: ND1457 ND146: ENDF/B-V.2: ND1465 ENDF/B-VII.0: ND1467 + ENDF/B-VII.1: ND1467 ND147: ENDF/B-V.2: ND1475 ENDF/B-VII.0: ND1477 + ENDF/B-VII.1: ND1477 ND148: ENDF/B-V.2: ND1485 ENDF/B-VII.0: ND1487 + ENDF/B-VII.1: ND1487 ND150: ENDF/B-V.2: ND1505 ENDF/B-VII.0: ND1507 + ENDF/B-VII.1: ND1507 NI: ENDF/B-V.2: NI S ENDF/B-VII.0: null + ENDF/B-VII.1: null NI58: ENDF/B-V.2: null ENDF/B-VII.0: NI58_7 + ENDF/B-VII.1: NI58_7 NI59: ENDF/B-V.2: null ENDF/B-VII.0: NI59_7 + ENDF/B-VII.1: NI59_7 NI60: ENDF/B-V.2: null ENDF/B-VII.0: NI60_7 + ENDF/B-VII.1: NI60_7 NI61: ENDF/B-V.2: null ENDF/B-VII.0: NI61_7 + ENDF/B-VII.1: NI61_7 NI62: ENDF/B-V.2: null ENDF/B-VII.0: NI62_7 + ENDF/B-VII.1: NI62_7 NI64: ENDF/B-V.2: null ENDF/B-VII.0: NI64_7 + ENDF/B-VII.1: NI64_7 +NP234: + ENDF/B-V.2: null + ENDF/B-VII.0: null + ENDF/B-VII.1: NP2347 NP235: ENDF/B-V.2: null ENDF/B-VII.0: NP2357 + ENDF/B-VII.1: NP2357 NP236: ENDF/B-V.2: null ENDF/B-VII.0: NP2367 + ENDF/B-VII.1: NP2367 NP237: ENDF/B-V.2: NP237V ENDF/B-VII.0: NP2377 + ENDF/B-VII.1: NP2377 NP238: ENDF/B-V.2: NP2385 ENDF/B-VII.0: NP2387 + ENDF/B-VII.1: NP2387 NP239: ENDF/B-V.2: null ENDF/B-VII.0: NP2397 + ENDF/B-VII.1: NP2397 O16: ENDF/B-V.2: O-16 5 ENDF/B-VII.0: O16__7 + ENDF/B-VII.1: O16__7 O17: ENDF/B-V.2: O-17 5 ENDF/B-VII.0: O17__7 + ENDF/B-VII.1: O17__7 P31: ENDF/B-V.2: P-31 5 ENDF/B-VII.0: P31__7 + ENDF/B-VII.1: P31__7 +PA229: + ENDF/B-V.2: null + ENDF/B-VII.0: null + ENDF/B-VII.1: PA2297 +PA230: + ENDF/B-V.2: null + ENDF/B-VII.0: null + ENDF/B-VII.1: PA2307 PA231: ENDF/B-V.2: PA2315 ENDF/B-VII.0: PA2317 + ENDF/B-VII.1: PA2317 PA232: ENDF/B-V.2: null ENDF/B-VII.0: PA2327 + ENDF/B-VII.1: PA2327 PA233: ENDF/B-V.2: PA2335 ENDF/B-VII.0: PA2337 + ENDF/B-VII.1: PA2337 PB: ENDF/B-V.2: PB 5 ENDF/B-VII.0: null + ENDF/B-VII.1: null PB204: ENDF/B-V.2: null ENDF/B-VII.0: PB2047 + ENDF/B-VII.1: PB2047 PB206: ENDF/B-V.2: null ENDF/B-VII.0: PB2067 + ENDF/B-VII.1: PB2067 PB207: ENDF/B-V.2: null ENDF/B-VII.0: PB2077 + ENDF/B-VII.1: PB2077 PB208: ENDF/B-V.2: null ENDF/B-VII.0: PB2087 + ENDF/B-VII.1: PB2087 PD102: ENDF/B-V.2: PD1025 ENDF/B-VII.0: PD1027 + ENDF/B-VII.1: PD1027 PD104: ENDF/B-V.2: PD1045 ENDF/B-VII.0: PD1047 + ENDF/B-VII.1: PD1047 PD105: ENDF/B-V.2: PD1055 ENDF/B-VII.0: PD1057 + ENDF/B-VII.1: PD1057 PD106: ENDF/B-V.2: PD1065 ENDF/B-VII.0: PD1067 + ENDF/B-VII.1: PD1067 PD107: ENDF/B-V.2: PD1075 ENDF/B-VII.0: PD1077 + ENDF/B-VII.1: PD1077 PD108: ENDF/B-V.2: PD1085 ENDF/B-VII.0: PD1087 + ENDF/B-VII.1: PD1087 PD110: ENDF/B-V.2: PD1105 ENDF/B-VII.0: PD1107 + ENDF/B-VII.1: PD1107 PM147: ENDF/B-V.2: PM1475 ENDF/B-VII.0: PM1477 + ENDF/B-VII.1: PM1477 PM148: ENDF/B-V.2: PM1485 ENDF/B-VII.0: PM1487 + ENDF/B-VII.1: PM1487 PM148M: ENDF/B-V.2: PM148M ENDF/B-VII.0: PM48M7 + ENDF/B-VII.1: PM48M7 PM149: ENDF/B-V.2: PM1495 ENDF/B-VII.0: PM1497 + ENDF/B-VII.1: PM1497 PM151: ENDF/B-V.2: PM1515 ENDF/B-VII.0: PM1517 + ENDF/B-VII.1: PM1517 PR141: ENDF/B-V.2: PR1415 ENDF/B-VII.0: PR1417 + ENDF/B-VII.1: PR1417 PR142: ENDF/B-V.2: PR1425 ENDF/B-VII.0: PR1427 + ENDF/B-VII.1: PR1427 PR143: ENDF/B-V.2: PR1435 ENDF/B-VII.0: PR1437 + ENDF/B-VII.1: PR1437 PU236: ENDF/B-V.2: PU2365 ENDF/B-VII.0: PU2367 + ENDF/B-VII.1: PU2367 PU237: ENDF/B-V.2: PU2375 ENDF/B-VII.0: PU2377 + ENDF/B-VII.1: PU2377 PU238: ENDF/B-V.2: PU2385 ENDF/B-VII.0: PU2387 + ENDF/B-VII.1: PU2387 PU239: ENDF/B-V.2: PU239V ENDF/B-VII.0: PU2397 + ENDF/B-VII.1: PU2397 PU240: ENDF/B-V.2: PU2405 ENDF/B-VII.0: PU2407 + ENDF/B-VII.1: PU2407 PU241: ENDF/B-V.2: PU2415 ENDF/B-VII.0: PU2417 + ENDF/B-VII.1: PU2417 PU242: ENDF/B-V.2: PU2425 ENDF/B-VII.0: PU2427 + ENDF/B-VII.1: PU2427 PU243: ENDF/B-V.2: PU2435 ENDF/B-VII.0: PU2437 + ENDF/B-VII.1: PU2437 PU244: ENDF/B-V.2: PU2445 ENDF/B-VII.0: PU2447 + ENDF/B-VII.1: PU2447 PU246: ENDF/B-V.2: null ENDF/B-VII.0: PU2467 + ENDF/B-VII.1: PU2467 RA223: ENDF/B-V.2: null ENDF/B-VII.0: RA2237 + ENDF/B-VII.1: RA2237 RA224: ENDF/B-V.2: null ENDF/B-VII.0: RA2247 + ENDF/B-VII.1: RA2247 RA225: ENDF/B-V.2: null ENDF/B-VII.0: RA2257 + ENDF/B-VII.1: RA2257 RA226: ENDF/B-V.2: null ENDF/B-VII.0: RA2267 + ENDF/B-VII.1: RA2267 RB85: ENDF/B-V.2: RB85 5 ENDF/B-VII.0: RB85_7 + ENDF/B-VII.1: RB85_7 RB86: ENDF/B-V.2: RB86 5 ENDF/B-VII.0: RB86_7 + ENDF/B-VII.1: RB86_7 RB87: ENDF/B-V.2: RB87 5 ENDF/B-VII.0: RB87_7 + ENDF/B-VII.1: RB87_7 RE185: ENDF/B-V.2: RE1855 ENDF/B-VII.0: RE1857 + ENDF/B-VII.1: RE1857 RE187: ENDF/B-V.2: RE1875 ENDF/B-VII.0: RE1877 + ENDF/B-VII.1: RE1877 RH103: ENDF/B-V.2: RH1035 ENDF/B-VII.0: RH1037 + ENDF/B-VII.1: RH1037 RH105: ENDF/B-V.2: RH1055 ENDF/B-VII.0: RH1057 + ENDF/B-VII.1: RH1057 RU100: ENDF/B-V.2: RU1005 ENDF/B-VII.0: RU1007 + ENDF/B-VII.1: RU1007 RU101: ENDF/B-V.2: RU1015 ENDF/B-VII.0: RU1017 + ENDF/B-VII.1: RU1017 RU102: ENDF/B-V.2: RU1025 ENDF/B-VII.0: RU1027 + ENDF/B-VII.1: RU1027 RU103: ENDF/B-V.2: RU1035 ENDF/B-VII.0: RU1037 + ENDF/B-VII.1: RU1037 RU104: ENDF/B-V.2: RU1045 ENDF/B-VII.0: RU1047 + ENDF/B-VII.1: RU1047 RU105: ENDF/B-V.2: RU1055 ENDF/B-VII.0: RU1057 + ENDF/B-VII.1: RU1057 RU106: ENDF/B-V.2: RU1065 ENDF/B-VII.0: RU1067 + ENDF/B-VII.1: RU1067 RU96: ENDF/B-V.2: RU96 5 ENDF/B-VII.0: RU96_7 + ENDF/B-VII.1: RU96_7 RU98: ENDF/B-V.2: RU98 5 ENDF/B-VII.0: RU98_7 + ENDF/B-VII.1: RU98_7 RU99: ENDF/B-V.2: RU99 5 ENDF/B-VII.0: RU99_7 + ENDF/B-VII.1: RU99_7 S: ENDF/B-V.2: S 5 ENDF/B-VII.0: null + ENDF/B-VII.1: null S32: ENDF/B-V.2: S-32 5 ENDF/B-VII.0: S32__7 + ENDF/B-VII.1: S32__7 S33: ENDF/B-V.2: null ENDF/B-VII.0: S33__7 + ENDF/B-VII.1: S33__7 S34: ENDF/B-V.2: null ENDF/B-VII.0: S34__7 + ENDF/B-VII.1: S34__7 S36: ENDF/B-V.2: null ENDF/B-VII.0: S36__7 + ENDF/B-VII.1: S36__7 SB121: ENDF/B-V.2: SB1215 ENDF/B-VII.0: SB1217 + ENDF/B-VII.1: SB1217 SB123: ENDF/B-V.2: SB1235 ENDF/B-VII.0: SB1237 + ENDF/B-VII.1: SB1237 SB124: ENDF/B-V.2: SB1245 ENDF/B-VII.0: SB1247 + ENDF/B-VII.1: SB1247 SB125: ENDF/B-V.2: SB1255 ENDF/B-VII.0: SB1257 + ENDF/B-VII.1: SB1257 SB126: ENDF/B-V.2: SB1265 ENDF/B-VII.0: SB1267 + ENDF/B-VII.1: SB1267 SC45: ENDF/B-V.2: null ENDF/B-VII.0: SC45_7 + ENDF/B-VII.1: SC45_7 SE74: ENDF/B-V.2: SE74 5 ENDF/B-VII.0: SE74_7 + ENDF/B-VII.1: SE74_7 SE76: ENDF/B-V.2: SE76 5 ENDF/B-VII.0: SE76_7 + ENDF/B-VII.1: SE76_7 SE77: ENDF/B-V.2: SE77 5 ENDF/B-VII.0: SE77_7 + ENDF/B-VII.1: SE77_7 SE78: ENDF/B-V.2: SE78 5 ENDF/B-VII.0: SE78_7 + ENDF/B-VII.1: SE78_7 SE79: ENDF/B-V.2: null ENDF/B-VII.0: SE79_7 + ENDF/B-VII.1: SE79_7 SE80: ENDF/B-V.2: SE80 5 ENDF/B-VII.0: SE80_7 + ENDF/B-VII.1: SE80_7 SE82: ENDF/B-V.2: SE82 5 ENDF/B-VII.0: SE82_7 + ENDF/B-VII.1: SE82_7 SI: ENDF/B-V.2: SI 5 ENDF/B-VII.0: null + ENDF/B-VII.1: null SI28: ENDF/B-V.2: null ENDF/B-VII.0: SI28_7 + ENDF/B-VII.1: SI28_7 SI29: ENDF/B-V.2: null ENDF/B-VII.0: SI29_7 + ENDF/B-VII.1: SI29_7 SI30: ENDF/B-V.2: null ENDF/B-VII.0: SI30_7 + ENDF/B-VII.1: SI30_7 SM144: ENDF/B-V.2: SM1445 ENDF/B-VII.0: SM1447 + ENDF/B-VII.1: SM1447 SM147: ENDF/B-V.2: SM1475 ENDF/B-VII.0: SM1477 + ENDF/B-VII.1: SM1477 SM148: ENDF/B-V.2: SM1485 ENDF/B-VII.0: SM1487 + ENDF/B-VII.1: SM1487 SM149: ENDF/B-V.2: SM1495 ENDF/B-VII.0: SM1497 + ENDF/B-VII.1: SM1497 SM150: ENDF/B-V.2: SM1505 ENDF/B-VII.0: SM1507 + ENDF/B-VII.1: SM1507 SM151: ENDF/B-V.2: SM1515 ENDF/B-VII.0: SM1517 + ENDF/B-VII.1: SM1517 SM152: ENDF/B-V.2: SM1525 ENDF/B-VII.0: SM1527 + ENDF/B-VII.1: SM1527 SM153: ENDF/B-V.2: SM1535 ENDF/B-VII.0: SM1537 + ENDF/B-VII.1: SM1537 SM154: ENDF/B-V.2: SM1545 ENDF/B-VII.0: SM1547 + ENDF/B-VII.1: SM1547 SN112: ENDF/B-V.2: SN1125 ENDF/B-VII.0: SN1127 + ENDF/B-VII.1: SN1127 SN113: ENDF/B-V.2: null ENDF/B-VII.0: SN1137 + ENDF/B-VII.1: SN1137 SN114: ENDF/B-V.2: SN1145 ENDF/B-VII.0: SN1147 + ENDF/B-VII.1: SN1147 SN115: ENDF/B-V.2: SN1155 ENDF/B-VII.0: SN1157 + ENDF/B-VII.1: SN1157 SN116: ENDF/B-V.2: SN1165 ENDF/B-VII.0: SN1167 + ENDF/B-VII.1: SN1167 SN117: ENDF/B-V.2: SN1175 ENDF/B-VII.0: SN1177 + ENDF/B-VII.1: SN1177 SN118: ENDF/B-V.2: SN1185 ENDF/B-VII.0: SN1187 + ENDF/B-VII.1: SN1187 SN119: ENDF/B-V.2: SN1195 ENDF/B-VII.0: SN1197 + ENDF/B-VII.1: SN1197 SN120: ENDF/B-V.2: SN1205 ENDF/B-VII.0: SN1207 + ENDF/B-VII.1: SN1207 SN122: ENDF/B-V.2: SN1225 ENDF/B-VII.0: SN1227 + ENDF/B-VII.1: SN1227 SN123: ENDF/B-V.2: SN1235 ENDF/B-VII.0: SN1237 + ENDF/B-VII.1: SN1237 SN124: ENDF/B-V.2: SN1245 ENDF/B-VII.0: SN1247 + ENDF/B-VII.1: SN1247 SN125: ENDF/B-V.2: SN1255 ENDF/B-VII.0: SN1257 + ENDF/B-VII.1: SN1257 SN126: ENDF/B-V.2: SN1265 ENDF/B-VII.0: SN1267 + ENDF/B-VII.1: SN1267 SR84: ENDF/B-V.2: SR84 5 ENDF/B-VII.0: SR84_7 + ENDF/B-VII.1: SR84_7 SR86: ENDF/B-V.2: SR86 5 ENDF/B-VII.0: SR86_7 + ENDF/B-VII.1: SR86_7 SR87: ENDF/B-V.2: SR87 5 ENDF/B-VII.0: SR87_7 + ENDF/B-VII.1: SR87_7 SR88: ENDF/B-V.2: SR88 5 ENDF/B-VII.0: SR88_7 + ENDF/B-VII.1: SR88_7 SR89: ENDF/B-V.2: SR89 5 ENDF/B-VII.0: SR89_7 + ENDF/B-VII.1: SR89_7 SR90: ENDF/B-V.2: SR90 5 ENDF/B-VII.0: SR90_7 + ENDF/B-VII.1: SR90_7 +TA180: + ENDF/B-V.2: null + ENDF/B-VII.0: null + ENDF/B-VII.1: TA1807 TA181: ENDF/B-V.2: TA1815 ENDF/B-VII.0: TA1817 + ENDF/B-VII.1: TA1817 TA182: ENDF/B-V.2: TA1825 ENDF/B-VII.0: TA1827 + ENDF/B-VII.1: TA1827 TB159: ENDF/B-V.2: TB1595 ENDF/B-VII.0: TB1597 + ENDF/B-VII.1: TB1597 TB160: ENDF/B-V.2: TB1605 ENDF/B-VII.0: TB1607 + ENDF/B-VII.1: TB1607 TC99: ENDF/B-V.2: TC99 5 ENDF/B-VII.0: TC99_7 + ENDF/B-VII.1: TC99_7 TE120: ENDF/B-V.2: TE1205 ENDF/B-VII.0: TE1207 + ENDF/B-VII.1: TE1207 TE122: ENDF/B-V.2: TE1225 ENDF/B-VII.0: TE1227 + ENDF/B-VII.1: TE1227 TE123: ENDF/B-V.2: TE1235 ENDF/B-VII.0: TE1237 + ENDF/B-VII.1: TE1237 TE124: ENDF/B-V.2: TE1245 ENDF/B-VII.0: TE1247 + ENDF/B-VII.1: TE1247 TE125: ENDF/B-V.2: TE1255 ENDF/B-VII.0: TE1257 + ENDF/B-VII.1: TE1257 TE126: ENDF/B-V.2: TE1265 ENDF/B-VII.0: TE1267 + ENDF/B-VII.1: TE1267 TE127M: ENDF/B-V.2: TE127M ENDF/B-VII.0: TE27M7 + ENDF/B-VII.1: TE27M7 TE128: ENDF/B-V.2: TE1285 ENDF/B-VII.0: TE1287 + ENDF/B-VII.1: TE1287 TE129M: ENDF/B-V.2: TE129M ENDF/B-VII.0: TE29M7 + ENDF/B-VII.1: TE29M7 TE130: ENDF/B-V.2: TE1305 ENDF/B-VII.0: TE1307 + ENDF/B-VII.1: TE1307 TE132: ENDF/B-V.2: TE1325 ENDF/B-VII.0: TE1327 + ENDF/B-VII.1: TE1327 TH227: ENDF/B-V.2: null ENDF/B-VII.0: TH2277 + ENDF/B-VII.1: TH2277 TH228: ENDF/B-V.2: null ENDF/B-VII.0: TH2287 + ENDF/B-VII.1: TH2287 TH229: ENDF/B-V.2: null ENDF/B-VII.0: TH2297 + ENDF/B-VII.1: TH2297 TH230: ENDF/B-V.2: TH2305 ENDF/B-VII.0: TH2307 + ENDF/B-VII.1: TH2307 +TH231: + ENDF/B-V.2: null + ENDF/B-VII.0: null + ENDF/B-VII.1: TH2317 TH232: ENDF/B-V.2: TH2325 ENDF/B-VII.0: TH2327 + ENDF/B-VII.1: TH2327 TH233: ENDF/B-V.2: null ENDF/B-VII.0: TH2337 + ENDF/B-VII.1: TH2337 TH234: ENDF/B-V.2: null ENDF/B-VII.0: TH2347 + ENDF/B-VII.1: TH2347 TI: ENDF/B-V.2: TI 5 ENDF/B-VII.0: null + ENDF/B-VII.1: null TI46: ENDF/B-V.2: null ENDF/B-VII.0: TI46_7 + ENDF/B-VII.1: TI46_7 TI47: ENDF/B-V.2: null ENDF/B-VII.0: TI47_7 + ENDF/B-VII.1: TI47_7 TI48: ENDF/B-V.2: null ENDF/B-VII.0: TI48_7 + ENDF/B-VII.1: TI48_7 TI49: ENDF/B-V.2: null ENDF/B-VII.0: TI49_7 + ENDF/B-VII.1: TI49_7 TI50: ENDF/B-V.2: null ENDF/B-VII.0: TI50_7 + ENDF/B-VII.1: TI50_7 +TM168: + ENDF/B-V.2: null + ENDF/B-VII.0: null + ENDF/B-VII.1: TM1687 +TM169: + ENDF/B-V.2: null + ENDF/B-VII.0: null + ENDF/B-VII.1: TM1697 +TM170: + ENDF/B-V.2: null + ENDF/B-VII.0: null + ENDF/B-VII.1: TM1707 +TL203: + ENDF/B-V.2: null + ENDF/B-VII.0: null + ENDF/B-VII.1: TL2037 +TL205: + ENDF/B-V.2: null + ENDF/B-VII.0: null + ENDF/B-VII.1: TL2057 +U230: + ENDF/B-V.2: null + ENDF/B-VII.0: null + ENDF/B-VII.1: U230_7 +U231: + ENDF/B-V.2: null + ENDF/B-VII.0: null + ENDF/B-VII.1: U231_7 U232: ENDF/B-V.2: U-2325 ENDF/B-VII.0: U232_7 + ENDF/B-VII.1: U232_7 U233: ENDF/B-V.2: U-2335 ENDF/B-VII.0: U233_7 + ENDF/B-VII.1: U233_7 U234: ENDF/B-V.2: U-2345 ENDF/B-VII.0: U234_7 + ENDF/B-VII.1: U234_7 U235: ENDF/B-V.2: U-2355 ENDF/B-VII.0: U235_7 + ENDF/B-VII.1: U235_7 U236: ENDF/B-V.2: U-2365 ENDF/B-VII.0: U236_7 + ENDF/B-VII.1: U236_7 U237: ENDF/B-V.2: U-2375 ENDF/B-VII.0: U237_7 + ENDF/B-VII.1: U237_7 U238: ENDF/B-V.2: U-2385 ENDF/B-VII.0: U238_7 + ENDF/B-VII.1: U238_7 U239: ENDF/B-V.2: null ENDF/B-VII.0: U239_7 + ENDF/B-VII.1: U239_7 U240: ENDF/B-V.2: null ENDF/B-VII.0: U240_7 + ENDF/B-VII.1: U240_7 U241: ENDF/B-V.2: null ENDF/B-VII.0: U241_7 + ENDF/B-VII.1: U241_7 V: ENDF/B-V.2: V 5 ENDF/B-VII.0: V____7 + ENDF/B-VII.1: null +V50: + ENDF/B-V.2: null + ENDF/B-VII.0: null + ENDF/B-VII.1: V50__7 +V51: + ENDF/B-V.2: null + ENDF/B-VII.0: null + ENDF/B-VII.1: V51__7 +W180: + ENDF/B-V.2: null + ENDF/B-VII.0: null + ENDF/B-VII.1: W180_7 W182: ENDF/B-V.2: W-182V ENDF/B-VII.0: W182_7 + ENDF/B-VII.1: W182_7 W183: ENDF/B-V.2: W-183V ENDF/B-VII.0: W183_7 + ENDF/B-VII.1: W183_7 W184: ENDF/B-V.2: W-184V ENDF/B-VII.0: W184_7 + ENDF/B-VII.1: W184_7 W186: ENDF/B-V.2: W-186V ENDF/B-VII.0: W186_7 + ENDF/B-VII.1: W186_7 XE123: ENDF/B-V.2: null ENDF/B-VII.0: XE1237 + ENDF/B-VII.1: XE1237 XE124: ENDF/B-V.2: XE1245 ENDF/B-VII.0: XE1247 + ENDF/B-VII.1: XE1247 XE126: ENDF/B-V.2: XE1265 ENDF/B-VII.0: XE1267 + ENDF/B-VII.1: XE1267 XE128: ENDF/B-V.2: XE1285 ENDF/B-VII.0: XE1287 + ENDF/B-VII.1: XE1287 XE129: ENDF/B-V.2: XE1295 ENDF/B-VII.0: XE1297 + ENDF/B-VII.1: XE1297 XE130: ENDF/B-V.2: XE1305 ENDF/B-VII.0: XE1307 + ENDF/B-VII.1: XE1307 XE131: ENDF/B-V.2: XE1315 ENDF/B-VII.0: XE1317 + ENDF/B-VII.1: XE1317 XE132: ENDF/B-V.2: XE1325 ENDF/B-VII.0: XE1327 + ENDF/B-VII.1: XE1327 XE133: ENDF/B-V.2: XE1335 ENDF/B-VII.0: XE1337 + ENDF/B-VII.1: XE1337 XE134: ENDF/B-V.2: XE1345 ENDF/B-VII.0: XE1347 + ENDF/B-VII.1: XE1347 XE135: ENDF/B-V.2: XE1355 ENDF/B-VII.0: XE1357 + ENDF/B-VII.1: XE1357 XE136: ENDF/B-V.2: XE1365 ENDF/B-VII.0: XE1367 + ENDF/B-VII.1: XE1367 Y89: ENDF/B-V.2: Y89 5 ENDF/B-VII.0: Y89__7 + ENDF/B-VII.1: Y89__7 Y90: ENDF/B-V.2: Y90 5 ENDF/B-VII.0: null + ENDF/B-VII.1: Y90__7 Y91: ENDF/B-V.2: Y91 5 ENDF/B-VII.0: Y91__7 + ENDF/B-VII.1: Y91__7 ZN: ENDF/B-V.2: null ENDF/B-VII.0: ZN___7 + ENDF/B-VII.1: null +ZN64: + ENDF/B-V.2: null + ENDF/B-VII.0: null + ENDF/B-VII.1: ZN64_7 +ZN65: + ENDF/B-V.2: null + ENDF/B-VII.0: null + ENDF/B-VII.1: ZN65_7 +ZN66: + ENDF/B-V.2: null + ENDF/B-VII.0: null + ENDF/B-VII.1: ZN66_7 +ZN67: + ENDF/B-V.2: null + ENDF/B-VII.0: null + ENDF/B-VII.1: ZN67_7 +ZN68: + ENDF/B-V.2: null + ENDF/B-VII.0: null + ENDF/B-VII.1: ZN68_7 +ZN70: + ENDF/B-V.2: null + ENDF/B-VII.0: null + ENDF/B-VII.1: ZN70_7 ZR: ENDF/B-V.2: ZIRCSV ENDF/B-VII.0: null + ENDF/B-VII.1: null ZR90: ENDF/B-V.2: ZR90SV ENDF/B-VII.0: ZR90_7 + ENDF/B-VII.1: ZR90_7 ZR91: ENDF/B-V.2: ZR91SV ENDF/B-VII.0: ZR91_7 + ENDF/B-VII.1: ZR91_7 ZR92: ENDF/B-V.2: ZR92SV ENDF/B-VII.0: ZR92_7 + ENDF/B-VII.1: ZR92_7 ZR93: ENDF/B-V.2: ZR93 5 ENDF/B-VII.0: ZR93_7 + ENDF/B-VII.1: ZR93_7 ZR94: ENDF/B-V.2: ZR94SV ENDF/B-VII.0: ZR94_7 + ENDF/B-VII.1: ZR94_7 ZR95: ENDF/B-V.2: ZR95 5 ENDF/B-VII.0: ZR95_7 + ENDF/B-VII.1: ZR95_7 ZR96: ENDF/B-V.2: ZR96 5 ENDF/B-VII.0: ZR96_7 + ENDF/B-VII.1: ZR96_7 \ No newline at end of file diff --git a/armi/settings/fwSettings/globalSettings.py b/armi/settings/fwSettings/globalSettings.py index cf9ed1dc6..353a0cc79 100644 --- a/armi/settings/fwSettings/globalSettings.py +++ b/armi/settings/fwSettings/globalSettings.py @@ -40,6 +40,7 @@ CONF_BETA = "beta" CONF_BRANCH_VERBOSITY = "branchVerbosity" CONF_BU_GROUPS = "buGroups" +CONF_TEMP_GROUPS = "tempGroups" CONF_BURN_CHAIN_FILE_NAME = "burnChainFileName" CONF_BURN_STEPS = "burnSteps" CONF_BURNUP_PEAKING_FACTOR = "burnupPeakingFactor" @@ -405,18 +406,32 @@ def defineSettings() -> List[setting.Setting]: ), setting.Setting( CONF_BU_GROUPS, - default=[10, 20, 30, 100], - label="Burnup Groups", + default=[10, 20, 30], + label="Burnup XS Groups", description="The range of burnups where cross-sections will be the same " - "for a given assembly type (units of %FIMA)", + "for a given cross section type (units of %FIMA)", schema=vol.Schema( [ vol.All( - vol.Coerce(int), vol.Range(min=0, min_included=False, max=100) + vol.Coerce(int), + vol.Range( + min=0, + min_included=False, + ), ) ] ), ), + setting.Setting( + CONF_TEMP_GROUPS, + default=[], + label="Temperature XS Groups", + description="The range of fuel temperatures where cross-sections will be the same " + "for a given cross section type (units of degrees C)", + schema=vol.Schema( + [vol.All(vol.Coerce(int), vol.Range(min=0, min_included=False))] + ), + ), setting.Setting( CONF_BURNUP_PEAKING_FACTOR, default=0.0, diff --git a/armi/tests/ThRZSettings.yaml b/armi/tests/ThRZSettings.yaml index f9ef807ac..70090ffbe 100644 --- a/armi/tests/ThRZSettings.yaml +++ b/armi/tests/ThRZSettings.yaml @@ -7,7 +7,7 @@ settings: comment: "Revised benchmark " geomFile: ThRZGeom.xml loadingFile: ThRZloading.yaml - numProcessors: 12 + nTasks: 12 outputFileExtension: png power: 1000000.0 diff --git a/armi/tests/anl-afci-177/anl-afci-177.yaml b/armi/tests/anl-afci-177/anl-afci-177.yaml index 02f8c512a..d2cd486d2 100644 --- a/armi/tests/anl-afci-177/anl-afci-177.yaml +++ b/armi/tests/anl-afci-177/anl-afci-177.yaml @@ -19,6 +19,6 @@ settings: - 100 comment: ANL-AFCI-177 CR 1.0 metal core but with HALEU instead of TRU genXS: Neutron - numProcessors: 1 + nTasks: 1 versions: armi: uncontrolled diff --git a/armi/tests/c5g7/c5g7-settings.yaml b/armi/tests/c5g7/c5g7-settings.yaml index 2025cb087..773344143 100644 --- a/armi/tests/c5g7/c5g7-settings.yaml +++ b/armi/tests/c5g7/c5g7-settings.yaml @@ -8,7 +8,7 @@ settings: cycleLength: 411.11 loadingFile: c5g7-blueprints.yaml nCycles: 10 - numProcessors: 1 + nTasks: 1 power: 1000000000.0 versions: armi: uncontrolled diff --git a/armi/tests/godiva/godiva.armi.unittest.yaml b/armi/tests/godiva/godiva.armi.unittest.yaml index 3ffa71b43..b09e5ab18 100644 --- a/armi/tests/godiva/godiva.armi.unittest.yaml +++ b/armi/tests/godiva/godiva.armi.unittest.yaml @@ -16,7 +16,7 @@ settings: neutronicsKernel: DIF3D-FD neutronicsOutputsToSave: All neutronicsType: both - numProcessors: 36 + nTasks: 36 outers: 200 power: 0.001 verbosity: debug diff --git a/armi/tests/test_interfaces.py b/armi/tests/test_interfaces.py index 2ae280457..1f23afdd3 100644 --- a/armi/tests/test_interfaces.py +++ b/armi/tests/test_interfaces.py @@ -14,12 +14,9 @@ """Tests the Interface.""" import unittest -import os from armi import interfaces from armi import settings -from armi.tests import TEST_ROOT -from armi.utils import textProcessors class DummyInterface(interfaces.Interface): @@ -75,25 +72,6 @@ def test_duplicate(self): self.assertEqual(i.enabled(), iDup.enabled()) -class TestTextProcessor(unittest.TestCase): - """Test Text processor.""" - - def setUp(self): - self.tp = textProcessors.TextProcessor(os.path.join(TEST_ROOT, "geom.xml")) - - def test_fsearch(self): - """Test fsearch in re mode.""" - line = self.tp.fsearch("xml") - self.assertIn("version", line) - self.assertEqual(self.tp.fsearch("xml"), "") - - def test_fsearch_text(self): - """Test fsearch in text mode.""" - line = self.tp.fsearch("xml", textFlag=True) - self.assertIn("version", line) - self.assertEqual(self.tp.fsearch("xml"), "") - - class TestTightCoupler(unittest.TestCase): """Test the tight coupler class.""" diff --git a/armi/tests/test_plugins.py b/armi/tests/test_plugins.py index 3674f4366..462d7a7ad 100644 --- a/armi/tests/test_plugins.py +++ b/armi/tests/test_plugins.py @@ -154,8 +154,8 @@ def test_beforeReactorConstructionHook(self): """Test that plugin hook successfully injects code before reactor initialization. .. test:: Capture code in the beforeReactorConstruction hook from reactor construction being carried out. - :id: T_ARMI_PLUGIN_BEFORE_REACTOR_HOOK - :tests: R_ARMI_PLUGIN_BEFORE_REACTOR_HOOK + :id: T_ARMI_SETTINGS_BEFORE_REACTOR_HOOK + :tests: R_ARMI_SETTINGS_BEFORE_REACTOR_HOOK """ pm = getPluginManagerOrFail() pm.register(BeforeReactorPlugin) diff --git a/armi/tests/zpprTest.yaml b/armi/tests/zpprTest.yaml index 51a6bb813..034567fa3 100644 --- a/armi/tests/zpprTest.yaml +++ b/armi/tests/zpprTest.yaml @@ -12,7 +12,7 @@ settings: geomFile: zpprTestGeom.xml loadingFile: 1DslabXSByCompTest.yaml mpiTasksPerNode: 6 - numProcessors: 12 + nTasks: 12 outputFileExtension: pdf power: 75000000.0 sortReactor: false # zpprs dont sor the right way. need better component sorting for slab... diff --git a/armi/utils/__init__.py b/armi/utils/__init__.py index f5943d02a..ae9c68443 100644 --- a/armi/utils/__init__.py +++ b/armi/utils/__init__.py @@ -24,7 +24,6 @@ import shutil import sys import tempfile -import threading import time from armi import runLog @@ -38,37 +37,33 @@ def getFileSHA1Hash(filePath, digits=40): """ - Generate a SHA-1 hash of the input file. + Generate a SHA-1 hash of input files. Parameters ---------- filePath : str - Path to file to obtain the SHA-1 hash + Path to file or directory to obtain the SHA-1 hash digits : int, optional Number of digits to include in the hash (40 digit maximum for SHA-1) """ sha1 = hashlib.sha1() - with open(filePath, "rb") as f: - while True: - data = f.read(_HASH_BUFFER_SIZE) - if not data: - break - sha1.update(data) - - return sha1.hexdigest()[:digits] + filesToHash = [] + if os.path.isdir(filePath): + for root, _, files in os.walk(filePath): + for file in sorted(files): + filesToHash.append(os.path.join(root, file)) + else: + filesToHash.append(filePath) + for file in filesToHash: + with open(file, "rb") as f: + while True: + data = f.read(_HASH_BUFFER_SIZE) + if not data: + break + sha1.update(data) -def copyWithoutBlocking(src, dest): - """ - Copy a file in a separate thread to avoid blocking while IO completes. - - Useful for copying large files while ARMI moves along. - """ - files = "{} to {}".format(src, dest) - runLog.extra("Copying (without blocking) {}".format(files)) - t = threading.Thread(target=shutil.copy, args=(src, dest)) - t.start() - return t + return sha1.hexdigest()[:digits] def getPowerFractions(cs): diff --git a/armi/utils/flags.py b/armi/utils/flags.py index 1dc710e9e..f0b83c271 100644 --- a/armi/utils/flags.py +++ b/armi/utils/flags.py @@ -91,6 +91,7 @@ def __new__(cls, name, bases, attrs): # Auto fields have been resolved, so now collect all ints allFields = {name: val for name, val in attrs.items() if isinstance(val, int)} + allFields = {n: v for n, v in allFields.items() if not _FlagMeta.isdunder(n)} flagClass._nameToValue = allFields flagClass._valuesTaken = set(val for _, val in allFields.items()) flagClass._autoAt = autoAt @@ -104,6 +105,10 @@ def __new__(cls, name, bases, attrs): return flagClass + @staticmethod + def isdunder(s): + return s.startswith("__") and s.endswith("__") + def __getitem__(cls, key): """ Implement indexing at the class level. diff --git a/armi/utils/outputCache.py b/armi/utils/outputCache.py index b54cd1746..e9f4b0ebc 100644 --- a/armi/utils/outputCache.py +++ b/armi/utils/outputCache.py @@ -175,10 +175,10 @@ def deleteCache(cachedFolder): """ Remove this folder. - Requires safeword because this is potentially extremely destructive. + Requires keyword because this is potentially extremely destructive. """ - if "Output_Cache" not in cachedFolder: - raise RuntimeError("Cache location must contain safeword: `Output_Cache`.") + if "cache" not in str(cachedFolder).lower(): + raise RuntimeError("Cache location must contain keyword: `cache`.") cleanPath(cachedFolder) diff --git a/armi/utils/pathTools.py b/armi/utils/pathTools.py index 5eb9d97a9..d80354a09 100644 --- a/armi/utils/pathTools.py +++ b/armi/utils/pathTools.py @@ -41,29 +41,34 @@ def armiAbsPath(*pathParts): return os.path.abspath(os.path.join(*pathParts)) -def copyOrWarn(fileDescription, sourcePath, destinationPath): - """Copy a file, or warn if the file doesn't exist. +def copyOrWarn(filepathDescription, sourcePath, destinationPath): + """Copy a file or directory, or warn if the filepath doesn't exist. Parameters ---------- - fileDescription : str + filepathDescription : str a description of the file and/or operation being performed. sourcePath : str - Path of the file to be copied. + Filepath to be copied. destinationPath : str - Path for the copied file. + Copied filepath. """ try: - shutil.copy(sourcePath, destinationPath) + if os.path.isdir(sourcePath): + shutil.copytree(sourcePath, destinationPath, dirs_exist_ok=True) + else: + shutil.copy(sourcePath, destinationPath) runLog.debug( - "Copied {}: {} -> {}".format(fileDescription, sourcePath, destinationPath) + "Copied {}: {} -> {}".format( + filepathDescription, sourcePath, destinationPath + ) ) except shutil.SameFileError: pass except Exception as e: runLog.warning( "Could not copy {} from {} to {}\nError was: {}".format( - fileDescription, sourcePath, destinationPath, e + filepathDescription, sourcePath, destinationPath, e ) ) diff --git a/armi/utils/tests/test_pathTools.py b/armi/utils/tests/test_pathTools.py index 47c4a8b87..8ac3affe0 100644 --- a/armi/utils/tests/test_pathTools.py +++ b/armi/utils/tests/test_pathTools.py @@ -19,6 +19,7 @@ import unittest from armi import context +from armi.tests import mockRunLogs from armi.utils import pathTools from armi.utils.directoryChangers import TemporaryDirectoryChanger @@ -26,6 +27,39 @@ class PathToolsTests(unittest.TestCase): + def test_copyOrWarnFile(self): + with TemporaryDirectoryChanger(): + # Test a successful copy + path = "test.txt" + pathCopy = "testcopy.txt" + with open(path, "w") as f1: + f1.write("test") + pathTools.copyOrWarn("Test File", path, pathCopy) + self.assertTrue(os.path.exists(pathCopy)) + + # Test a non-existant file + with mockRunLogs.BufferLog() as mock: + pathTools.copyOrWarn("Test File", "FileDoesntExist.txt", pathCopy) + self.assertIn("Could not copy", mock.getStdout()) + + def test_copyOrWarnDir(self): + with TemporaryDirectoryChanger(): + # Test a successful copy + pathDir = "testDir" + path = os.path.join(pathDir, "test.txt") + pathDirCopy = "testcopy" + os.mkdir(pathDir) + with open(path, "w") as f1: + f1.write("test") + pathTools.copyOrWarn("Test File", pathDir, pathDirCopy) + self.assertTrue(os.path.exists(pathDirCopy)) + self.assertTrue(os.path.exists(os.path.join(pathDirCopy, "test.txt"))) + + # Test a non-existant file + with mockRunLogs.BufferLog() as mock: + pathTools.copyOrWarn("Test File", "DirDoesntExist", pathDirCopy) + self.assertIn("Could not copy", mock.getStdout()) + def test_separateModuleAndAttribute(self): self.assertRaises( ValueError, pathTools.separateModuleAndAttribute, r"path/with/no/colon" diff --git a/armi/utils/tests/test_textProcessors.py b/armi/utils/tests/test_textProcessors.py index a7a56bcdc..1d15afcc6 100644 --- a/armi/utils/tests/test_textProcessors.py +++ b/armi/utils/tests/test_textProcessors.py @@ -13,17 +13,41 @@ # limitations under the License. """Tests for functions in textProcessors.py.""" from io import StringIO +import logging import os import pathlib import ruamel import unittest +from armi import runLog +from armi.tests import mockRunLogs +from armi.tests import TEST_ROOT from armi.utils import textProcessors +from armi.utils.directoryChangers import TemporaryDirectoryChanger THIS_DIR = os.path.dirname(__file__) RES_DIR = os.path.join(THIS_DIR, "resources") +class TestTextProcessor(unittest.TestCase): + """Test Text processor.""" + + def setUp(self): + self.tp = textProcessors.TextProcessor(os.path.join(TEST_ROOT, "geom.xml")) + + def test_fsearch(self): + """Test fsearch in re mode.""" + line = self.tp.fsearch("xml") + self.assertIn("version", line) + self.assertEqual(self.tp.fsearch("xml"), "") + + def test_fsearchText(self): + """Test fsearch in text mode.""" + line = self.tp.fsearch("xml", textFlag=True) + self.assertIn("version", line) + self.assertEqual(self.tp.fsearch("xml"), "") + + class YamlIncludeTest(unittest.TestCase): def test_resolveIncludes(self): with open(os.path.join(RES_DIR, "root.yaml")) as f: @@ -38,8 +62,7 @@ def test_resolveIncludes(self): anyIncludes = True self.assertFalse(anyIncludes) - # Re-parse the resolved stream, make sure that we included the stuff that we - # want + # Re-parse the resolved stream, make sure that we included the stuff that we want resolved.seek(0) data = ruamel.yaml.YAML().load(resolved) self.assertEqual(data["billy"]["children"][1]["full_name"], "Jennifer Person") @@ -99,15 +122,21 @@ class SequentialReaderTests(unittest.TestCase): _DUMMY_FILE_NAME = "DUMMY.txt" - @classmethod - def setUpClass(cls): - with open(cls._DUMMY_FILE_NAME, "w") as f: - f.write(cls.textStream) + def setUp(self): + self.td = TemporaryDirectoryChanger() + self.td.__enter__() + + with open(self._DUMMY_FILE_NAME, "w") as f: + f.write(self.textStream) - @classmethod - def tearDownClass(cls): - if os.path.exists(cls._DUMMY_FILE_NAME): - os.remove(cls._DUMMY_FILE_NAME) + def tearDown(self): + if os.path.exists(self._DUMMY_FILE_NAME): + try: + os.remove(self._DUMMY_FILE_NAME) + except OSError: + pass + + self.td.__exit__(None, None, None) def test_readFile(self): with textProcessors.SequentialReader(self._DUMMY_FILE_NAME) as sr: @@ -118,3 +147,32 @@ def test_readFileWithPattern(self): with textProcessors.SequentialReader(self._DUMMY_FILE_NAME) as sr: self.assertTrue(sr.searchForPattern("(X\s+Y\s+\d+\.\d+)")) self.assertEqual(float(sr.line.split()[2]), 3.5) + + def test_issueWarningOnFindingText(self): + with textProcessors.SequentialReader(self._DUMMY_FILE_NAME) as sr: + warningMsg = "Oh no" + sr.issueWarningOnFindingText("example test stream", warningMsg) + + with mockRunLogs.BufferLog() as mock: + runLog.LOG.startLog("test_issueWarningOnFindingText") + runLog.LOG.setVerbosity(logging.WARNING) + self.assertEqual("", mock.getStdout()) + self.assertTrue(sr.searchForPattern("example test stream")) + self.assertIn(warningMsg, mock.getStdout()) + + self.assertFalse(sr.searchForPattern("Killer Tomatoes")) + + def test_raiseErrorOnFindingText(self): + with textProcessors.SequentialReader(self._DUMMY_FILE_NAME) as sr: + sr.raiseErrorOnFindingText("example test stream", IOError) + + with self.assertRaises(IOError): + self.assertTrue(sr.searchForPattern("example test stream")) + + def test_consumeLine(self): + with textProcessors.SequentialReader(self._DUMMY_FILE_NAME) as sr: + sr.line = "hi" + sr.match = 1 + sr.consumeLine() + self.assertEqual(len(sr.line), 0) + self.assertIsNone(sr.match) diff --git a/armi/utils/tests/test_utils.py b/armi/utils/tests/test_utils.py index 826a155ef..f0e0b6500 100644 --- a/armi/utils/tests/test_utils.py +++ b/armi/utils/tests/test_utils.py @@ -24,26 +24,47 @@ from armi.settings.caseSettings import Settings from armi.tests import mockRunLogs from armi.utils import ( + codeTiming, directoryChangers, - getPowerFractions, - getCycleNames, getAvailabilityFactors, - getStepLengths, - getCycleLengths, getBurnSteps, + getCumulativeNodeNum, + getCycleLengths, + getCycleNames, + getCycleNodeFromCumulativeNode, + getCycleNodeFromCumulativeStep, + getFileSHA1Hash, getMaxBurnSteps, getNodesPerCycle, - getCycleNodeFromCumulativeStep, - getCycleNodeFromCumulativeNode, + getPowerFractions, getPreviousTimeNode, - getCumulativeNodeNum, + getStepLengths, hasBurnup, - codeTiming, safeCopy, ) class TestGeneralUtils(unittest.TestCase): + def test_getFileSHA1Hash(self): + with directoryChangers.TemporaryDirectoryChanger(): + path = "test.txt" + with open(path, "w") as f1: + f1.write("test") + sha = getFileSHA1Hash(path) + self.assertIn("a94a8", sha) + + def test_getFileSHA1HashDir(self): + with directoryChangers.TemporaryDirectoryChanger(): + pathDir = "testDir" + path1 = os.path.join(pathDir, "test1.txt") + path2 = os.path.join(pathDir, "test2.txt") + os.mkdir(pathDir) + for i, path in enumerate([path1, path2]): + with open(path, "w") as f1: + f1.write(f"test{i}") + sha = getFileSHA1Hash(pathDir) + self.assertIn("ccd13", sha) + def test_mergeableDictionary(self): mergeableDict = utils.MergeableDict() normalDict = {"luna": "thehusky", "isbegging": "fortreats", "right": "now"} diff --git a/armi/utils/textProcessors.py b/armi/utils/textProcessors.py index fcb6e1431..db9868316 100644 --- a/armi/utils/textProcessors.py +++ b/armi/utils/textProcessors.py @@ -304,7 +304,6 @@ def issueWarningOnFindingText(self, text, warning): ---------- text : str text to find within the file - warning : str An warning message to issue. @@ -316,7 +315,8 @@ def issueWarningOnFindingText(self, text, warning): self._textWarnings.append((text, warning)) def raiseErrorOnFindingText(self, text, error): - """Add a text search for every line of the file, if the text is found the specified error will be raised. + """Add a text search for every line of the file, if the text is found the specified error + will be raised. This is important for determining if errors occurred while searching for text. @@ -335,7 +335,8 @@ def raiseErrorOnFindingText(self, text, error): self._textErrors.append((text, error)) def raiseErrorOnFindingPattern(self, pattern, error): - """Add a pattern search for every line of the file, if the pattern is found the specified error will be raised. + """Add a pattern search for every line of the file, if the pattern is found the specified + error will be raised. This is important for determining if errors occurred while searching for text. @@ -546,12 +547,7 @@ def __init__(self, fname, highMem=False): # need this not to fail for detecting when RXSUM doesn't exist, etc. # note: Could make it check before instantiating... raise FileNotFoundError(f"{fname} does not exist.") - if not highMem: - # keep the file on disk, read as necessary - self.f = f - else: - # read all of f into memory and set up a list that remembers where it is. - self.f = SmartList(f) + self.f = f def reset(self): """Rewinds the file so you can search through it again.""" @@ -616,42 +612,3 @@ def fsearch(self, pattern, msg=None, killOn=None, textFlag=False): result = "" return result - - -class SmartList: - """A list that does stuff like files do i.e. remembers where it was, can seek, etc. - Actually this is pretty slow. so much for being smart. nice idea though. - """ - - def __init__(self, f): - self.lines = f.readlines() - self.position = 0 - self.name = f.name - self.length = len(self.lines) - - def __getitem__(self, index): - return self.lines[index] - - def __setitem__(self, index, line): - self.lines[index] = line - - def next(self): - if self.position >= self.length: - self.position = 0 - raise StopIteration - else: - c = self.position - self.position += 1 - return self.lines[c] - - def __iter__(self): - return self - - def __len__(self): - return len(self.lines) - - def seek(self, line): - self.position = line - - def close(self): - pass diff --git a/armi/utils/units.py b/armi/utils/units.py index 012654588..defa61110 100644 --- a/armi/utils/units.py +++ b/armi/utils/units.py @@ -105,6 +105,7 @@ ASCII_MIN_CHAR = 44 # First char allowed in various FORTRAN inputs ASCII_LETTER_A = 65 ASCII_LETTER_Z = 90 +ASCII_LETTER_a = 97 ASCII_ZERO = 48 TRACE_NUMBER_DENSITY = 1e-50 MIN_FUEL_HM_MOLES_PER_CC = 1e-10 diff --git a/doc/developer/guide.rst b/doc/developer/guide.rst index 86e434bf6..343200f13 100644 --- a/doc/developer/guide.rst +++ b/doc/developer/guide.rst @@ -2,46 +2,41 @@ Framework Architecture ********************** -Here we will discuss some big-picture elements of the ARMI architecture. Throughout, -links to the API docs will lead to additional details. - -The Reactor Model -================= - -The :py:mod:`~armi.reactor` package is the central representation of a nuclear reactor -in ARMI. All modules can be expected to want access to some element of the state data -in a run, and should be enabled to find the data present somewhere in the ``reactor`` -package's code during runtime. - -An approximation of `Composite Design Pattern -`_ is used to represent the **Reactor** -in ARMI. In this hierarchy the **Reactor** object has a child **Core** object, and -potentially many generic **Composite** child objects representing ex-core structures. -The **Core** is made of **Assembly** objects, which are in turn made up as a collection -of **Block** objects. :term:`State ` variables may be stored at any level -of this hierarchy using the :py:mod:`armi.reactor.parameters` system to contain results -(e.g., ``keff``, ``flow rates``, ``power``, ``flux``, etc.). Within each block are -**Components** that define the pin-level geometry. Associated with each Component are -**Material** objects that contain material properties (``density``, ``conductivity``, -``heat capacity``, etc.) and isotopic mass fractions. - -.. note:: Non-core structures (spent fuel pools, core restraint, heat exchangers, etc.) - may be represented analogously to the **Core**, but this feature is new and under - development. Historically, the **Core** and **Reactor** were the same thing, and some - information in the documentation still reflects this. +What follows is a discussion of the high-level elements of the ARMI framework. Throughout, links to +the API docs will be provided for additional details. + +The Reactor Data Model +====================== + +The ARMI framework represents a nuclear reactor via a reactor data model, which is defined in the +:py:mod:`~armi.reactor` package. Each physical piece of the nuclear reactor is defined by a Python +object, called an :py:class:`ArmiObject `. Each ``ArmiObject`` +has associated data like: shape, material, or other physical values. The physical values can be +nearly anything, and are attached to the data model via ARMI's +:py:mod:`Parameter ` system. Example parameters might be: ``keff``, +``flow rates``, ``power``, ``flux``, etc. + +The reactor data model is a hierarchical model, following the `Composite Design Pattern +`_. The top of the data model is the +:py:class:`Reactor `, which contains one +:py:class:`Core ` object and a collection of zero or more +:py:class:`ExcoreStructures `. An example +``ExcoreStructure`` might be a :py:class:`SpentFuelPool `. + +For now, the ``Core`` object in ARMI assumes it contains **Assembly** objects, which are in turn +made up as a collection of **Block** objects. The leaves of the the Composite Model in the ARMI +framework are called :py:class:`Component `. .. figure:: /.static/armi_reactor_objects.png :align: center The primary data containers in ARMI -Each level of the composite pattern hierarchy contains most of its state data in a -collection of parameters detailing considerations of how the reactor has progressed -through time to any given point. This information also constitutes the majority of what -gets written to the database for evaluation and/or follow-on analysis. +Time-evolving the parameters on the reactor composite hierarchy is what most modelers and analysts +will want from the ARMI framework. -Review the data model :ref:`armi-tutorials` section for examples -exploring a populated instance of the **Reactor** model. +Review the data model :ref:`armi-tutorials` section for examples exploring a populated instance of +the ``Reactor`` data model. Finding objects in a model -------------------------- @@ -101,8 +96,7 @@ various physics operations. For example, some lattice physics routines convert the full core to a 2D R-Z model and compute flux with thousands of energy groups to properly capture the spectral-spatial -coupling in a core/reflector interface. The converters are used heavily in these -operations. +coupling in a core/reflector interface. The converters are used heavily in these operations. Blueprints ---------- @@ -160,7 +154,7 @@ configuration (such as Fuel management, and depletion) are disabled. The Interface Stack ------------------- *Interfaces* (:py:class:`armi.interfaces.Interface`) operate upon the Reactor Model to -do analysis. They're designed to allow expansion of the code in a natural and +do analysis. They're designed to allow expansion of the code in a natural and well-organized manner. Interfaces are useful to link external codes to ARMI as well for adding new internal physics into the rest of the system. As a result, very many aspects of ARMI are contained within interfaces. @@ -235,7 +229,7 @@ hooks include: after every node step/flux calculation, if tight physics coupling is active. These interaction points are optional in every interface, and you may override one or -more of them to suit your needs. You should not change the arguments to the hooks, +more of them to suit your needs. You should not change the arguments to the hooks, which are integers. Each interface has a ``enabled`` flag. If this is set to ``False``, then the interface's @@ -252,7 +246,6 @@ When using the Operators that come with ARMI, Interfaces are discovered using th :py:meth:`createInterfaces ` method. - How interfaces get called ------------------------- @@ -269,9 +262,9 @@ To use interfaces in parallel, please refer to :py:mod:`armi.mpiActions`. Plugins ======= -Plugins are higher-level objects that can bring in one or more Interfaces, settings -definitions, parameters, validations, etc. They are documented in -:ref:`armi-app-making` and :py:mod:`armi.plugins`. +Plugins are higher-level objects that can add things to the simulations like Interfaces, settings +definitions, parameters, validations, etc. They are documented in :ref:`armi-app-making` and +:py:mod:`armi.plugins`. Entry Points @@ -282,9 +275,6 @@ invoke ARMI with ``python -m armi run``, the ``__main__.py`` file is loaded and valid Entry Points are dynamically loaded. The proper entry point (in this case, :py:class:`armi.cli.run.RunEntryPoint`) is invoked. As ARMI initializes itself, settings are loaded into a :py:class:`Settings ` -object. From those settings, an :py:class:`Operator ` +object. From those settings, an :py:class:`Operator ` subclass is built by a factory and its ``operate`` method is called. This fires up the -main ARMI analysis loop and its interface stack is looped over as indicated by user -input. - - +main ARMI analysis loop and its interface stack is looped over as indicated by user input. diff --git a/doc/release/0.4.rst b/doc/release/0.4.rst index c02e604c6..413558d6b 100644 --- a/doc/release/0.4.rst +++ b/doc/release/0.4.rst @@ -2,85 +2,6 @@ ARMI v0.4 Release Notes *********************** -ARMI v0.4.1 -=========== -Release Date: TBD - -New Features ------------- -#. Adding data models for ex-core structures in ARMI. (`PR#1891 `_) -#. ARMI now supports Python 3.12. (`PR#1813 `_) -#. Removing the ``tabulate`` dependency by ingesting it to ``armi.utils.tabulate``. (`PR#1811 `_) -#. Adding ``--skip-inspection`` flag to ``CompareCases`` CLI. (`PR#1842 `_) -#. Allow merging a component with zero area into another component. (`PR#1858 `_) -#. Use ``Block.getNumPins()`` in ``HexBlock._rotatePins()``. (`PR#1859 `_) -#. Provide utilities for determining location of a rotated object in a hexagonal lattice (``getIndexOfRotatedCell``). (`PR#1846 `_) -#. Allow merging a component with zero area into another component. (`PR#1858 `_) -#. Provide ``Parameter.hasCategory`` for quickly checking if a parameter is defined with a given category. (`PR#1899 `_) -#. Provide ``ParameterCollection.where`` for efficient iteration over parameters who's definition matches a given condition. (`PR#1899 `_) -#. Flags can now be defined with letters and numbers. (`PR#1966 `_) -#. Plugins can provide the ``getAxialExpansionChanger`` hook to customize axial expansion. (`PR#1870 `_) -#. ``HexBlock.rotate`` updates the spatial locator for children of that block. (`PR#1943 `_) -#. New plugin hook ``beforeReactorConstruction`` added to enable plugins to process case settings before reactor init. (`PR#1945 `_) -#. Provide ``Block.getInputHeight`` for determining the height of a block from blueprints. (`PR#1927 `_) -#. Improve performance by changing the lattice physics interface so that cross sections are not updated on ``everyNode`` calls during coupled calculations (`PR#1963 `_) -#. Improve efficiency of reaction rate calculations. (`PR#1887 `_) -#. Adding new options for simplifying 1D cross section modeling. (`PR#1949 `_) -#. TBD - -API Changes ------------ -#. Removing flags ``CORE`` and ``REACTOR``. (`PR#1835 `_) -#. Alphabetizing ``Flags.toString()`` results. (`PR#1912 `_) -#. Moving ``settingsValidation`` from ``operators`` to ``settings``. (`PR#1895 `_) -#. Removing deprecated method ``prepSearch``. (`PR#1845 `_) -#. Removing unused function ``SkippingXsGen_BuChangedLessThanTolerance``. (`PR#1845 `_) -#. Renaming ``Reactor.moveList`` to ``Reactor.moves``. (`PR#1881 `_) -#. ``copyInterfaceInputs`` no longer requires a valid setting object. (`PR#1934 `_) -#. Removing ``buildEqRingSchedule``. (`PR#1928 `_) -#. Removing broken plot ``buVsTime``. (`PR#1994 `_) -#. Allowing for unknown Flags when opening a DB. (`PR#1844 `_) -#. Removing ``Assembly.doubleResolution()``. (`PR#1951 `_) -#. Removing ``assemblyLists.py`` and the ``AssemblyList`` class. (`PR#1891 `_) -#. Removing ``Assembly.rotatePins`` and ``Block.rotatePins``. Prefer ``Assembly.rotate`` and ``Block.rotate``. (`PR#1846 `_) -#. Removing unused setting ``autoGenerateBlockGrids``. (`PR#1947 `_) -#. Transposing ``pinMgFluxes`` parameters so that leading dimension is pin index. (`PR#1937 `_) -#. ``Block.getPinCoordinates`` returns an ``(N, 3)`` array, rather than a length ``N`` list of three-length arrays. (`PR#1943 `_) -#. Removing ``globalFluxInterface.DoseResultsMapper`` class. (`PR#1952 `_) -#. Removing setting ``mpiTasksPerNode`` and renaming ``numProcessors`` to ``nTasks``. (`PR#1958 `_) -#. Changing ``synDbAfterWrite`` default to ``True``. (`PR#1968 `_) -#. TBD - -Bug Fixes ---------- -#. Fixed spatial grids of pins in Blocks on flats-up grids. (`PR#1947 `_) -#. Fixed ``DerivedShape.getArea`` for ``cold=True``. (`PR#1831 `_) -#. Fixed error parsing command line integers in ``ReportsEntryPoint``. (`PR#1824 `_) -#. Fixed ``PermissionError`` when using ``syncDbAfterWrite``. (`PR#1857 `_) -#. Fixed ``MpiDirectoryChanger``. (`PR#1853 `_) -#. Changed data type of ``thKernel`` setting from ``bool`` to ``str`` in ``ThermalHydraulicsPlugin``. (`PR#1855 `_) -#. Update height of fluid components after axial expansion. (`PR#1828 `_) -#. Rotate hexagonal assembly patches correctly on facemap plots. (`PR#1883 `_) -#. Material theoretical density is serialized to and read from database. (`PR#1852 `_) -#. Removed broken and unused column in ``summarizeMaterialData``. (`PR#1925 `_) -#. Fixed hex block rotation in ``plotBlockDiagram``. (`PR#1926 `_) -#. Fixed edge case in ``assemblyBlueprint._checkParamConsistency()``. (`PR#1928 `_) -#. Fixed wetted perimeter for hex inner ducts. (`PR#1985 `_) -#. TBD - -Quality Work ------------- -#. Removing deprecated code ``axialUnitGrid``. (`PR#1809 `_) -#. Refactoring ``axialExpansionChanger``. (`PR#1861 `_) -#. Raising a ValueError when database load fails. (`PR#1940 `_) -#. Changes to make axial expansion related classes more extensible. (`PR#1920 `_) -#. TBD - -Changes that Affect Requirements --------------------------------- -#. TBD - - ARMI v0.4.0 =========== Release Date: 2024-07-29 @@ -94,6 +15,8 @@ New Features (`PR#1729 `_ and `PR#1750 `_) #. Density can be specified for components via ``custom isotopics`` in the blueprints. (`PR#1745 `_) #. Implement a new ``JaggedArray`` class that handles HDF5 interface for jagged data. (`PR#1726 `_) +#. Adding temperature dependent representative blocks to cross section group manager. (`PR#1987 `_) + API Changes ----------- @@ -133,6 +56,7 @@ API Changes #. Changing the Doppler constant params to ``VOLUME_INTEGRATED``. (`PR#1659 `_) #. Change ``Operator._expandCycleAndTimeNodeArgs`` to be a non-static method. (`PR#1766 `_) #. Database now writes state at the last time node of a cycle rather than during the ``DatabaseInterface.interactEOC`` interaction. (`PR#1090 `_) +#. Renaming ``b.p.buGroup`` to ``b.p.envGroup``. Environment group captures both burnup and temperature. (`PR#1987 `_) Bug Fixes --------- @@ -152,6 +76,6 @@ Quality Work Changes that Affect Requirements -------------------------------- -#. Very minor change to ``Block.coords()``, removing unused argument. (`PR#1651 `_) +#. Removing unused argument to ``Block.coords()``. (`PR#1651 `_) #. Touched ``HexGrid`` by adding a "cornersUp" property and fixing two bugs. (`PR#1649 `_) #. Very slightly modified the implementation of ``Assembly.add()``. (`PR#1670 `_) diff --git a/doc/release/0.5.rst b/doc/release/0.5.rst new file mode 100644 index 000000000..6ebbc39ca --- /dev/null +++ b/doc/release/0.5.rst @@ -0,0 +1,100 @@ +*********************** +ARMI v0.5 Release Notes +*********************** + +ARMI v0.5.1 +=========== +Release Date: TBD + +New Features +------------ +#. TBD + +API Changes +----------- +#. TBD + +Bug Fixes +--------- +#. TBD + +Quality Work +------------ +#. TBD + + +ARMI v0.5.0 +=========== +Release Date: 2024-12-14 + +New Features +------------ +#. Supporting Python 3.12. (`PR#1813 `_) +#. Supporting Python 3.13. (`PR#1996 `_) +#. Adding data models for ex-core structures in ARMI. (`PR#1891 `_) +#. Opening some DBs without the ``App`` that created them. (`PR#1917 `_) +#. Adding support for ENDF/B-VII.1-based MC2-3 libraries. (`PR#1982 `_) +#. Removing the ``tabulate`` dependency by ingesting it to ``armi.utils.tabulate``. (`PR#1811 `_) +#. ``HexBlock.rotate`` updates the spatial locator for children of that block. (`PR#1943 `_) +#. Provide ``Block.getInputHeight`` for determining the height of a block from blueprints. (`PR#1927 `_) +#. Provide ``Parameter.hasCategory`` for quickly checking if a parameter is defined with a given category. (`PR#1899 `_) +#. Provide ``ParameterCollection.where`` for efficient iteration over parameters who's definition matches a given condition. (`PR#1899 `_) +#. Flags can now be defined with letters and numbers. (`PR#1966 `_) +#. Provide utilities for determining location of a rotated object in a hexagonal lattice (``getIndexOfRotatedCell``). (`PR#1846 `_) +#. Allow merging a component with zero area into another component. (`PR#1858 `_) +#. New plugin hook ``getAxialExpansionChanger`` to customize axial expansion. (`PR#1870 `_) +#. New plugin hook ``beforeReactorConstruction`` to process settings before reactor init. (`PR#1945 `_) +#. Improving performance in the lattice physics interface by not updating cross sections at ``everyNode`` during coupled calculations. (`PR#1963 `_) +#. Allow merging a component with zero area into another component. (`PR#1858 `_) +#. Updating ``copyOrWarn`` and ``getFileSHA1Hash`` to support directories. (`PR#1984 `_) +#. Improve efficiency of reaction rate calculations. (`PR#1887 `_) +#. Adding new options for simplifying 1D cross section modeling. (`PR#1949 `_) +#. Adding ``--skip-inspection`` flag to ``CompareCases`` CLI. (`PR#1842 `_) +#. Exposing ``detailedNDens`` to components. (`PR#1954 `_) + +API Changes +----------- +#. ``nuclideBases.byMcc3ID`` and ``getMcc3Id()`` return IDs consistent with ENDF/B-VII.1. (`PR#1982 `_) +#. Moving ``settingsValidation`` from ``operators`` to ``settings``. (`PR#1895 `_) +#. Allowing for unknown Flags when opening a DB. (`PR#1844 `_) +#. Renaming ``Reactor.moveList`` to ``Reactor.moves``. (`PR#1881 `_) +#. Transposing ``pinMgFluxes`` parameters so that leading dimension is pin index. (`PR#1937 `_) +#. ``Block.getPinCoordinates`` returns an ``(N, 3)`` array, rather than a list of arrays. (`PR#1943 `_) +#. Alphabetizing ``Flags.toString()`` results. (`PR#1912 `_) +#. ``copyInterfaceInputs`` no longer requires a valid setting object. (`PR#1934 `_) +#. Changing ``synDbAfterWrite`` default to ``True``. (`PR#1968 `_) +#. Removing ``Assembly.rotatePins`` and ``Block.rotatePins``. Prefer ``Assembly.rotate`` and ``Block.rotate``. (`PR#1846 `_) +#. Removing broken plot ``buVsTime``. (`PR#1994 `_) +#. Removing class ``AssemblyList`` and ``assemblyLists.py``. (`PR#1891 `_) +#. Removing class ``globalFluxInterface.DoseResultsMapper``. (`PR#1952 `_) +#. Removing class ``SmartList``. (`PR#1992 `_) +#. Removing flags ``CORE`` and ``REACTOR``. (`PR#1835 `_) +#. Removing method ``Assembly.doubleResolution()``. (`PR#1951 `_) +#. Removing method ``buildEqRingSchedule``. (`PR#1928 `_) +#. Removing method ``prepSearch``. (`PR#1845 `_) +#. Removing method ``SkippingXsGen_BuChangedLessThanTolerance``. (`PR#1845 `_) +#. Removing setting ``autoGenerateBlockGrids``. (`PR#1947 `_) +#. Removing setting ``mpiTasksPerNode`` and renaming ``numProcessors`` to ``nTasks``. (`PR#1958 `_) + +Bug Fixes +--------- +#. Fixed spatial grids of pins in Blocks on flats-up grids. (`PR#1947 `_) +#. Fixed ``DerivedShape.getArea`` for ``cold=True``. (`PR#1831 `_) +#. Fixed error parsing command line integers in ``ReportsEntryPoint``. (`PR#1824 `_) +#. Fixed ``PermissionError`` when using ``syncDbAfterWrite``. (`PR#1857 `_) +#. Fixed ``MpiDirectoryChanger``. (`PR#1853 `_) +#. Changed data type of ``thKernel`` setting from ``bool`` to ``str`` in ``ThermalHydraulicsPlugin``. (`PR#1855 `_) +#. Update height of fluid components after axial expansion. (`PR#1828 `_) +#. Rotate hexagonal assembly patches correctly on facemap plots. (`PR#1883 `_) +#. Material theoretical density is serialized to and read from database. (`PR#1852 `_) +#. Removed broken and unused column in ``summarizeMaterialData``. (`PR#1925 `_) +#. Fixed hex block rotation in ``plotBlockDiagram``. (`PR#1926 `_) +#. Fixed edge case in ``assemblyBlueprint._checkParamConsistency()``. (`PR#1928 `_) +#. Fixed wetted perimeter for hex inner ducts. (`PR#1985 `_) + +Quality Work +------------ +#. Removing deprecated code ``axialUnitGrid``. (`PR#1809 `_) +#. Refactoring ``axialExpansionChanger``. (`PR#1861 `_) +#. Raising a ``ValueError`` when ``Database.load()`` fails. (`PR#1940 `_) +#. Making axial expansion-related classes more extensible. (`PR#1920 `_) diff --git a/doc/user/outputs.rst b/doc/user/outputs.rst index 71152caad..db152f9b4 100644 --- a/doc/user/outputs.rst +++ b/doc/user/outputs.rst @@ -2,8 +2,8 @@ Outputs ******* -ARMI output files are described in this section. Many outputs may be generated during an ARMI run. They fall into -various categories: +ARMI output files are described in this section. Many outputs may be generated during an ARMI run. +They fall into various categories: Framework outputs Files like the **stdout** and the **database** are produced in nearly all runs. @@ -15,10 +15,10 @@ Physics kernel outputs If ARMI executes an external physics kernel during a run, its associated output files are often available in the working directory. These files are typically read by ARMI during the run, and relevant data is transferred onto the reactor model (and ends up in the ARMI **database**). If the user desires to retain all of the inputs and outputs - associated with the physics kernel runs for a given time step, this can be specified with the ``savePhysicsIO`` setting. - For any time step specified in the list under ``savePhysicsIO``, a ``cXnY/`` folder will be created, and ARMI will store all - inputs and outputs associated with each physics kernel executed at this time step in a folder inside of ``cXnY/``. - The format for specifying a state point is 00X00Y for cycle X, step Y. + associated with the physics kernel runs for a given time step, this can be specified with the ``savePhysicsIO`` + setting. For any time step specified in the list under ``savePhysicsIO``, a ``cXnY/`` folder will be created, and + ARMI will store all inputs and outputs associated with each physics kernel executed at this time step in a folder + inside of ``cXnY/``. The format for specifying a state point is 00X00Y for cycle X, step Y. Together the output fully define the analyzed ARMI case. @@ -68,28 +68,26 @@ This provides live information on the progress. The Database File ================= -The **database** file is a self-contained complete (or nearly complete) binary -representation of the ARMI composite model state during a case. The database contains -the text of the input files that were used to create the case, and for each time node, -the values of all composite parameters as well as layout information to help fully -reconstruct the structure of the reactor model. +The **database** file is a self-contained, binary representation of the state of the ARMI composite +model state during a simulation. The database contains full, plain-text of the input files that were +used to create the case. And for each time node, the values of all composite parameters as well as +layout information to help fully reconstruct the reactor data model. Loading Reactor State --------------------- -Among other things, the database file can be used to recover an ARMI reactor model from -any of the time nodes that it contains. This can be useful for performing restart runs, -or for doing custom post-processing tasks. To load a reactor state, you will need to -open the database file into a ``Database`` object. From there, you can call the -:py:meth:`armi.bookkeeping.db.Database.load()` method to get a recovered -reactor object. For instance, given a database file called ``myDatabase.h5``, we could -load the reactor state at cycle 5, time node 2 with the following:: +Among other things, the database file can be used to recover an ARMI reactor model from any of the +time nodes that it contains. This can be useful for performing restart runs, or for doing custom +post-processing analysis. To load a reactor state, you will need to open the database file into a +``Database`` object. From there, you can call the :py:meth:`armi.bookkeeping.db.Database.load()` +method to get a recovered ``Reactor`` object. For instance, given a database file called +``myDatabase.h5``, we could load the reactor state at cycle 5, time node 2 with the following:: from armi.bookkeeping.db import databaseFactory db = databaseFactory("myDatabase.h5", "r") - # The underlying file is not left open when we can help it. Use the handy context - # manager to temporarily open the file and interact with the data: + # The underlying file is not left open unless necessary. Use the handy context manager to + # temporarily open the file and interact with the data: with db: r = db.load(5, 2) @@ -127,28 +125,22 @@ available for viewing, editing, and scripting HDF5 files. The ARMI database uses `h5py` package for interacting with the underlying data and metadata. At a high level there are 3 things to know about HDF5: -1. Groups - groups are named collections of datasets. You might think of a group as a - filesystem folder. -2. Datasets - Datasets are named values. If a group is a folder, a dataset - is a file. Values are - strongly typed (think `int`, `float`, `double`, but also whether it is big endian, - little endian so that the file is portable across different systems). Values can be - scalar, vector, or N-dimensional arrays. -3. Attributes - attributes can exist on a dataset or a group to provide supplemental - information about the group or dataset. We use attributes to indicate the ARMI - database version that was used to create the database, the time the case was - executed, and whether or not the case completed successfully. We also sometimes apply - attributes to datasets to indicate if any special formatting or layout was used to - store Parameter values or the like. - -There are many other features of HDF5, but from a usability standpoint that is enough -information to get started. +1. **Groups** - Groups are named collections of datasets. Think of a group as a filesystem folder. +2. **Datasets** - Datasets are named values. If a group is a folder, a dataset is a file. Values are + strongly typed (think `int`, `float`, `double`, but also whether it is big endian, little endian + so that the file is portable across different systems). Values can be scalar, vector, or + N-dimensional arrays. +3. **Attributes** - Attributes can exist on a dataset or a group to provide supplemental + information about the group or dataset. We use attributes to indicate the ARMI database version + that was used to create the database, the time the case was executed, and whether or not the + case completed successfully. We also sometimes apply attributes to datasets to indicate if any + special formatting or layout was used to store Parameter values or the like. + +There are many other features of HDF5, but this is enough information to get started. Database Structure ------------------ -The database structure is outlined below. This shows the broad strokes of how the -database is put together, but many more details may be gleaned from the in-line -documentation of the database modules. +The broad strokes of the database structure is outlined below. .. list-table:: Database structure :header-rows: 1 @@ -257,3 +249,25 @@ such special data to the HDF5 file and reading it back again is accomplished wit :py:func:`armi.bookkeeping.db.database.packSpecialData` and :py:func:`armi.bookkeeping.db.database.unpackSpecialData`. Refer to their implementations and documentation for more details. + +Loading Reactor State as Read-Only +---------------------------------- +Another option you have, though it will probably come up less often, is to lead a ``Reactor`` object +from a database file in read-only mode. Mostly what this does is set all the parameters loaded into +the reactor data model to a read-only mode. This can be useful to ensure that downstream analysts +do not modify the data they are reading. It looks much like the usual database load:: + + from armi.bookkeeping.db import databaseFactory + + db = databaseFactory("myDatabase.h5", "r") + + with db: + r = db.loadReadOnly(5, 2) + +Another common use for ``Database.loadReadOnly()`` is when you want to build a tool for analysts +that can open an ARMI database file without the ``App`` that created it. Solving such a problem +generically is hard-or-impossible, but assuming you probably know a lot about the ``App`` that +created an ARMI output file, this is usually doable in practice. To do so, you will want to look at +the :py:class:`PassiveDBLoadPlugin `. +This tool allows you to passively load an output database even if there are parameters or blueprint +sections that are unknown. diff --git a/doc/user/spatial_block_parameters.rst b/doc/user/spatial_block_parameters.rst index c17c9f741..2dd4e6f0c 100644 --- a/doc/user/spatial_block_parameters.rst +++ b/doc/user/spatial_block_parameters.rst @@ -186,7 +186,7 @@ component exists in space. If we grab the fuel component from the UO2 block in t [ 0.4444 -0.4444 0. ] [0.76972338 0.76972338 0. ] [0. 0. 0.] - Anchor: + Anchor: Offset: [0. 0. 0.] Num Locations: 400> >>> fuel = fuelBlock.getChildrenWithFlags(Flags.FUEL)[0] diff --git a/pyproject.toml b/pyproject.toml index fac5685aa..a1b4c6a2f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ build-backend = "setuptools.build_meta" [project] name = "armi" -version = "0.4.0" +version = "0.5.0" description = "An open-source nuclear reactor analysis automation framework that helps design teams increase efficiency and quality." license = {file = "LICENSE.md"} requires-python = ">3.8" @@ -61,6 +61,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Topic :: Scientific/Engineering :: Information Analysis", ]