def setUp(self): bp = blueprints.Blueprints() r = reactors.Reactor(settings.getMasterCs(), bp) r.add(reactors.Core("Core", settings.getMasterCs())) r.core.spatialGrid = grids.hexGridFromPitch(1.0) r.core.spatialGrid.symmetry = geometry.THIRD_CORE + geometry.PERIODIC r.core.spatialGrid.geomType = geometry.HEX aList = [] for ring in range(10): a = assemblies.HexAssembly("fuel") a.spatialLocator = r.core.spatialGrid[ring, 1, 0] a.parent = r.core aList.append(a) self.aList = aList
def setUp(self): bp = blueprints.Blueprints() geom = geometry.SystemLayoutInput() geom.symmetry = "third core periodic" r = reactors.Reactor(settings.getMasterCs(), bp) r.add(reactors.Core("Core", settings.getMasterCs(), geom)) r.core.spatialGrid = grids.hexGridFromPitch(1.0) aList = [] for ring in range(10): a = assemblies.HexAssembly("fuel") a.spatialLocator = r.core.spatialGrid[ring, 1, 0] a.parent = r.core aList.append(a) self.aList = aList
def _updateMassFractionsFromParamValues(reactor, blockParamNames, blockList): """ Set the block densities based on the already-updated n block-params. The DB reads in params that represent the nuclide densities on each block, but they cannot be applied to the component until the temperatures are updated. """ runLog.info("Updating component mass fractions from DB params") # Set all number densities on a block at a time so we don't have to compute volume fractions N times. allNucNamesInProblem = set(reactor.blueprints.allNuclidesInProblem) allNucBasesInProblem = { nuclideBases.byName[nucName] for nucName in allNucNamesInProblem } nucBasesInBlockParams = { nb for nb in allNucBasesInProblem if nb.getDatabaseName() in blockParamNames } if settings.getMasterCs()["zeroOutNuclidesNotInDB"]: nucBasesNotInBlockParams = allNucBasesInProblem - nucBasesInBlockParams zeroOut = { nb.getDatabaseName(): 0.0 for nb in nucBasesNotInBlockParams } if zeroOut: runLog.important( "Zeroing out {0} because they are not in the db.".format( nucBasesNotInBlockParams)) else: zeroOut = {} for b in blockList: ndens = { nuc.name: b.p[nuc.getDatabaseName()] for nuc in nucBasesInBlockParams } ndens.update(zeroOut) # apply all non-zero number densities no matter what. # zero it out if it was already there and is now set to zero. ndens = { name: val for name, val in ndens.items() if val or name in set(b.getNuclides()) } b.setNumberDensities(ndens) allNucsNamesInDB = { nuclideBases.nucNameFromDBName(paramName) for paramName in blockParamNames.intersection( nuclideBases.byDBName.keys()) } nucNamesInDataBaseButNotProblem = allNucsNamesInDB - allNucNamesInProblem for nucName in nucNamesInDataBaseButNotProblem: runLog.warning( "Nuclide {0} exists in the database but not the problem. It is being ignored" "".format(nucName))
def _resetWorker(self): """ Clear out the reactor on the workers to start anew. Notes ----- This was made to help minimize the amount of RAM that is used during some gigantic long-running cases. Resetting after building copies of reactors or transforming their geometry is one approach. We hope to implement more efficient solutions in the future. .. warning:: This should build empty non-core systems too. """ xsGroups = self.getInterface("xsGroups") if xsGroups: xsGroups.clearRepresentativeBlocks() cs = settings.getMasterCs() bp = self.r.blueprints spatialGrid = self.r.core.spatialGrid self.detach() self.r = reactors.Reactor(cs.caseTitle, bp) core = reactors.Core("Core") self.r.add(core) core.spatialGrid = spatialGrid self.reattach(self.r, cs)
def writeToDB(self, reactor, statePointName=None): """ Puts self.r in the database. Parameters ---------- statePointName: str, optional explicit name of the state point to be written. A state point is not necessarily a cycle or time node, it may make the most sense to simply think of it as a label for the current state. """ if self._initDatabaseContact: self._createDBSchema(reactor) self._initDatabaseContact = False if statePointName: statePointName = statePointName else: # check for None instead of truth since these may be 0 cycle = reactor.p.cycle node = reactor.p.timeNode statePointName = utils.getTimeStepNum(cycle, node, settings.getMasterCs()) self._writeReactorParams(reactor, statePointName) self._writeAssemblyParams(reactor, statePointName) self._writeBlockParams(reactor, statePointName) self._writeComponentParams(reactor, statePointName) self._numTimeSteps = -1 # update numTimeSteps attribute
def __init__(self, r, cs): r""" Construct an interface. The ``r`` and ``cs`` arguments are required, but may be ``None``, where appropriate for the specific ``Interface`` implementation. Parameters ---------- r : Reactor A reactor to attach to cs : Settings Settings object to use Raises ------ RuntimeError Interfaces derived from Interface must define their name """ if self.name is None: raise RuntimeError("Interfaces derived from Interface must define " "their name ({}).".format(type(self).__name__)) self._enabled = True self.reverseAtEOL = False self._bolForce = False # override disabled flag in interactBOL if true. self.cs = settings.getMasterCs() if cs is None else cs self.r = r self.o = r.o if r else None
def buildOperatorOfEmptyHexBlocks(customSettings=None): """ Builds a operator w/ a reactor object with some hex assemblies and blocks, but all are empty Doesn't depend on inputs and loads quickly. Params ------ customSettings : dict Dictionary of off-default settings to update """ settings.setMasterCs(None) # clear cs = settings.getMasterCs() # fetch new cs["db"] = False # stop use of database if customSettings is not None: cs.update(customSettings) r = tests.getEmptyHexReactor() r.core.setOptionsFromCs(cs) o = operators.Operator(cs) o.initializeInterfaces(r) a = assemblies.HexAssembly("fuel") a.spatialGrid = grids.axialUnitGrid(1) b = blocks.HexBlock("TestBlock") b.setType("fuel") dims = {"Tinput": 600, "Thot": 600, "op": 16.0, "ip": 1, "mult": 1} c = Hexagon("fuel", uZr.UZr(), **dims) b.add(c) a.add(b) a.spatialLocator = r.core.spatialGrid[1, 0, 0] o.r.core.add(a) return o
def setUp(self): self.o, self.r = loadTestReactor(TEST_ROOT) self.cs = settings.getMasterCs() runLog.setVerbosity("extra") self._expandReactor = False self._massScaleFactor = 1.0 if not self._expandReactor: self._massScaleFactor = 3.0
def setUp(self): self.o, self.r = loadTestReactor(TEST_ROOT, {"circularRingMode": True}) self.cs = settings.getMasterCs() runLog.setVerbosity("info") self._expandReactor = False self._massScaleFactor = 1.0 if not self._expandReactor: self._massScaleFactor = 3.0
def __init__(self, r=None, externalCodeInterface=None, fName=None): self.externalCodeInterface = externalCodeInterface self.eci = self.externalCodeInterface self.r = r self.cs = settings.getMasterCs() if fName: self.output = textProcessors.TextProcessor(fName) else: self.output = None self.fName = fName
def xsHistoryVsTime(reactor): r""" Plot cross section history vs. time. Parameters ---------- reactor : armi.reactor.reactors object """ history = reactor.o.getInterface("history") if not history or not history.xsHistory: return colors = itertools.cycle(["b", "g", "r", "c", "m", "y", "k"]) plt.figure() maxbu = 0.0 for typeNum, dataList in history.xsHistory.items(): times = [d[0] for d in dataList] burnups = [d[1] for d in dataList] maxb = max(burnups) if maxb > maxbu: maxbu = maxb xsType = crossSectionGroupManager.getXSTypeLabelFromNumber(typeNum) color = next(colors) plt.plot(times, burnups, color + ".", label="Type {0} XS".format(xsType)) for upperBu in [0.0] + settings.getMasterCs()["buGroups"]: # draw a hline at the limits of each burnup group plt.axhline(y=upperBu) plt.legend() plt.title("Block burnups used to generate XS for {0}".format(reactor.name)) plt.xlabel("Time (years)") plt.ylabel("Burnup (% FIMA)") plt.ylim(0, maxbu * 1.05) figName = (reactor.name + ".bugroups." + settings.getMasterCs()["outputFileExtension"]) plt.savefig(figName) plt.close(1) report.setData("Xs Plot", os.path.abspath(figName), report.XS_PLOT)
def getEmptyHexReactor(cs=None): """Make an empty hex reactor used in some tests.""" cs = cs or settings.getMasterCs() bp = blueprints.Blueprints() reactor = reactors.Reactor(cs, bp) reactor.add(reactors.Core("Core", cs)) reactor.core.spatialGrid = grids.hexGridFromPitch(1.0) reactor.core.spatialGrid.symmetry = geometry.THIRD_CORE + geometry.PERIODIC reactor.core.spatialGrid.geomType = geometry.HEX reactor.core.spatialGrid.armiObject = reactor.core return reactor
def createSIMPLE_HEXZ_NHFLUX(runVariant=False): """ Create NHFLUX file for storage into *fixtures* directory. In order to test the reading of NHFLUX file, there is a need to provide one such file for testing. This function runs the provided DIF3D input, and generates an NHFLUX file. The DIF3D input is a modified version of test case 01 from the DIF3D code package. It uses the 4-group cross sections located in the last part of the input. The modification includes: 1) reduce the geometry to 3 rows of assemblies in full core geometry; 2) reduce the fuel assembly to contain 6 nodes only; 3) change the dimension to be whole numbers. In this way, the produced NHFLUX file is fairly small in size that makes it suitable for testing purposes. Another benefit of the simplified input is the trivial computer running time. It should take < 10 seconds to generate the NHFLUX file. Nevertheless, since the diffusion approximation is likely to be invalid for such a small-size core, results are not meant for physics benchmarking or other realistic applications. .. important:: This requires both DIF3D and the TerraPower's DIF3D ARMI plugin in order to run. The latest output is shipped with the test, but regenerating or updating it will require these extra dependencies. Also, if you don't have them you can ask someone that does and maybe they can hook you up. """ DIF3D_EXE = settings.getMasterCs()["dif3d"] runDir = tempfile.mkdtemp() tempInputPath = os.path.join(runDir, "input") shutil.copy(SIMPLE_HEXZ_INP, tempInputPath) # If running VARIANT, include a Card 12 on A.DIF3D. Runs P1P0 with isotropic cross # sections. if runVariant: with open(tempInputPath, "r") as fIn: dif3dInput = fIn.read() oldString = "UNFORM=A.NIP3" newString = "12 40601 10101 0 0 0 -1 0 0 0 1 0\n" + oldString dif3dInput = re.sub(oldString, newString, dif3dInput, count=1) with open(tempInputPath, "w") as fOut: fOut.write(dif3dInput) with open(tempInputPath, "r") as fIn: _process = subprocess.run(DIF3D_EXE, cwd=runDir, capture_output=True, stdin=fIn) copyName = SIMPLE_HEXZ_NHFLUX if not runVariant else SIMPLE_HEXZ_NHFLUX_VARIANT shutil.copy(os.path.join(runDir, "NHFLUX"), copyName) shutil.rmtree(runDir)
def _checkCsConsistency(self): """Debugging check to verify that CS objects are not unexpectedly multiplying.""" cs = settings.getMasterCs() wrong = (self.cs is not cs) or any((i.cs is not cs) for i in self.interfaces) if wrong: msg = ["Master cs ID is {}".format(id(cs))] for i in self.interfaces: msg.append("{:30s} has cs ID: {:12d}".format(str(i), id(i.cs))) msg.append("{:30s} has cs ID: {:12d}".format(str(self), id(self.cs))) raise RuntimeError("\n".join(msg)) runLog.debug( "Reactors, operators, and interfaces all share master cs: {}".format(id(cs)) )
def test_changeOfCS(self): self.inspector.addQuery( lambda: self.inspector.cs["runType"] == "banane", # german for banana "babababa", "", self.inspector.NO_ACTION, ) query = self.inspector.queries[0] self.assertFalse(query) newCS = settings.getMasterCs().duplicate() newCS["runType"] = "banane" self.inspector.cs = newCS self.assertTrue(query)
def _applyTemperaturesToComponents(reactor): """Update temperatures of block components now that densities have been set. Thermally expand them if there's coupled TH Useful only if the case is being loaded from did NOT have coupled T/H. See Also -------- _setInputComponentTemperatures : deals with loading from coupled TH cases """ from terrapower.physics.thermalHydraulics import thutils if (settings.getMasterCs()["useInputTemperaturesOnDBLoad"] and reactor.o.couplingIsActive()): thutils.applyTemperaturesToComponents(reactor)
def movesVsCycle(reactor, scalars): r""" make a bar chart showing the number of moves per cycle in the full core A move is defined as an assembly being picked up, moved, and put down. So if two assemblies are swapped, that is 2 moves. Note that it does not count temporary storage for such swaps. This is an approximation because in a chain of moves, only one out of the chain would have to be temporarily stored. So as the chains get longer, this approximation gets more accurate. Parameters ---------- scalars : dict The reactor-level params for this case. See Also -------- FuelHandler.outage : sets the number of moves in each cycle """ cycles = [] yvals = [] for moves, cycle in zip(scalars["numMoves"], scalars["cycle"]): if moves is None: moves = 0.0 if cycle not in cycles: # only one move per cycle cycles.append( cycle ) # use the cycles scalar val in case burnSteps is dynamic yvals.append(moves) plt.figure(figsize=(12, 6)) # make it wide and short. plt.bar(cycles, yvals, align="center") if len(cycles) > 1: plt.xticks(cycles) plt.grid(color="0.70") plt.xlabel("Cycle") plt.ylabel("Number of Moves") plt.title("Fuel management rate for " + reactor.name) figName = reactor.name + ".moves." + settings.getMasterCs( )["outputFileExtension"] plt.savefig(figName) plt.close(1) report.setData("Moves Plot", os.path.abspath(figName), report.MOVES_PLOT)
def buildOperatorOfEmptyCartesianBlocks(customSettings=None): """ Builds a operator w/ a reactor object with some Cartesian assemblies and blocks, but all are empty Doesn't depend on inputs and loads quickly. Params ------ customSettings : dict Dictionary of off-default settings to update """ settings.setMasterCs(None) # clear cs = settings.getMasterCs() # fetch new if customSettings is None: customSettings = {} customSettings["db"] = False # stop use of database cs = cs.modified(newSettings=customSettings) settings.setMasterCs(cs) # reset r = tests.getEmptyCartesianReactor() r.core.setOptionsFromCs(cs) o = operators.Operator(cs) o.initializeInterfaces(r) a = assemblies.CartesianAssembly("fuel") a.spatialGrid = grids.axialUnitGrid(1) b = blocks.CartesianBlock("TestBlock") b.setType("fuel") dims = { "Tinput": 600, "Thot": 600, "widthOuter": 16.0, "lengthOuter": 10.0, "widthInner": 1, "lengthInner": 1, "mult": 1, } c = Rectangle("fuel", uZr.UZr(), **dims) b.add(c) a.add(b) a.spatialLocator = r.core.spatialGrid[1, 0, 0] o.r.core.add(a) return o
def buVsTime(reactor, scalars): r""" produces a burnup and DPA vs. time plot for this case Will add a second axis containing DPA if the scalar column maxDPA exists. Parameters ---------- scalars : dict Scalar values for this case """ plt.figure() try: plt.plot(scalars["time"], scalars["maxBuI"], ".-", label="Driver") except ValueError: runLog.warning( "Incompatible axis length in burnup plot. Time has {0}, bu has {1}. Skipping" "".format(len(scalars["time"]), len(scalars["maxBuI"])) ) plt.close(1) return plt.plot(scalars["time"], scalars["maxBuF"], ".-", label="Feed") plt.xlabel("Time (yr)") plt.ylabel("BU (%FIMA)") plt.grid(color="0.70") plt.legend(loc="lower left") title = "Maximum burnup" if scalars["maxDPA"]: plt.twinx() plt.plot(scalars["time"], scalars["maxDPA"], "r--", label="dpa") plt.legend(loc="lower right") plt.ylabel("dpa") title += " and DPA" title += " for " + reactor.name plt.title(title) plt.legend(loc="lower right") figName = reactor.name + ".bu." + settings.getMasterCs()["outputFileExtension"] plt.savefig(figName) plt.close(1) report.setData("Burnup Plot", os.path.abspath(figName), report.BURNUP_PLOT)
def valueVsTime(reactor, x, y, key, yaxis, title, ymin=None): r""" Plots a value vs. time with a standard graph format Parameters ---------- reactor : armi.reactor.reactors object reportGroup : armi.bookkeeping.report.data.Group object x : iterable The x-axis values (the abscissa) y : iterable The y-axis values (the ordinate) key : str A key word to add the item to the report interface yaxis : str The y axis label title : str the plot title ymin : str, optional The minimum y-axis value. If any ordinates are less than this value, it will be ignored. """ plt.figure() plt.plot(x, y, ".-") plt.xlabel("Time (yr)") plt.ylabel(yaxis) plt.grid(color="0.70") plt.title(title + " for {0}".format(reactor.name)) if ymin is not None and all([yi > ymin for yi in y]): # set ymin all values are greater than it and it exists. ax = plt.gca() ax.set_ylim(bottom=ymin) figName = ( reactor.name + "." + key + "." + settings.getMasterCs()["outputFileExtension"] ) plt.savefig(figName) plt.close(1) report.setData("PlotTime", os.path.abspath(figName), report.TIME_PLOT)
def keffVsTime(reactor, time, keff, keffUnc=None, ymin=None): r""" Plots core keff vs. time Parameters ---------- time : list Time in years keff : list Keff in years keffUnc : list, optional Uncontrolled keff or None (will be plotted as secondary series) ymin : float, optional Minimum y-axis value to target. """ plt.figure() if any(keffUnc): label1 = "Controlled k-eff" label2 = "Uncontrolled k-eff" else: label1 = None plt.plot(time, keff, ".-", label=label1) if any(keffUnc): plt.plot(time, keffUnc, ".-", label=label2) plt.legend() plt.xlabel("Time (yr)") plt.ylabel("k-eff") plt.grid(color="0.70") plt.title("k-eff vs. time" + " for {0}".format(reactor.name)) if ymin is not None and all([yi > ymin for yi in keff]): # set ymin all values are greater than it and it exists. ax = plt.gca() ax.set_ylim(bottom=ymin) figName = reactor.name + ".keff." + settings.getMasterCs( )["outputFileExtension"] plt.savefig(figName) plt.close(1) report.setData("K-Eff", os.path.abspath(figName), report.KEFF_PLOT)
def distortionVsTime(reactor, scalars): r"""plots max distortion vs. time if the distortion interface is attached""" dd = reactor.o.getInterface("ductDistortion") if not dd or not "maxSwelling" in dd.__dict__: return # skip plotting plt.figure() plt.plot(scalars["time"], dd.maxTotal, label="Total") plt.plot(scalars["time"], dd.maxCreep, label="Creep") plt.plot(scalars["time"], dd.maxSwelling, label="Swelling") plt.xlabel("Time (yr)") plt.ylabel("Distortion (mm)") plt.grid(color="0.70") plt.legend(loc="lower right") plt.title("Maximum duct distortion for " + reactor.name) figName = reactor.name + ".duct." + settings.getMasterCs()["outputFileExtension"] plt.savefig(figName) plt.close(1) report.setData("Distortion Plot", os.path.abspath(figName), report.DISTORTION_PLOT)
def test_exposeInterfaces(self): """Make sure that the exposeInterfaces hook is properly implemented""" if self.plugin is None: return if not hasattr(self.plugin, "exposeInterfaces"): return cs = settings.getMasterCs() results = self.plugin.exposeInterfaces(cs) # each plugin should return a list self.assertIsInstance(results, list) for result in results: # Make sure that all elements in the list satisfy the constraints of the # hookspec self.assertIsInstance(result, tuple) self.assertEqual(len(result), 3) order, interface, kwargs = result self.assertIsInstance(order, (int, float)) self.assertTrue(issubclass(interface, interfaces.Interface)) self.assertIsInstance(kwargs, dict)
def createSIMPLE_HEXZ_NHFLUX(): """ Create NHFLUX file. In order to test the reading of NHFLUX file, there is a need to provide one such file for testing. This function runs the provided DIF3D input, and generates an NHFLUX file. The DIF3D input is a modified version of test case 01 from the DIF3D code package. It uses the 4-group cross sections located in the last part of the input. The modification includes: 1) reduce the geometry to 3 rows of assemblies in full core geometry; 2) reduce the fuel assembly to contain 6 nodes only; 3) change the dimension to be whole numbers. In this way, the produced NHFLUX file is fairly small in size that makes it suitable for testing purposes. Another benefit of the simplified input is the trivial computer running time. It should take < 10 seconds to generate the NHFLUX file. Nevertheless, since the diffusion approximation is likely to be invalid for such a small-size core, results are not meant for physics benchmarking or other realistic applications. .. important:: This requires both DIF3D and the TerraPower's DIF3D ARMI plugin in order to run. The latest output is shipped with the test, but regenerating or updating it will require these extra dependencies. Also, if you don't have them you can ask someone that does and maybe they can hook you up. """ DIF3D_EXE = settings.getMasterCs().settings["dif3d"].value runDir = tempfile.mkdtemp() shutil.copy(SIMPLE_HEXZ_INP, os.path.join(runDir, "input")) process = subprocess.Popen( "{} < input > output".format(DIF3D_EXE), cwd=runDir, shell=True ) process.communicate() shutil.copy(os.path.join(runDir, "NHFLUX"), SIMPLE_HEXZ_NHFLUX) shutil.rmtree(runDir)
def test_csWorks(self): """Ensure plugin settings become available and have defaults""" a = settings.getMasterCs() self.assertEqual(a["circularRingOrder"], "angle")
def loadUZrAssembly(self, materialModifications): yamlString = self.uZrInput + "\n" + materialModifications design = blueprints.Blueprints.load(yamlString) design._prepConstruction("hex", settings.getMasterCs()) return design.assemblies["fuel a"]
def writeStandardReportTemplate(f, report): f.write(r"<!DOCTYPE html>" + "\n") cs = settings.getMasterCs() with Html(f): with Head(f): f.write(r'<meta charset="UTF-8">' + "\n") with Title(f): f.write(cs.caseTitle) with Body(f): with Div( f, attrs={ "id": "navbar", "class": "navbar navbar-default navbar-fixed-top", }, ): with Div(f, attrs={"class": "container"}): with Div(f, attrs={"class": "navbar-header"}): with Span(f, attrs={ "class": "navbar-text navbar-version pull-left" }): with Img( f, attrs={ "src": encode64( os.path.join( armi.RES, "images", "armiicon.ico")) }, ): pass with A( f, attrs={ "class": "navbar-brand", "href": "#", "style": "color: #d9230f;", }, ): with B(f): f.write(cs.caseTitle) with Span(f, attrs={ "class": "navbar-text navbar-version pull-left" }): with B(f): f.write(armi.USER) with Span(f, attrs={ "class": "navbar-text navbar-version pull-left" }): with B(f): f.write(datetime.datetime.now().isoformat()) with Div(f, attrs={ "class": "container", "style": "padding-top: 20px;" }): with Div(f, attrs={"class": "page-header"}): with H1(f): f.write(report.title) with P(f): f.write(cs["comment"]) report.writeGroupsHTML(f) with Footer( f, attrs= { "style": "width: 100%; border-top: 1px solid #ccc; padding-top: 10px;" }, ): with UL(f, attrs={"class": "list-unstyled"}): with LI(f, attrs={"class": "pull-right"}): with A(f, attrs={"href": "#top"}): f.write("Back to top") with LI(f): with A( f, attrs={ "href": "https://terrapower.github.io/armi/" }, ): f.write("ARMI docs") with P(f): f.write("Automatically generated by ARMI")
def setUp(self): self.o, self.r = loadTestReactor(TEST_ROOT) self.cs = settings.getMasterCs()
def __init__(self, r=None, externalCodeInterface=None, cs=None): self.externalCodeInterface = externalCodeInterface self.eci = externalCodeInterface self.r = r self.cs = cs or settings.getMasterCs()
def workerOperate(self): """ The main loop on any worker MPI nodes. Notes ----- This method is what worker nodes are in while they wait for instructions from the master node in a parallel run. The nodes will sit, waiting for a "worker command". When this comes (from a bcast from the master), a set of if statements are evaluated, with specific behaviors defined for each command. If the operator doesn't understand the command, it loops through the interface stack to see if any of the interfaces understand it. Originally, "magic strings" were broadcast, which were handled either here or in one of the interfaces' ``workerOperate`` methods. Since then, the :py:mod:`~armi.mpiActions` system has been devised which just broadcasts ``MpiAction`` objects. Both methods are still supported. See Also -------- armi.mpiActions : MpiAction information armi.interfaces.workerOperate : interface-level handling of worker commands. """ while True: # sit around waiting for a command from the master runLog.extra("Node {0} ready and waiting".format(armi.MPI_RANK)) cmd = armi.MPI_COMM.bcast(None, root=0) runLog.extra("worker received command {0}".format(cmd)) # got a command. go use it. if isinstance(cmd, mpiActions.MpiAction): cmd.invoke(self, self.r, self.cs) elif cmd == "quit": self.workerQuit() break # If this break is removed, the program will remain in the while loop forever. elif cmd == "finished": runLog.warning( "Received unexpected FINISHED command. Usually a QUIT command precedes this. " "Skipping cleanup of temporary files.") break elif cmd == "sync": # wait around for a sync runLog.debug("Worker syncing") note = armi.MPI_COMM.bcast("wait", root=0) if note != "wait": raise RuntimeError( 'did not get "wait". Got {0}'.format(note)) else: # we don't understand the command on our own. check the interfaces # this allows all interfaces to have their own custom operation code. handled = False for i in self.interfaces: handled = i.workerOperate(cmd) if handled: break if not handled: if armi.MPI_RANK == 0: print("Interfaces" + str(self.interfaces)) runLog.error( "No interface understood worker command {0}\n check stdout for err\n" "available interfaces:\n {1}".format( cmd, "\n ".join("name:{} typeName:{} {}".format( i.name, i.function, i) for i in self.interfaces), )) raise RuntimeError( "Failed to delegate worker command {} to an interface." .format(cmd)) if self._workersShouldResetAfter(cmd): # clear out the reactor on the workers to start anew. # Note: This should build empty non-core systems too. xsGroups = self.getInterface("xsGroups") if xsGroups: xsGroups.clearRepresentativeBlocks() cs = settings.getMasterCs() bp = self.r.blueprints spatialGrid = self.r.core.spatialGrid self.detach() self.r = reactors.Reactor(cs, bp) core = reactors.Core("Core", cs) self.r.add(core) core.spatialGrid = spatialGrid self.reattach(self.r, cs) # might be an mpi action which has a reactor and everything, preventing # garbage collection del cmd gc.collect()