Esempio n. 1
0
 def test_isTopDummyBlockPresent(self):
     # build test assembly without dummy
     assembly = HexAssembly("testAssemblyType")
     assembly.spatialGrid = grids.axialUnitGrid(numCells=1)
     assembly.spatialGrid.armiObject = assembly
     assembly.add(_buildTestBlock("shield", "FakeMat"))
     assembly.calculateZCoords()
     assembly.reestablishBlockOrder()
     # create instance of expansion changer
     obj = AxialExpansionChanger(detailedAxialExpansion=True)
     with self.assertRaises(RuntimeError) as cm:
         obj.setAssembly(assembly)
         the_exception = cm.exception
         self.assertEqual(the_exception.error_code, 3)
Esempio n. 2
0
class TestSpecifyTargetComponent(unittest.TestCase):
    """verify specifyTargetComponent method is properly updating _componentDeterminesBlockHeight"""
    def setUp(self):
        self.obj = AxialExpansionChanger()
        self.a = buildTestAssemblyWithFakeMaterial(name="FakeMatException")
        self.obj.setAssembly(self.a)
        # need an empty dictionary because we want to test for the added component only
        self.obj.expansionData._componentDeterminesBlockHeight = {}

    def test_specifyTargetComponent(self):
        # build a test block
        b = HexBlock("fuel", height=10.0)
        fuelDims = {
            "Tinput": 25.0,
            "Thot": 25.0,
            "od": 0.76,
            "id": 0.00,
            "mult": 127.0
        }
        cladDims = {
            "Tinput": 25.0,
            "Thot": 25.0,
            "od": 0.80,
            "id": 0.77,
            "mult": 127.0
        }
        fuel = Circle("fuel", "FakeMat", **fuelDims)
        clad = Circle("clad", "FakeMat", **cladDims)
        b.add(fuel)
        b.add(clad)
        # call method, and check that target component is correct
        self.obj.expansionData.specifyTargetComponent(b)
        self.assertTrue(
            self.obj.expansionData.isTargetComponent(fuel),
            msg=
            "specifyTargetComponent failed to recognize intended component: {}"
            .format(fuel),
        )

    def test_specifyTargetComponentBlockWithMultipleFlags(self):
        # build a block that has two flags as well as a component matching each
        # flag
        b = HexBlock("fuel poison", height=10.0)
        fuelDims = {
            "Tinput": 25.0,
            "Thot": 600.0,
            "od": 0.9,
            "id": 0.5,
            "mult": 200.0
        }
        poisonDims = {
            "Tinput": 25.0,
            "Thot": 400.0,
            "od": 0.5,
            "id": 0.0,
            "mult": 10.0
        }
        fuel = Circle("fuel", "FakeMat", **fuelDims)
        poison = Circle("poison", "FakeMat", **poisonDims)
        b.add(fuel)
        b.add(poison)
        # call method, and check that target component is correct
        self.obj.expansionData.specifyTargetComponent(b)
        self.assertTrue(
            self.obj.expansionData.isTargetComponent(fuel),
            msg=
            "specifyTargetComponent failed to recognize intended component: {}"
            .format(fuel),
        )
