def _assignTypeNums(self): if self.blockDesigns is None: # this happens when directly defining assemblies. self.blockDesigns = BlockKeyedList() for aDesign in self.assemDesigns: for bDesign in aDesign.blocks: if bDesign not in self.blockDesigns: self.blockDesigns.add(bDesign)
def __init__(self): # again, yamlize does not call __init__, instead we use Blueprints.load which creates and # instance of a Blueprints object and initializes it with values using setattr. Since the # method is never called, it serves the purpose of preventing pylint from issuing warnings # about attributes not existing. self.systemDesigns = Systems() self.assemDesigns = AssemblyKeyedList() self.blockDesigns = BlockKeyedList() self.assemblies = {}
def __init__(self): # again, yamlize does not call __init__, instead we use Blueprints.load which # creates and instance of a Blueprints object and initializes it with values # using setattr. Since the method is never called, it serves the purpose of # preventing pylint from issuing warnings about attributes not existing. self._assembliesBySpecifier = {} self._prepped = False self.systemDesigns = Systems() self.assemDesigns = AssemblyKeyedList() self.blockDesigns = BlockKeyedList() self.assemblies = {} self.grids = Grids() self.elementsToExpand = []
class Blueprints(yamlize.Object, metaclass=_BlueprintsPluginCollector): """Base Blueprintsobject representing all the subsections in the input file.""" nuclideFlags = yamlize.Attribute(key="nuclide flags", type=isotopicOptions.NuclideFlags, default=None) customIsotopics = yamlize.Attribute(key="custom isotopics", type=isotopicOptions.CustomIsotopics, default=None) blockDesigns = yamlize.Attribute(key="blocks", type=BlockKeyedList, default=None) assemDesigns = yamlize.Attribute(key="assemblies", type=AssemblyKeyedList, default=None) systemDesigns = yamlize.Attribute(key="systems", type=Systems, default=None) gridDesigns = yamlize.Attribute(key="grids", type=Grids, default=None) # These are used to set up new attributes that come from plugins. Defining its # initial state here to make pylint happy _resolveFunctions = [] def __new__(cls): # yamlizable does not call __init__, so attributes that are not defined above # need to be initialized here self = yamlize.Object.__new__(cls) self.assemblies = {} self._prepped = False self._assembliesBySpecifier = {} self.allNuclidesInProblem = ( ordered_set.OrderedSet() ) # Better for performance since these are used for lookups self.activeNuclides = ordered_set.OrderedSet() self.inertNuclides = ordered_set.OrderedSet() self.elementsToExpand = [] return self def __init__(self): # again, yamlize does not call __init__, instead we use Blueprints.load which # creates and instance of a Blueprints object and initializes it with values # using setattr. Since the method is never called, it serves the purpose of # preventing pylint from issuing warnings about attributes not existing. self._assembliesBySpecifier = {} self._prepped = False self.systemDesigns = Systems() self.assemDesigns = AssemblyKeyedList() self.blockDesigns = BlockKeyedList() self.assemblies = {} self.grids = Grids() self.elementsToExpand = [] def __repr__(self): return "<{} Assemblies:{} Blocks:{}>".format(self.__class__.__name__, len(self.assemDesigns), len(self.blockDesigns)) def constructAssem(self, cs, name=None, specifier=None): """ Construct a new assembly instance from the assembly designs in this Blueprints object. Parameters ---------- cs : CaseSettings object Used to apply various modeling options when constructing an assembly. name : str (optional, and should be exclusive with specifier) Name of the assembly to construct. This should match the key that was used to define the assembly in the Blueprints YAML file. specifier : str (optional, and should be exclusive with name) Identifier of the assembly to construct. This should match the identifier that was used to define the assembly in the Blueprints YAML file. Raises ------ ValueError If neither name nor specifier are passed Notes ----- There is some possibility for "compiling" the logic with closures to make constructing an assembly / block / component faster. At this point is is pretty much irrelevant because we are currently just deepcopying already constructed assemblies. Currently, this method is backward compatible with other code in ARMI and generates the `.assemblies` attribute (the BOL assemblies). Eventually, this should be removed. """ self._prepConstruction(cs) # TODO: this should be migrated assembly designs instead of assemblies if name is not None: assem = self.assemblies[name] elif specifier is not None: assem = self._assembliesBySpecifier[specifier] else: raise ValueError( "Must supply assembly name or specifier to construct") a = copy.deepcopy(assem) # since a deepcopy has the same assembly numbers and block id's, we need to make it unique a.makeUnique() return a def _prepConstruction(self, cs): """ This method initializes a bunch of information within a Blueprints object such as assigning assembly and block type numbers, resolving the nuclides in the problem, and pre-populating assemblies. Ideally, it would not be necessary at all, but the ``cs`` currently contains a bunch of information necessary to create the applicable model. If it were possible, it would be terrific to override the Yamlizable.from_yaml method to run this code after the instance has been created, but we need additional information in order to build the assemblies that is not within the YAML file. This method should not be called directly, but it is used in testing. """ if not self._prepped: self._assignTypeNums() for func in self._resolveFunctions: func(self, cs) self._resolveNuclides(cs) self._assembliesBySpecifier.clear() self.assemblies.clear() for aDesign in self.assemDesigns: a = aDesign.construct(cs, self) self._assembliesBySpecifier[aDesign.specifier] = a self.assemblies[aDesign.name] = a self._checkAssemblyAreaConsistency(cs) runLog.header( "=========== Verifying Assembly Configurations ===========") # pylint: disable=no-member armi.getPluginManagerOrFail().hook.afterConstructionOfAssemblies( assemblies=self.assemblies.values(), cs=cs) self._prepped = True def _assignTypeNums(self): if self.blockDesigns is None: # this happens when directly defining assemblies. self.blockDesigns = BlockKeyedList() for aDesign in self.assemDesigns: for bDesign in aDesign.blocks: if bDesign not in self.blockDesigns: self.blockDesigns.add(bDesign) def _resolveNuclides(self, cs): """ Process elements and determine how to expand them to natural isotopics. Also builds meta-data about which nuclides are in the problem. This system works by building a dictionary in the ``elementsToExpand`` attribute with ``Element`` keys and list of ``NuclideBase`` values. The actual expansion of elementals to isotopics occurs during :py:meth:`Component construction <armi.reactor.blueprints.componentBlueprint. ComponentBlueprint._constructMaterial>`. """ from armi import utils actives = set() inerts = set() undefBurnChainActiveNuclides = set() if self.nuclideFlags is None: self.nuclideFlags = isotopicOptions.genDefaultNucFlags() self.elementsToExpand = [] for nucFlag in self.nuclideFlags: # this returns any nuclides that are flagged specifically for expansion by input expandedElements = nucFlag.fileAsActiveOrInert( actives, inerts, undefBurnChainActiveNuclides) self.elementsToExpand.extend(expandedElements) inerts -= actives self.customIsotopics = self.customIsotopics or isotopicOptions.CustomIsotopics( ) ( elementalsToKeep, expansions, ) = isotopicOptions.autoSelectElementsToKeepFromSettings(cs) nucsFromInput = actives | inerts # join # Flag all elementals for expansion unless they've been flagged otherwise by # user input or automatic lattice/datalib rules. for elemental in nuclideBases.instances: if not isinstance(elemental, nuclideBases.NaturalNuclideBase): # `elemental` may be a NaturalNuclideBase or a NuclideBase # skip all NuclideBases continue if elemental in elementalsToKeep: continue if elemental.name in actives: currentSet = actives actives.remove(elemental.name) elif elemental.name in inerts: currentSet = inerts inerts.remove(elemental.name) else: # This was not specified in the nuclide flags at all. # If a material with this in its composition is brought in # it's nice from a user perspective to allow it. # But current behavior is that all nuclides in problem # must be declared up front. continue self.elementsToExpand.append(elemental.element) if (elemental.name in self.nuclideFlags and self.nuclideFlags[elemental.name].expandTo): # user-input has precedence newNuclides = [ nuclideBases.byName[nn] for nn in self.nuclideFlags[ elemental.element.symbol].expandTo ] elif (elemental in expansions and elemental.element.symbol in self.nuclideFlags): # code-specific expansion required newNuclides = expansions[elemental] # overlay code details onto nuclideFlags for other parts of the code # that will use them. # CRAP: would be better if nuclideFlags did this upon reading s.t. # order didn't matter. On the other hand, this is the only place in # the code where NuclideFlags get built and have user settings around # (hence "resolve"). # This must be updated because the operative expansion code just uses the flags # # Also, if this element is not in nuclideFlags at all, we just don't add it self.nuclideFlags[elemental.element.symbol].expandTo = [ nb.name for nb in newNuclides ] else: # expand to all possible natural isotopics newNuclides = elemental.element.getNaturalIsotopics() for nb in newNuclides: currentSet.add(nb.name) if self.elementsToExpand: runLog.info( "Will expand {} elementals to have natural isotopics".format( ", ".join(element.symbol for element in self.elementsToExpand))) self.activeNuclides = ordered_set.OrderedSet(sorted(actives)) self.inertNuclides = ordered_set.OrderedSet(sorted(inerts)) self.allNuclidesInProblem = ordered_set.OrderedSet( sorted(actives.union(inerts))) # Inform user which nuclides are truncating the burn chain. if undefBurnChainActiveNuclides: runLog.info( tabulate.tabulate( [[ "Nuclides truncating the burn-chain:", utils.createFormattedStrWithDelimiter( list(undefBurnChainActiveNuclides)), ]], tablefmt="plain", ), single=True, ) def _checkAssemblyAreaConsistency(self, cs): references = None for a in self.assemblies.values(): if references is None: references = (a, a.getArea()) continue assemblyArea = a.getArea() if isinstance(a, assemblies.RZAssembly): # R-Z assemblies by definition have different areas, so skip the check continue if abs(references[1] - assemblyArea) > 1e-9: runLog.error("REFERENCE COMPARISON ASSEMBLY:") references[0][0].printContents() runLog.error("CURRENT COMPARISON ASSEMBLY:") a[0].printContents() raise InputError( "Assembly {} has a different area {} than assembly {} {}. Check inputs for accuracy" .format(a, assemblyArea, references[0], references[1])) blockArea = a[0].getArea() for b in a[1:]: if (abs(b.getArea() - blockArea) / blockArea > cs["acceptableBlockAreaError"]): runLog.error("REFERENCE COMPARISON BLOCK:") a[0].printContents(includeNuclides=False) runLog.error("CURRENT COMPARISON BLOCK:") b.printContents(includeNuclides=False) for c in b.getChildren(): runLog.error("{0} area {1} effective area {2}" "".format(c, c.getArea(), c.getVolume() / b.getHeight())) raise InputError( "Block {} has a different area {} than block {} {}. Check inputs for accuracy" .format(b, b.getArea(), a[0], blockArea)) @classmethod def migrate(cls, inp: typing.TextIO): """Given a stream representation of a blueprints file, migrate it. Parameters ---------- inp : typing.TextIO Input stream to migrate. """ for migI in migration.ACTIVE_MIGRATIONS: if issubclass(migI, migration.base.BlueprintsMigration): mig = migI(stream=inp) inp = mig.apply() return inp
class Blueprints(yamlize.Object): """Base Blueprintsobject representing all the subsections in the input file.""" nuclideFlags = yamlize.Attribute(key="nuclide flags", type=NuclideFlags, default=None) customIsotopics = yamlize.Attribute(key="custom isotopics", type=CustomIsotopics, default=None) blockDesigns = yamlize.Attribute(key="blocks", type=BlockKeyedList, default=None) assemDesigns = yamlize.Attribute(key="assemblies", type=AssemblyKeyedList, default=None) systemDesigns = yamlize.Attribute(key="systems", type=Systems, default=None) # These are used to set up new attributes that come from plugins. Defining its # initial state here to make pylint happy _resolveFunctions = [] def __new__(cls): # yamlizable does not call __init__, so attributes that are not defined above # need to be initialized here self = yamlize.Object.__new__(cls) self.assemblies = {} self._prepped = False self._assembliesBySpecifier = {} self.allNuclidesInProblem = ( ordered_set.OrderedSet() ) # Better for performance since these are used for lookups self.activeNuclides = ordered_set.OrderedSet() self.inertNuclides = ordered_set.OrderedSet() self.elementsToExpand = [] return self def __init__(self): # again, yamlize does not call __init__, instead we use Blueprints.load which creates and # instance of a Blueprints object and initializes it with values using setattr. Since the # method is never called, it serves the purpose of preventing pylint from issuing warnings # about attributes not existing. self.systemDesigns = Systems() self.assemDesigns = AssemblyKeyedList() self.blockDesigns = BlockKeyedList() self.assemblies = {} def __repr__(self): return "<{} Assemblies:{} Blocks:{}>".format(self.__class__.__name__, len(self.assemDesigns), len(self.blockDesigns)) def constructAssem(self, geomType, cs, name=None, specifier=None): """ Construct a new assembly instance from the assembly designs in this Blueprints object. Parameters ---------- geomType : str string indicating the geometry type. This is used to select the correct Assembly and Block subclasses. ``'hex'`` should be used to create hex assemblies. This input is derived based on the Geometry object, though it would be nice to instead infer it from block components, and then possibly fail if there is mismatch. Though, you can fit a round peg in a square hole so long as D <= s. cs : CaseSettings object Used to apply various modeling options when constructing an assembly. name : str (optional, and should be exclusive with specifier) Name of the assembly to construct. This should match the key that was used to define the assembly in the Blueprints YAML file. specifier : str (optional, and should be exclusive with name) Identifier of the assembly to construct. This should match the identifier that was used to define the assembly in the Blueprints YAML file. Raises ------ ValueError If neither name nor specifier are passed Notes ----- There is some possibility for "compiling" the logic with closures to make constructing an assembly / block / component faster. At this point is is pretty much irrelevant because we are currently just deepcopying already constructed assemblies. Currently, this method is backward compatible with other code in ARMI and generates the `.assemblies` attribute (the BOL assemblies). Eventually, this should be removed. """ self._prepConstruction(geomType, cs) # TODO: this should be migrated assembly designs instead of assemblies if name is not None: assem = self.assemblies[name] elif specifier is not None: assem = self._assembliesBySpecifier[specifier] else: raise ValueError( "Must supply assembly name or specifier to construct") a = copy.deepcopy(assem) # since a deepcopy has the same assembly numbers and block id's, we need to make it unique a.makeUnique() return a def _prepConstruction(self, geomType, cs): """ This method initializes a bunch of information within a Blueprints object such as assigning assembly and block type numbers, resolving the nuclides in the problem, and pre-populating assemblies. Ideally, it would not be necessary at all, but the ``cs`` currently contains a bunch of information necessary to create the applicable model. If it were possible, it would be terrific to override the Yamlizable.from_yaml method to run this code after the instance has been created, but we need additional information in order to build the assemblies that is not within the YAML file. This method should not be called directly, but it is used in testing. """ if not self._prepped: self._assignTypeNums() for func in self._resolveFunctions: func(self, cs) self._resolveNuclides(cs) self._assembliesBySpecifier.clear() self.assemblies.clear() for aDesign in self.assemDesigns: a = aDesign.construct(cs, self) self._assembliesBySpecifier[aDesign.specifier] = a self.assemblies[aDesign.name] = a self._checkAssemblyAreaConsistency(cs) runLog.header( "=========== Verifying Assembly Configurations ===========") # pylint: disable=no-member armi.getPluginManagerOrFail().hook.afterConstructionOfAssemblies( assemblies=self.assemblies.values(), cs=cs) self._prepped = True def _assignTypeNums(self): if self.blockDesigns is None: # this happens when directly defining assemblies. self.blockDesigns = BlockKeyedList() for aDesign in self.assemDesigns: for bDesign in aDesign.blocks: if bDesign not in self.blockDesigns: self.blockDesigns.add(bDesign) def _resolveNuclides(self, cs): """Expands the density of any elemental nuclides to its natural isotopics.""" from armi import utils # expand burn-chain to only contain nuclides, no elements actives = set() inerts = set() undefBurnChainActiveNuclides = set() if self.nuclideFlags is None: self.nuclideFlags = genDefaultNucFlags() for nucFlag in self.nuclideFlags: nucFlag.prepForCase(actives, inerts, undefBurnChainActiveNuclides) inerts -= actives self.customIsotopics = self.customIsotopics or CustomIsotopics() self.elementsToExpand = [] elementalsToSkip = self._selectNuclidesToExpandForModeling(cs) # if elementalsToSkip=[CR], we expand everything else. e.g. CR -> CR (unchanged) nucsFromInput = actives | inerts # join for elemental in nuclideBases.instances: if not isinstance(elemental, nuclideBases.NaturalNuclideBase): continue if elemental.name not in nucsFromInput: continue # we've now confirmed this elemental is in the problem if elemental in elementalsToSkip: continue nucsInProblem = actives if elemental.name in actives else inerts nucsInProblem.remove(elemental.name) self.elementsToExpand.append(elemental.element) for nb in elemental.element.getNaturalIsotopics(): nucsInProblem.add(nb.name) if self.elementsToExpand: runLog.info( "Expanding {} elementals to have natural isotopics".format( ", ".join(element.symbol for element in self.elementsToExpand))) self.activeNuclides = ordered_set.OrderedSet(sorted(actives)) self.inertNuclides = ordered_set.OrderedSet(sorted(inerts)) self.allNuclidesInProblem = ordered_set.OrderedSet( sorted(actives.union(inerts))) # Inform user that the burn-chain may not be complete if undefBurnChainActiveNuclides: runLog.info( tabulate.tabulate( [[ "Nuclides truncating the burn-chain:", utils.createFormattedStrWithDelimiter( list(undefBurnChainActiveNuclides)), ]], tablefmt="plain", ), single=True, ) @staticmethod def _selectNuclidesToExpandForModeling(cs): elementalsToSkip = set() endf70Elementals = [ nuclideBases.byName[name] for name in ["C", "V", "ZN"] ] endf71Elementals = [nuclideBases.byName[name] for name in ["C"]] if "MCNP" in cs["neutronicsKernel"]: if int(cs["mcnpLibrary"]) == 50: elementalsToSkip.update( nuclideBases.instances) # skip expansion # ENDF/B VII.0 elif 70 <= int(cs["mcnpLibrary"]) <= 79: elementalsToSkip.update(endf70Elementals) # ENDF/B VII.1 elif 80 <= int(cs["mcnpLibrary"]) <= 89: elementalsToSkip.update(endf71Elementals) else: raise InputError( "Failed to determine nuclides for modeling. " "The `mcnpLibrary` setting value ({}) is not supported.". format(cs["mcnpLibrary"])) elif cs["xsKernel"] in ["SERPENT", "MC2v3", "MC2v3-PARTISN"]: elementalsToSkip.update(endf70Elementals) elif cs["xsKernel"] == "MC2v2": elementalsToSkip.update(nuclideBases.instances) # skip expansion return elementalsToSkip def _checkAssemblyAreaConsistency(self, cs): references = None for a in self.assemblies.values(): if references is None: references = (a, a.getArea()) continue assemblyArea = a.getArea() if abs(references[1] - assemblyArea) > 1e-9 and not hasattr( a.location, "ThRZmesh"): # if the location has a mesh then the assemblies can have irregular areas runLog.error("REFERENCE COMPARISON ASSEMBLY:") references[0][0].printContents() runLog.error("CURRENT COMPARISON ASSEMBLY:") a[0].printContents() raise InputError( "Assembly {} has a different area {} than assembly {} {}. Check inputs for accuracy" .format(a, assemblyArea, references[0], references[1])) blockArea = a[0].getArea() for b in a[1:]: if (abs(b.getArea() - blockArea) / blockArea > cs["acceptableBlockAreaError"]): runLog.error("REFERENCE COMPARISON BLOCK:") a[0].printContents(includeNuclides=False) runLog.error("CURRENT COMPARISON BLOCK:") b.printContents(includeNuclides=False) for c in b.getChildren(): runLog.error("{0} area {1} effective area {2}" "".format(c, c.getArea(), c.getVolume() / b.getHeight())) raise InputError( "Block {} has a different area {} than block {} {}. Check inputs for accuracy" .format(b, b.getArea(), a[0], blockArea))