Esempio n. 1
0
 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)
Esempio n. 2
0
 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 = {}
Esempio n. 3
0
 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 = []
Esempio n. 4
0
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
Esempio n. 5
0
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))