Esempio n. 3
0
class SystemBlueprint(yamlize.Object):
    """
    The reactor-level structure input blueprint.

    .. note:: We use string keys to link grids to objects that use them. This differs
        from how blocks/assembies are specified, which use YAML anchors. YAML anchors
        have proven to be problematic and difficult to work with

    """

    name = yamlize.Attribute(key="name", type=str)
    typ = yamlize.Attribute(key="type", type=str, default="core")
    gridName = yamlize.Attribute(key="grid name", type=str)
    origin = yamlize.Attribute(key="origin", type=Triplet, default=None)

    def __init__(self, name=None, gridName=None, origin=None):
        """
        A Reactor Level Structure like a core or SFP.

        Notes
        -----
        yamlize does not call an __init__ method, instead it uses __new__ and setattr
        this is only needed for when you want to make this object from a non-YAML source.
        """
        self.name = name
        self.gridName = gridName
        self.origin = origin
        self.axialExpChngr = None

    @staticmethod
    def _resolveSystemType(typ: str):
        # Loop over all plugins that could be attached and determine if any
        # tell us how to build a specific systems attribute. Sub-optimial
        # as this check is called for each system (e.g., core, spent fuel pool).
        # It is assumed that the number of systems is currently low enough to justify
        # this structure.

        manager = getPluginManagerOrFail()

        # Only need this to handle the case we don't find the system we expect
        seen = set()
        for options in manager.hook.defineSystemBuilders():
            for key, builder in options.items():
                # Take the first match we find. This would allow other plugins to
                # define a new core builder before finding those defined by the
                # ReactorPlugin
                if key == typ:
                    return builder
                seen.add(key)

        raise ValueError(
            "Could not determine an appropriate class for handling a "
            "system of type `{}`. Supported types are {}.".format(
                typ, sorted(seen)))

    def construct(self, cs, bp, reactor, geom=None, loadAssems=True):
        """Build a core/IVS/EVST/whatever and fill it with children.

        Parameters
        ----------
        cs : :py:class:`Settings <armi.settings.Settings>` object.
            armi settings to apply
        bp : :py:class:`Reactor <armi.reactor.blueprints.Blueprints>` object.
            armi blueprints to apply
        reactor : :py:class:`Reactor <armi.reactor.reactors.Reactor>` object.
            reactor to fill
        geom : optional
        loadAssems : bool, optional
            whether to fill reactor with assemblies, as defined in blueprints, or not. Is False in
            :py:class:`UniformMeshGeometryConverter <armi.reactor.converters.uniformMesh.UniformMeshGeometryConverter>`
            within the initNewReactor() class method.

        Raises
        ------
        ValueError
            input error, no grid design provided
        ValueError
            for 1/3 core maps, assemblies are defined outside the expected 1/3 core region
        """
        from armi.reactor import reactors  # avoid circular import

        runLog.info("Constructing the `{}`".format(self.name))

        self.axialExpChngr = AxialExpansionChanger(
            cs["detailedAxialExpansion"])
        # TODO: We should consider removing automatic geom file migration.
        if geom is not None and self.name == "core":
            gridDesign = geom.toGridBlueprints("core")[0]
        else:
            if not bp.gridDesigns:
                raise ValueError(
                    "The input must define grids to construct a reactor, but "
                    "does not. Update input.")
            gridDesign = bp.gridDesigns.get(self.gridName, None)

        system = self._resolveSystemType(self.typ)(self.name)

        # TODO: This could be somewhere better. If system blueprints could be
        # subclassed, this could live in the CoreBlueprint. setOptionsFromCS() also isnt
        # great to begin with, so ideally it could be removed entirely.
        if isinstance(system, reactors.Core):
            system.setOptionsFromCs(cs)

        # Some systems may not require a prescribed grid design. Only try to use one if
        # it was provided
        if gridDesign is not None:
            spatialGrid = gridDesign.construct()
            system.spatialGrid = spatialGrid
            system.spatialGrid.armiObject = system

        reactor.add(system)  # need parent before loading assemblies
        spatialLocator = grids.CoordinateLocation(self.origin.x, self.origin.y,
                                                  self.origin.z, None)
        system.spatialLocator = spatialLocator
        if context.MPI_RANK != 0:
            # on non-master nodes we don't bother building up the assemblies
            # because they will be populated with DistributeState.
            return None

        # TODO: This is also pretty specific to Core-like things. We envision systems
        # with non-Core-like structure. Again, probably only doable with subclassing of
        # Blueprints
        if loadAssems:
            self._loadAssemblies(cs, system, gridDesign.gridContents, bp)

            # TODO: This post-construction work is specific to Cores for now. We need to
            # generalize this. Things to consider:
            # - Should the Core be able to do geom modifications itself, since it already
            # has the grid constructed from the grid design?
            # - Should the summary be so specifically Material data? Should this be done for
            # non-Cores? Like geom modifications, could this just be done in processLoading?
            # Should it be invoked higher up, by whatever code is requesting the Reactor be
            # built from Blueprints?
            if isinstance(system, reactors.Core):
                summarizeMaterialData(system)
                self._modifyGeometry(system, gridDesign)
                system.processLoading(cs)
        return system

    # pylint: disable=no-self-use
    def _loadAssemblies(self, cs, container, gridContents, bp):
        runLog.header("=========== Adding Assemblies to {} ===========".format(
            container))
        badLocations = set()
        for locationInfo, aTypeID in gridContents.items():
            newAssembly = bp.constructAssem(cs, specifier=aTypeID)
            if not cs["inputHeightsConsideredHot"]:
                if not newAssembly.hasFlags(Flags.CONTROL):
                    self.axialExpChngr.setAssembly(newAssembly)
                    self.axialExpChngr.expansionData.computeThermalExpansionFactors(
                    )
                    self.axialExpChngr.axiallyExpandAssembly(thermal=True)

            i, j = locationInfo
            loc = container.spatialGrid[i, j, 0]
            try:
                container.add(newAssembly, loc)
            except LookupError:
                badLocations.add(loc)

        if badLocations:
            raise ValueError(
                "Geometry core map xml had assemblies outside the "
                "first third core, but had third core symmetry. \n"
                "Please update symmetry to be `full core` or "
                "remove assemblies outside the first third. \n"
                "The locations outside the first third are {}".format(
                    badLocations))

    def _modifyGeometry(self, container, gridDesign):
        """Perform post-load geometry conversions like full core, edge assems."""
        # all cases should have no edge assemblies. They are added ephemerally when needed
        from armi.reactor.converters import geometryConverters  # circular imports

        runLog.header(
            "=========== Applying Geometry Modifications ===========")
        converter = geometryConverters.EdgeAssemblyChanger()
        converter.removeEdgeAssemblies(container)

        # now update the spatial grid dimensions based on the populated children
        # (unless specified on input)
        if not gridDesign.latticeDimensions:
            runLog.info(
                "Updating spatial grid pitch data for {} geometry".format(
                    container.geomType))
            if container.geomType == geometry.GeomType.HEX:
                container.spatialGrid.changePitch(container[0][0].getPitch())
            elif container.geomType == geometry.GeomType.CARTESIAN:
                xw, yw = container[0][0].getPitch()
                container.spatialGrid.changePitch(xw, yw)