diff --git a/.github/workflows/unittests.yaml b/.github/workflows/unittests.yaml index b020e6dbf..4dead8224 100644 --- a/.github/workflows/unittests.yaml +++ b/.github/workflows/unittests.yaml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-24.04 strategy: matrix: - python: [3.9, '3.10', '3.11'] + python: [3.9, '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v2 diff --git a/armi/bookkeeping/db/database3.py b/armi/bookkeeping/db/database3.py index 99e3efddb..f1f35d572 100644 --- a/armi/bookkeeping/db/database3.py +++ b/armi/bookkeeping/db/database3.py @@ -1462,7 +1462,7 @@ def _writeAttrs(obj, group, attrs): ) if "attrs" not in group: - attrGroup = group.create_group("attrs", track_order=True) + attrGroup = group.create_group("attrs") else: attrGroup = group["attrs"] dataName = str(len(attrGroup)) + "_" + key diff --git a/armi/bookkeeping/db/layout.py b/armi/bookkeeping/db/layout.py index b0b329710..a2b38f720 100644 --- a/armi/bookkeeping/db/layout.py +++ b/armi/bookkeeping/db/layout.py @@ -418,23 +418,34 @@ def writeToDB(self, h5group): "layout/indexInData", data=self.indexInData, compression="gzip" ) h5group.create_dataset( - "layout/numChildren", data=self.numChildren, compression="gzip" + "layout/numChildren", + data=self.numChildren, + compression="gzip", + track_order=True, ) h5group.create_dataset( - "layout/location", data=self.location, compression="gzip" + "layout/location", + data=self.location, + compression="gzip", + track_order=True, ) h5group.create_dataset( "layout/locationType", data=numpy.array(self.locationType).astype("S"), compression="gzip", + track_order=True, ) h5group.create_dataset( "layout/material", data=numpy.array(self.material).astype("S"), compression="gzip", + track_order=True, ) h5group.create_dataset( - "layout/temperatures", data=self.temperatures, compression="gzip" + "layout/temperatures", + data=self.temperatures, + compression="gzip", + track_order=True, ) h5group.create_dataset( @@ -445,31 +456,41 @@ def writeToDB(self, h5group): compression="gzip", ) - gridsGroup = h5group.create_group("layout/grids") + gridsGroup = h5group.create_group("layout/grids", track_order=True) gridsGroup.attrs["nGrids"] = len(self.gridParams) gridsGroup.create_dataset( - "type", data=numpy.array([gp[0] for gp in self.gridParams]).astype("S") + "type", + data=numpy.array([gp[0] for gp in self.gridParams]).astype("S"), + track_order=True, ) for igrid, gridParams in enumerate(gp[1] for gp in self.gridParams): - thisGroup = gridsGroup.create_group(str(igrid)) - thisGroup.create_dataset("unitSteps", data=gridParams.unitSteps) + thisGroup = gridsGroup.create_group(str(igrid), track_order=True) + thisGroup.create_dataset( + "unitSteps", data=gridParams.unitSteps, track_order=True + ) for ibound, bound in enumerate(gridParams.bounds): if bound is not None: bound = numpy.array(bound) - thisGroup.create_dataset("bounds_{}".format(ibound), data=bound) + thisGroup.create_dataset( + "bounds_{}".format(ibound), data=bound, track_order=True + ) thisGroup.create_dataset( - "unitStepLimits", data=gridParams.unitStepLimits + "unitStepLimits", data=gridParams.unitStepLimits, track_order=True ) offset = gridParams.offset thisGroup.attrs["offset"] = offset is not None if offset is not None: - thisGroup.create_dataset("offset", data=offset) - thisGroup.create_dataset("geomType", data=gridParams.geomType) - thisGroup.create_dataset("symmetry", data=gridParams.symmetry) + thisGroup.create_dataset("offset", data=offset, track_order=True) + thisGroup.create_dataset( + "geomType", data=gridParams.geomType, track_order=True + ) + thisGroup.create_dataset( + "symmetry", data=gridParams.symmetry, track_order=True + ) except RuntimeError: runLog.error("Failed to create datasets in: {}".format(h5group)) raise diff --git a/armi/bookkeeping/db/tests/test_database3.py b/armi/bookkeeping/db/tests/test_database3.py index 18b8921b9..a77629830 100644 --- a/armi/bookkeeping/db/tests/test_database3.py +++ b/armi/bookkeeping/db/tests/test_database3.py @@ -350,12 +350,13 @@ def test_mergeHistory(self): self.r.p.cycle = 1 self.r.p.timeNode = 0 tnGroup = self.db.getH5Group(self.r) + randomText = "this isn't a reference to another dataset" database3.Database3._writeAttrs( tnGroup["layout/serialNum"], tnGroup, { - "fakeBigData": numpy.eye(6400), - "someString": "this isn't a reference to another dataset", + "fakeBigData": numpy.eye(64), + "someString": randomText, }, ) @@ -369,15 +370,15 @@ def test_mergeHistory(self): # this test is a little bit implementation-specific, but nice to be explicit self.assertEqual( - tnGroup["layout/serialNum"].attrs["fakeBigData"], - "@/c01n00/attrs/0_fakeBigData", + tnGroup["layout/serialNum"].attrs["someString"], + randomText, ) # exercise the _resolveAttrs function attrs = database3.Database3._resolveAttrs( tnGroup["layout/serialNum"].attrs, tnGroup ) - self.assertTrue(numpy.array_equal(attrs["fakeBigData"], numpy.eye(6400))) + self.assertTrue(numpy.array_equal(attrs["fakeBigData"], numpy.eye(64))) keys = sorted(db2.keys()) self.assertEqual(len(keys), 4) diff --git a/armi/cases/tests/test_suiteBuilder.py b/armi/cases/tests/test_suiteBuilder.py index e1681091f..4f1c5780e 100644 --- a/armi/cases/tests/test_suiteBuilder.py +++ b/armi/cases/tests/test_suiteBuilder.py @@ -116,25 +116,25 @@ def test_buildSuite(self): SettingModifier("settingName2", value) for value in (3, 4, 5) ) - self.assertEquals(builder.modifierSets[0][0].value, 1) - self.assertEquals(builder.modifierSets[0][1].value, 3) + self.assertEqual(builder.modifierSets[0][0].value, 1) + self.assertEqual(builder.modifierSets[0][1].value, 3) - self.assertEquals(builder.modifierSets[1][0].value, 2) - self.assertEquals(builder.modifierSets[1][1].value, 3) + self.assertEqual(builder.modifierSets[1][0].value, 2) + self.assertEqual(builder.modifierSets[1][1].value, 3) - self.assertEquals(builder.modifierSets[2][0].value, 1) - self.assertEquals(builder.modifierSets[2][1].value, 4) + self.assertEqual(builder.modifierSets[2][0].value, 1) + self.assertEqual(builder.modifierSets[2][1].value, 4) - self.assertEquals(builder.modifierSets[3][0].value, 2) - self.assertEquals(builder.modifierSets[3][1].value, 4) + self.assertEqual(builder.modifierSets[3][0].value, 2) + self.assertEqual(builder.modifierSets[3][1].value, 4) - self.assertEquals(builder.modifierSets[4][0].value, 1) - self.assertEquals(builder.modifierSets[4][1].value, 5) + self.assertEqual(builder.modifierSets[4][0].value, 1) + self.assertEqual(builder.modifierSets[4][1].value, 5) - self.assertEquals(builder.modifierSets[5][0].value, 2) - self.assertEquals(builder.modifierSets[5][1].value, 5) + self.assertEqual(builder.modifierSets[5][0].value, 2) + self.assertEqual(builder.modifierSets[5][1].value, 5) - self.assertEquals(len(builder.modifierSets), 6) + self.assertEqual(len(builder.modifierSets), 6) class TestSeparateEffectsBuilder(unittest.TestCase): @@ -155,19 +155,19 @@ def test_buildSuite(self): SettingModifier("settingName2", value) for value in (3, 4, 5) ) - self.assertEquals(builder.modifierSets[0][0].value, 1) - self.assertEquals(builder.modifierSets[0][0].settingName, "settingName1") + self.assertEqual(builder.modifierSets[0][0].value, 1) + self.assertEqual(builder.modifierSets[0][0].settingName, "settingName1") - self.assertEquals(builder.modifierSets[1][0].value, 2) - self.assertEquals(builder.modifierSets[1][0].settingName, "settingName1") + self.assertEqual(builder.modifierSets[1][0].value, 2) + self.assertEqual(builder.modifierSets[1][0].settingName, "settingName1") - self.assertEquals(builder.modifierSets[2][0].value, 3) - self.assertEquals(builder.modifierSets[2][0].settingName, "settingName2") + self.assertEqual(builder.modifierSets[2][0].value, 3) + self.assertEqual(builder.modifierSets[2][0].settingName, "settingName2") - self.assertEquals(builder.modifierSets[3][0].value, 4) - self.assertEquals(builder.modifierSets[3][0].settingName, "settingName2") + self.assertEqual(builder.modifierSets[3][0].value, 4) + self.assertEqual(builder.modifierSets[3][0].settingName, "settingName2") - self.assertEquals(builder.modifierSets[4][0].value, 5) - self.assertEquals(builder.modifierSets[4][0].settingName, "settingName2") + self.assertEqual(builder.modifierSets[4][0].value, 5) + self.assertEqual(builder.modifierSets[4][0].settingName, "settingName2") - self.assertEquals(len(builder.modifierSets), 5) + self.assertEqual(len(builder.modifierSets), 5) diff --git a/armi/nucDirectory/tests/test_nuclideBases.py b/armi/nucDirectory/tests/test_nuclideBases.py index 1de82a039..01ca11f08 100644 --- a/armi/nucDirectory/tests/test_nuclideBases.py +++ b/armi/nucDirectory/tests/test_nuclideBases.py @@ -127,13 +127,7 @@ def test_NaturalNuclide_atomicWeightIsAverageOfNaturallyOccuringIsotopes(self): atomicMass = 0.0 for natIso in natNuk.getNaturalIsotopics(): atomicMass += natIso.abundance * natIso.weight - self.assertEqual( - atomicMass, - natNuk.weight, - "{} weight is {}, expected {}".format( - natNuk, natNuk.weight, atomicMass - ), - ) + self.assertAlmostEqual(atomicMass, natNuk.weight, delta=0.000001) def test_nucBases_labelAndNameCollsionsAreForSameNuclide(self): """The name and labels for correct for nuclides. diff --git a/armi/reactor/assemblies.py b/armi/reactor/assemblies.py index 957c459b3..970dba181 100644 --- a/armi/reactor/assemblies.py +++ b/armi/reactor/assemblies.py @@ -66,7 +66,7 @@ def __init__(self, typ, assemNum=None): """ # If no assembly number is provided, generate a random number as a placeholder. if assemNum is None: - assemNum = randint(-9e12, -1) + assemNum = randint(-9000000000000, -1) name = self.makeNameFromAssemNum(assemNum) composites.Composite.__init__(self, name) self.p.assemNum = assemNum @@ -157,7 +157,7 @@ def makeUnique(self): otherwise have been the same object. """ # Default to a random negative assembly number (unique enough) - self.p.assemNum = randint(-9e12, -1) + self.p.assemNum = randint(-9000000000000, -1) self.renumber(self.p.assemNum) def add(self, obj: blocks.Block): diff --git a/doc/release/0.4.rst b/doc/release/0.4.rst index 5c98afce5..709207f79 100644 --- a/doc/release/0.4.rst +++ b/doc/release/0.4.rst @@ -8,6 +8,7 @@ Release Date: TBD New Features ------------ +#. ARMI now supports Python 3.12. (`PR#1813 `_) #. TBD API Changes diff --git a/pyproject.toml b/pyproject.toml index 19ae48a2b..8d74fb8b0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,8 @@ authors = [ ] dependencies = [ "coverage>=7.2.0", # Code coverage tool. Sadly baked into every Case. - "h5py>=3.0,<=3.9", # Needed because our database files are H5 format + "h5py>=3.9 ; python_version >= '3.11.0'", # Needed because our database files are H5 format + "h5py>=3.0,<=3.9 ; python_version < '3.11.0'", "htmltree>=0.7.6", # Our reports have HTML output "matplotlib>=3.5.3,<3.8.0", # Important plotting library "numpy>=1.21", # Important math library @@ -39,8 +40,10 @@ dependencies = [ "pluggy>=1.2.0", # Central tool behind the ARMI Plugin system "pyDOE>=0.3.8", # We import a Latin-hypercube algorithm to explore a phase space "pyevtk>=1.2.0", # Handles binary VTK visualization files - "ruamel.yaml.clib<=0.2.7", # C-based core of ruamel below - "ruamel.yaml<=0.17.21", # Our foundational YAML library + "ruamel.yaml.clib ; python_version >= '3.11.0'", # C-based core of ruamel below + "ruamel.yaml ; python_version >= '3.11.0'", # Our foundational YAML library + "ruamel.yaml.clib<=0.2.7 ; python_version < '3.11.0'", # C-based core of ruamel below + "ruamel.yaml<=0.17.21 ; python_version < '3.11.0'", # Our foundational YAML library "scipy>=1.7.0", # Used for curve-fitting and matrix math "tabulate>=0.8.9", # Used to pretty-print tabular data "toml>0.9.5", # Needed to parse the pyproject.toml file @@ -58,6 +61,7 @@ classifiers = [ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Scientific/Engineering :: Information Analysis", ]