diff --git a/armi/reactor/blocks.py b/armi/reactor/blocks.py index 9210ca5bb..9d5ef7916 100644 --- a/armi/reactor/blocks.py +++ b/armi/reactor/blocks.py @@ -1454,7 +1454,7 @@ def breakFuelComponentsIntoIndividuals(self): # Handle all other components that may be linked to the fuel multiplicity # by unlinking them and setting them directly. # TODO: What about other (actual) dimensions? This is a limitation in that only fuel - # compuents are duplicated, and not the entire pin. It is also a reasonable assumption with + # components are duplicated, and not the entire pin. It is also a reasonable assumption with # current/historical usage of ARMI. for comp, dim in self.getComponentsThatAreLinkedTo(fuel, "mult"): comp.setDimension(dim, nPins) diff --git a/armi/reactor/flags.py b/armi/reactor/flags.py index 3386772f9..a85c309c4 100644 --- a/armi/reactor/flags.py +++ b/armi/reactor/flags.py @@ -128,11 +128,16 @@ def __fromStringGeneral(cls, typeSpec, updateMethod): result |= _CONVERSIONS[conversion] for name in typeSpec.split(): - # ignore numbers so we don't have to define flags up to 217+ (number of pins/assem) - typeSpecWithoutNumbers = "".join([c for c in name if not c.isdigit()]) - if not typeSpecWithoutNumbers: - continue - result |= updateMethod(typeSpecWithoutNumbers) + try: + # check for an exact name match first + result |= cls[name] + except KeyError: + # ignore numbers so we don't have to define flags up to the number of pins/assem + typeSpecWithoutNumbers = "".join([c for c in name if not c.isdigit()]) + if not typeSpecWithoutNumbers: + continue + result |= updateMethod(typeSpecWithoutNumbers) + return result @@ -148,8 +153,7 @@ def _fromStringIgnoreErrors(cls, typeSpec): Complications arise when: - a. multiple-word flags are used such as *grid plate* or - *inlet nozzle* so we use lookups. + a. multiple-word flags are used such as *grid plate* or *inlet nozzle* so we use lookups. b. Some flags have digits in them. We just strip those off. """ @@ -170,8 +174,8 @@ def updateMethod(typeSpec): return cls[typeSpec] except KeyError: raise InvalidFlagsError( - "The requested type specification `{}` is invalid. " - "See armi.reactor.flags documentation.".format(typeSpec) + f"The requested type specification `{typeSpec}` is invalid. " + "See armi.reactor.flags documentation." ) return __fromStringGeneral(cls, typeSpec, updateMethod) @@ -289,15 +293,14 @@ def fromString(cls, typeSpec): :id: I_ARMI_FLAG_TO_STR0 :implements: R_ARMI_FLAG_TO_STR - For a string passed as ``typeSpec``, first converts the whole string - to uppercase. Then tries to parse the string for any special phrases, as - defined in the module dictionary ``_CONVERSIONS``, and converts those - phrases to flags directly. + For a string passed as ``typeSpec``, first converts the whole string to uppercase. Then + tries to parse the string for any special phrases, as defined in the module dictionary + ``_CONVERSIONS``, and converts those phrases to flags directly. - Then it splits the remaining string into separate words based on the presence - of spaces. Looping over each of the words, any numbers are stripped out - and the remaining string is matched up to any class attribute names. - If any matches are found these are returned as flags. + Then it splits the remaining string into words based on spaces. Looping over each of the + words, if any word exactly matches a flag name. Otherwise, any numbers are stripped out + and the remaining string is matched up to any class attribute names. If any matches are + found these are returned as flags. """ return _fromString(cls, typeSpec) @@ -310,11 +313,9 @@ def toString(cls, typeSpec): :id: I_ARMI_FLAG_TO_STR1 :implements: R_ARMI_FLAG_TO_STR - This converts the representation of a bunch of flags from ``typeSpec``, - which might look like ``Flags.A|B``, - into a string with spaces in between the flag names, which would look - like ``'A B'``. This is done via nesting string splitting and replacement - actions. + This converts the representation of a bunch of flags from ``typeSpec``, which might look + like ``Flags.A|B``, into a string with spaces in between the flag names, which would + look like ``'A B'``. This is done via nesting string splitting and replacement actions. """ return _toString(cls, typeSpec) diff --git a/armi/reactor/tests/test_flags.py b/armi/reactor/tests/test_flags.py index 7155cae95..7d3d2fe8b 100644 --- a/armi/reactor/tests/test_flags.py +++ b/armi/reactor/tests/test_flags.py @@ -26,6 +26,49 @@ def test_fromString(self): self._help_fromString(flags.Flags.fromStringIgnoreErrors) self.assertEqual(flags.Flags.fromStringIgnoreErrors("invalid"), flags.Flags(0)) + def test_fromStringWithNumbers(self): + # testing pure numbers + self.assertEqual(flags.Flags.fromStringIgnoreErrors("1"), flags.Flags(0)) + self.assertEqual(flags.Flags.fromStringIgnoreErrors("7"), flags.Flags(0)) + + # testing fuel naming logic + self.assertEqual(flags.Flags.fromStringIgnoreErrors("Fuel1"), flags.Flags.FUEL) + self.assertEqual( + flags.Flags.fromStringIgnoreErrors("Fuel123"), flags.Flags.FUEL + ) + self.assertEqual(flags.Flags.fromStringIgnoreErrors("fuel 1"), flags.Flags.FUEL) + self.assertEqual( + flags.Flags.fromStringIgnoreErrors("fuel 123"), flags.Flags.FUEL + ) + + def test_flagsDefinedWithNumbers(self): + """Test that if we DEFINE flags with numbers in them, those are treated as exceptions.""" + # define flags TYPE1 and TYPE1B (arbitrary example) + flags.Flags.extend({"TYPE1": flags.auto(), "TYPE1B": flags.auto()}) + + # verify that these flags are correctly found + self.assertEqual(flags.Flags["TYPE1"], flags.Flags.TYPE1) + self.assertEqual(flags.Flags["TYPE1B"], flags.Flags.TYPE1B) + self.assertEqual(flags.Flags.fromStringIgnoreErrors("type1"), flags.Flags.TYPE1) + self.assertEqual( + flags.Flags.fromStringIgnoreErrors("Type1b"), flags.Flags.TYPE1B + ) + + # the more complicated situation where our exceptions are mixed with the usual flag logic + self.assertEqual( + flags.Flags.fromString("type1 fuel"), flags.Flags.TYPE1 | flags.Flags.FUEL + ) + + self.assertEqual( + flags.Flags.fromString("type1 fuel 123 bond"), + flags.Flags.TYPE1 | flags.Flags.FUEL | flags.Flags.BOND, + ) + + self.assertEqual( + flags.Flags.fromString("type1 fuel123 bond"), + flags.Flags.TYPE1 | flags.Flags.FUEL | flags.Flags.BOND, + ) + def test_flagsToAndFromString(self): """ Convert flag to and from string for serialization. @@ -58,8 +101,7 @@ def _help_fromString(self, method): self.assertEqual(method("bond1"), flags.Flags.BOND) self.assertEqual(method("bond 2"), flags.Flags.BOND) self.assertEqual(method("fuel test"), flags.Flags.FUEL | flags.Flags.TEST) - # test the more strict GRID conversion, which can cause collisions with - # GRID_PLATE + # test the more strict GRID conversion, which can cause collisions with GRID_PLATE self.assertEqual( flags.Flags.fromStringIgnoreErrors("grid_plate"), flags.Flags.GRID_PLATE ) diff --git a/doc/release/0.4.rst b/doc/release/0.4.rst index ea65d6de1..768c967be 100644 --- a/doc/release/0.4.rst +++ b/doc/release/0.4.rst @@ -18,6 +18,7 @@ New Features #. 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 `_) #. New plugin hook ``beforeReactorConstruction`` added to enable plugins to process case settings before reactor init. (`PR#1945 `_)