def plotConvertedBlock(self): """Render an image of the converted block.""" figName = self._sourceBlock.name + "_1D_cylinder.svg" runLog.extra( "Plotting equivalent cylindrical block of {} as {}".format( self._sourceBlock, figName ) ) fig, ax = plt.subplots() fig.patch.set_visible(False) ax.patch.set_visible(False) ax.axis("off") patches = [] colors = [] for circleComp in self.convertedBlock: innerR, outerR = ( circleComp.getDimension("id") / 2.0, circleComp.getDimension("od") / 2.0, ) runLog.debug( "Plotting {:40s} with {:10.3f} {:10.3f} ".format( circleComp, innerR, outerR ) ) circle = Wedge((0.0, 0.0), outerR, 0, 360.0, outerR - innerR) patches.append(circle) colors.append(circleComp.density()) colorMap = matplotlib.cm p = PatchCollection(patches, alpha=1.0, linewidths=0.1, cmap=colorMap.rainbow) p.set_array(numpy.array(colors)) p.set_clim(0, 20) ax.add_collection(p) ax.autoscale_view(True, True, True) plt.savefig(figName) return figName
def run(self): """ Run the executer steps. .. warning:: If a calculation requires anything different from what this method does, do not update this method with new complexity! Instead, simply make your own run sequence and/or class. This pattern is useful only in that it is fairly simple. By all means, do use ``DirectoryChanger`` and ``ExecuterOptions`` and other utilities. """ self.options.resolveDerivedOptions() runLog.debug(self.options.describe()) if self.options.executablePath and not os.path.exists( self.options.executablePath): raise IOError( f"Required executable `{self.options.executablePath}` not found for {self}" ) self._performGeometryTransformations() inputs, outputs = self._collectInputsAndOutputs() # must either write input to CWD for analysis and then copy to runDir # or not list it in inputs (for optimization) self.writeInput() with directoryChangers.ForcedCreationDirectoryChanger( self.options.runDir, filesToMove=inputs, filesToRetrieve=outputs) as dc: self.options.workingDir = dc.initial self._execute() output = self._readOutput() if self.options.applyResultsToReactor: output.apply(self.r) self._undoGeometryTransformations() return output
def addNewOutputFileToRetrieve(self, sourceName, destinationName=None): """ Add a new file to the list of files that will be copied to the shared drive after a run. These should be names only, not paths. Parameters ---------- sourceName : str The name of the file in the source location, e.g. `FT06` destinationName : str or None The name of the file in the destination location, e.g. `caseName.dif3d.out` See Also -------- armi.utils.directoryChangers.DirectoryChanger.retrieveFiles : should be used instead of this. """ if destinationName is None: # no name change destinationName = sourceName runLog.debug( "Adding output file {} to list of files to retrieve".format( sourceName)) self._outputFilesNamesToRetrieve.append((sourceName, destinationName))
def _updateReactorParams(self, reactor, dbTimeStep): """Update reactor-/core-level parameters from the database""" # These are the names present in the reactors section of the DB dbParamNames = self.getReactorParamNames() reactorNames = set( pDef.name for pDef in parameters.ALL_DEFINITIONS.forType(reactors.Reactor) ) coreNames = set( pDef.name for pDef in parameters.ALL_DEFINITIONS.forType(reactors.Core) ) for paramName in dbParamNames: if paramName == "TimeStep": continue runLog.debug("Reading scalar {0}".format(paramName)) # get time-ordered list of scalar vals. Pick the relevant one. val = self.readReactorParam(paramName, dbTimeStep)[0] if val is parameters.NoDefault: continue if paramName in ["cycle", "timeNode"]: # int(float('0.000E+00')) works, but int('0.00E+00') val = int(float(val)) if paramName in reactorNames: reactor.p[paramName] = val elif paramName in coreNames: reactor.core.p[paramName] = val else: runLog.warning( 'The parameter "{}" was present in the database, but is not ' "recognized as a Reactor or Core parameter".format(paramName) )
def _updateAssemblyParams(self, reactor, dbTimeStep): """Update assembly-level parameters (and name) from the database""" runLog.debug("Reading assembly-level params of all assemblies") # an assembly-level param table exists. Load from it. loadAssemParamsFromDB = True assemParams = self.getAssemblyParamNames() for removal in ["RowID", "spatialLocator.indices"]: if removal in assemParams: assemParams.remove( removal ) # take out the DB primary so as to not set it. assemParamData = self.readMultipleAssemblyParam( dbTimeStep, assemParams ) # pylint: disable=no-member for a in reactor.core.getAssemblies(): if loadAssemParamsFromDB: ring, pos = a.spatialLocator.getRingPos() for assemParamName in assemParams: val = assemParamData[assemParamName, ring, pos] setParameterWithRenaming(a, assemParamName, val) if assemParamName == "assemNum" and val: # update assembly name based on assemNum name = a.makeNameFromAssemNum(val) a.name = name a.renameBlocksAccordingToAssemblyNum()
def construct(self, blueprint, matMods): """Construct a component""" runLog.debug("Constructing component {}".format(self.name)) kwargs = self._conformKwargs(blueprint, matMods) component = components.factory(self.shape.strip().lower(), [], kwargs) _insertDepletableNuclideKeys(component, blueprint) return component
def plotConvertedBlock(self, fName=None): """Render an image of the converted block.""" runLog.extra("Plotting equivalent cylindrical block of {}".format( self._sourceBlock)) fig, ax = plt.subplots() fig.patch.set_visible(False) ax.patch.set_visible(False) ax.axis("off") patches = [] colors = [] for circleComp in self.convertedBlock: innerR, outerR = ( circleComp.getDimension("id") / 2.0, circleComp.getDimension("od") / 2.0, ) runLog.debug("Plotting {:40s} with {:10.3f} {:10.3f} ".format( circleComp, innerR, outerR)) circle = Wedge((0.0, 0.0), outerR, 0, 360.0, outerR - innerR) patches.append(circle) colors.append(circleComp.density()) colorMap = matplotlib.cm p = PatchCollection(patches, alpha=1.0, linewidths=0.1, cmap=colorMap.YlGn) p.set_array(numpy.array(colors)) ax.add_collection(p) ax.autoscale_view(True, True, True) ax.set_aspect("equal") fig.tight_layout() if fName: plt.savefig(fName) else: plt.show() return fName
def _updateBurnupGroups(self, blockList): """ Update the burnup group of each block based on its burnup If only one burnup group exists, then this is skipped so as to accomodate the possibility of 2-character xsGroup values (useful for detailed V&V models w/o depletion). See Also -------- armi.reactor.blocks.Block.getMicroSuffix """ if self._buGroupUpdatesEnabled and len(self._upperBuGroupBounds) > 1: runLog.debug("Updating burnup groups of {0} blocks".format(len(blockList))) for block in blockList: bu = block.p.percentBu for buGroupIndex, upperBu in enumerate(self._upperBuGroupBounds): if bu <= upperBu: block.p.buGroupNum = buGroupIndex break else: raise ValueError("no bu group found for bu={0}".format(bu)) else: runLog.debug( "Skipping burnup group update of {0} blocks because it is disabled" "".format(len(blockList)) )
def createRepresentativeBlocks(self): """ Get a representative block from each cross section ID managed here. """ representativeBlocks = {} self.avgNucTemperatures = {} self._unrepresentedXSIDs = [] runLog.extra("Generating representative blocks for XS") blockCollectionsByXsGroup = self.makeCrossSectionGroups() for xsID, collection in blockCollectionsByXsGroup.items(): numCandidateBlocks = len(collection.getCandidateBlocks()) if self.xsTypeIsPregenerated(xsID): self._copyPregeneratedXSFile(xsID) continue if numCandidateBlocks > 0: runLog.debug("Creating representative block for {}".format(xsID)) reprBlock = collection.createRepresentativeBlock() representativeBlocks[xsID] = reprBlock self.avgNucTemperatures[xsID] = collection.avgNucTemperatures else: runLog.debug( "No candidate blocks for {} will apply different burnup group".format( xsID ) ) self._unrepresentedXSIDs.append(xsID) self.representativeBlocks = collections.OrderedDict( sorted(representativeBlocks.items()) ) self._modifyUnrepresentedXSIDs(blockCollectionsByXsGroup) self._summarizeGroups(blockCollectionsByXsGroup)
def _getAllFissionProductNames(self): """ Find all fission product names in the problem Considers all LFP collections, whether they be global, block-level, or a mix of these. sets fissionProductNames, a list of nuclide names of all the fission products """ runLog.debug(" Gathering all possible FPs") fissionProductNames = [] lfpCollections = [] # get all possible lfp collections (global + block-level) for b in self.r.core.getBlocks(Flags.FUEL, includeAll=True): lfpCollection = b.getLumpedFissionProductCollection() if lfpCollection and lfpCollection not in lfpCollections: lfpCollections.append(lfpCollection) # get all possible FP names in each LFP collection for lfpCollection in lfpCollections: for fpName in lfpCollection.getAllFissionProductNames(): if fpName not in fissionProductNames: fissionProductNames.append(fpName) self.fissionProductNames = fissionProductNames
def copyOrWarn(fileDescription, sourcePath, destinationPath): """Copy a file, or warn if the file doesn't exist. Parameters ---------- fileDescription : str a description of the file and/or operation being performed. sourcePath : str Path of the file to be copied. destinationPath : str Path for the copied file. """ try: shutil.copy(sourcePath, destinationPath) runLog.debug( "Copied {}: {} -> {}".format(fileDescription, sourcePath, destinationPath) ) except Exception as e: runLog.warning( "Could not copy {} from {} to {}\nError was: {}".format( fileDescription, sourcePath, destinationPath, e ) )
def getRingZoneRings(self): """ Get rings in each ring zone as a list of lists. Returns ------- ringZones : list List of lists. Each entry is the ring numbers in a ring zone. If there are no ring zones defined, returns a list of all rings. """ core = self.core if not self.cs["ringZones"]: # no ring zones defined. Return all rings. return [range(1, core.getNumRings() + 1)] # ringZones are upper limits, defining ring zones from the center. so if they're # [3, 5, 8, 90] then the ring zones are from 1 to 3, 4 to 5, 6 to 8, etc. # AKA, the upper bound is included in that particular zone. # check validity of ringZones. Increasing order and integers. ring0 = 0 for i, ring in enumerate(self.cs["ringZones"]): if ring <= ring0 or not isinstance(ring, int): runLog.warning( "ring zones {0} are invalid. Must be integers, increasing in order. " "Can not return ring zone rings.".format(self.cs["ringZones"]) ) return ring0 = ring if i == len(self.cs["ringZones"]) - 1: # this is the final ring zone if ring < (core.getNumRings() + 1): finalRing = core.getNumRings() else: finalRing = None # modify the ringZones to definitely include all assemblies if finalRing: runLog.debug( "Modifying final ring zone definition to include all assemblies. New max: {0}".format( finalRing ), single=True, label="Modified ring zone definition", ) self.cs["ringZones"][-1] = finalRing # build the ringZone list ring0 = 0 ringZones = [] for upperRing in self.cs["ringZones"]: ringsInThisZone = range( ring0 + 1, upperRing + 1 ) # the rings in this ring zone as defined above. ringZones.append(ringsInThisZone) ring0 = upperRing return ringZones
def setMasterCs(cs): """ Set the master Cs to be the one that is passed in. These are kept track of independently on a PID basis to allow independent multiprocessing. """ Settings.instance = cs runLog.debug("Master cs set to {} with ID: {}".format(cs, id(cs)))
def _constructSpatialGrid(self): """ Build spatial grid. If you do not enter latticeDimensions, a unit grid will be produced which must be adjusted to the proper dimensions (often by inspection of children) at a later time. """ geom = self.geom maxIndex = self._getMaxIndex() runLog.extra("Creating the spatial grid") if geom in (geometry.RZT, geometry.RZ): if self.gridBounds is None: # This check is regrattably late. It would be nice if we could validate # that bounds are provided if R-Theta mesh is being used. raise InputError( "Grid bounds must be provided for `{}` to specify a grid with " "r-theta components.".format(self.name)) for key in ("theta", "r"): if key not in self.gridBounds: raise InputError( "{} grid bounds were not provided for `{}`.".format( key, self.name)) # convert to list, otherwise it is a CommentedSeq theta = numpy.array(self.gridBounds["theta"]) radii = numpy.array(self.gridBounds["r"]) for l, name in ((theta, "theta"), (radii, "radii")): if not _isMonotonicUnique(l): raise InputError( "Grid bounds for {}:{} is not sorted or contains " "duplicates. Check blueprints.".format( self.name, name)) spatialGrid = grids.ThetaRZGrid(bounds=(theta, radii, (0.0, 0.0))) if geom == geometry.HEX: pitch = self.latticeDimensions.x if self.latticeDimensions else 1.0 # add 2 for potential dummy assems spatialGrid = grids.HexGrid.fromPitch(pitch, numRings=maxIndex + 2) elif geom == geometry.CARTESIAN: # if full core or not cut-off, bump the first assembly from the center of # the mesh into the positive values. xw, yw = ((self.latticeDimensions.x, self.latticeDimensions.y) if self.latticeDimensions else (1.0, 1.0)) isOffset = (self.symmetry and geometry.THROUGH_CENTER_ASSEMBLY not in self.symmetry) spatialGrid = grids.CartesianGrid.fromRectangle(xw, yw, numRings=maxIndex + 1, isOffset=isOffset) runLog.debug("Built grid: {}".format(spatialGrid)) # set geometric metadata on spatialGrid. This information is needed in various # parts of the code and is best encapsulated on the grid itself rather than on # the container state. spatialGrid.geomType = self.geom spatialGrid.symmetry = self.symmetry return spatialGrid
def _cacheLFPDensities(self, blockList): # pass 2: Cache all LFP densities for all blocks (so they aren't read # for each FP) runLog.debug(" Caching LFP densities of all blocks") lfpDensities = {} for b in blockList: for lfpName in self._globalLFPs: lfpDensities[lfpName, b.getName()] = b.getNumberDensity(lfpName) return lfpDensities
def writeHTML(self): """Renders this report as a standalone HTML file""" filename = "{}.html".format(self.title) runLog.debug("Writing HTML document {}.".format(filename)) with html.HTMLFile(filename, "w") as f: html.writeStandardReportTemplate(f, self) runLog.info("HTML document {} written".format(filename))
def createMacrosFromMicros(self, microLibrary, block, nucNames=None, libType="micros"): """ Creates a macroscopic cross section set based on a microscopic XS library using a block object Micro libraries have lots of nuclides, but macros only have 1. Parameters ---------- microLibrary : xsCollection.XSCollection Input micros block : Block Object whos number densities should be used to generate macros nucNames : list, optional List of nuclides to include in the macros. Defaults to all in block. libType : str, optional The block attribute containing the desired microscopic XS for this block: either "micros" for neutron XS or "gammaXS" for gamma XS. Returns ------- macros : xsCollection.XSCollection A new XSCollection full of macroscopic cross sections """ runLog.debug( "Building macroscopic cross sections for {0}".format(block)) if nucNames is None: nucNames = block.getNuclides() self.microLibrary = microLibrary self.block = block self.xsSuffix = block.getMicroSuffix() self.macros = XSCollection(parent=block) self.densities = dict( zip(nucNames, block.getNuclideNumberDensities(nucNames))) self.ng = getattr(self.microLibrary, "numGroups" + _getLibTypeSuffix(libType)) self._initializeMacros() self._convertBasicXS(libType=libType) self._computeAbsorptionXS() self._convertScatterMatrices(libType=libType) self._computeDiffusionConstants() self._buildTotalScatterMatrix() self._computeRemovalXS() self.macros.chi = computeBlockAverageChi(b=self.block, isotxsLib=self.microLibrary) return self.macros
def __exit__(self, exc_type, exc_value, traceback): """At the termination of a with command, navigate back to the original directory.""" runLog.debug("Returning to directory {}".format(self.initial)) if exc_type is not None and self._dumpOnException: runLog.info("An exception was raised within a DirectoryChanger. " "Retrieving entire folder for debugging.") self._retrieveEntireFolder() else: self.retrieveFiles() self.close()
def validate(self): """ Performs validation checks on the inputs and provides warnings for option inconsistencies. Raises ------ ValueError When the mutually exclusive ``fileLocation`` and ``geometry`` attributes are provided or when neither are provided. """ # Check for valid inputs when the file location is supplied. if self.fileLocation is None and self.geometry is None: raise ValueError(f"{self} is missing a geometry input or a file location.") if self.fileLocation is not None and self.geometry is not None: runLog.warning( f"Either file location or geometry inputs in {self} should be given, but not both. " "The file location setting will take precedence over the geometry inputs. " "Remove one or the other in the `crossSectionSettings` input to fix this warning." ) invalids = [] if self.fileLocation is not None: for var, val in self: # Skip these attributes since they are valid options # when the ``fileLocation`` attribute`` is set. if var in [CONF_XSID, CONF_FILE_LOCATION, CONF_BLOCK_REPRESENTATION]: continue if val is not None: invalids.append((var, val)) if invalids: runLog.debug( f"The following inputs in {self} are not valid when the file location is set:" ) for var, val in invalids: runLog.debug(f"\tAttribute: {var}, Value: {val}") # Check for valid inputs when the geometry is supplied. invalids = [] if self.geometry is not None: validOptions = _VALID_INPUTS_BY_GEOMETRY_TYPE[self.geometry] for var, val in self: if var not in validOptions and val is not None: invalids.append((var, val)) if invalids: runLog.debug( f"The following inputs in {self} are not valid when `{self.geometry}` geometry type is set:" ) for var, val in invalids: runLog.debug(f"\tAttribute: {var}, Value: {val}") runLog.debug( f"The valid options for the `{self.geometry}` geometry are: {validOptions}" )
def _getLinkedComponents(self, b, c): """retrieve the axial linkage for component c Parameters ---------- b : :py:class:`Block <armi.reactor.blocks.Block>` object key to access blocks containing linked components c : :py:class:`Component <armi.reactor.components.component.Component>` object component to determine axial linkage for Raises ------ RuntimeError multiple candidate components are found to be axially linked to a component """ lstLinkedC = [None, None] for ib, linkdBlk in enumerate(self.linkedBlocks[b]): if linkdBlk is not None: for otherC in linkdBlk.getChildren(): if _determineLinked(c, otherC): if lstLinkedC[ib] is not None: errMsg = ( "Multiple component axial linkages have been found for " "Component {0}; Block {1}; Assembly {2}." " This is indicative of an error in the blueprints! Linked components found are" "{3} and {4}".format( c, b, b.parent, lstLinkedC[ib], otherC ) ) runLog.error(msg=errMsg) raise RuntimeError(errMsg) lstLinkedC[ib] = otherC self.linkedComponents[c] = lstLinkedC if lstLinkedC[0] is None: runLog.debug( "Assembly {0:22s} at location {1:22s}, Block {2:22s}, Component {3:22s} " "has nothing linked below it!".format( str(self.a.getName()), str(self.a.getLocation()), str(b.p.flags), str(c.p.flags), ) ) if lstLinkedC[1] is None: runLog.debug( "Assembly {0:22s} at location {1:22s}, Block {2:22s}, Component {3:22s} " "has nothing linked above it!".format( str(self.a.getName()), str(self.a.getLocation()), str(b.p.flags), str(c.p.flags), ) )
def _getLinkedBlocks(self, b): """retrieve the axial linkage for block b Parameters ---------- b : :py:class:`Block <armi.reactor.blocks.Block>` object block to determine axial linkage for NOTES ----- - block linkage is determined by matching ztop/zbottom (see below) - block linkage is stored in self.linkedBlocks[b] _ _ | | | 2 | Block 2 is linked to block 1. |_ _| | | | 1 | Block 1 is linked to both block 0 and 1. |_ _| | | | 0 | Block 0 is linked to block 1. |_ _| """ lowerLinkedBlock = None upperLinkedBlock = None block_list = self.a.getChildren() for otherBlk in block_list: if b.name != otherBlk.name: if b.p.zbottom == otherBlk.p.ztop: lowerLinkedBlock = otherBlk elif b.p.ztop == otherBlk.p.zbottom: upperLinkedBlock = otherBlk self.linkedBlocks[b] = [lowerLinkedBlock, upperLinkedBlock] if lowerLinkedBlock is None: runLog.debug( "Assembly {0:22s} at location {1:22s}, Block {2:22s}" "is not linked to a block below!".format( str(self.a.getName()), str(self.a.getLocation()), str(b.p.flags), ) ) if upperLinkedBlock is None: runLog.debug( "Assembly {0:22s} at location {1:22s}, Block {2:22s}" "is not linked to a block above!".format( str(self.a.getName()), str(self.a.getLocation()), str(b.p.flags), ) )
def _clearStateOnSourceReactor(self): """Delete existing state that will be updated so they don't increment.""" runLog.debug( "Clearing params from source reactor that will be converted.") for rp in self.reactorParamNames: self._sourceReactor.core.p[rp] = 0.0 for b in self._sourceReactor.core.getBlocks(): b.p.mgFlux = [] b.p.adjMgFlux = [] for bp in self.blockParamNames: b.p[bp] = 0.0
def expandFissionProducts(massFrac, lumpedFissionProducts): """ expands lumped fission products in a massFrac vector Parameters ---------- massFrac : dict lumpedFissionProducts : LumpedFissionProductCollection (acts like a dict) result of <fissionProductInterface>.getGlobalLumpedFissionProducts Returns ------- newMassFracs : dict """ lfpNbs = [] elementalNbs = [] newMassFrac = {} for nucName in massFrac.keys(): nB = nuclideBases.byName[nucName] if isinstance(nB, nuclideBases.LumpNuclideBase): lfpNbs.append(nB) elif isinstance(nB, nuclideBases.NaturalNuclideBase): elementalNbs.append(nB) else: newMassFrac[nucName] = massFrac[nucName] for lfp in lfpNbs: if massFrac[lfp.name] != 0: try: lfpFP = lumpedFissionProducts[lfp.name] except KeyError: errorMessage = ["{}".format(lumpedFissionProducts)] errorMessage.append("\ntype {}".format( type(lumpedFissionProducts))) errorMessage.append("\nmassFrac dict {}".format(massFrac)) errorMessage.append("\nLumped Fission Product Name {}".format( lfp.name)) runLog.debug("".join(errorMessage)) for nB in lfpFP.keys(): newMassFrac[nB.name] = massFrac.get( nB.name, 0) + massFrac[lfp.name] * lfpFP.getMassFrac(nuclideBase=nB) for element in elementalNbs: for nB in element.getNaturalIsotopics(): newMassFrac[nB.name] = (massFrac.get(nB.name, 0) + massFrac[element.name] * nB.abundance * nB.weight / element.weight) return newMassFrac
def addDummyNuclidesToLibrary(lib, dummyNuclides): """ This method adds DUMMY nuclides to the current GAMISO library. Parameters ---------- lib : obj GAMISO library object dummyNuclides: list List of DUMMY nuclide objects that will be copied and added to the GAMISO file Notes ----- Since MC2-3 does not write DUMMY nuclide information for GAMISO files, this is necessary to provide a consistent set of nuclide-level data across all the nuclides in a :py:class:`~armi.nuclearDataIO.xsLibraries.XSLibrary`. """ if not dummyNuclides: runLog.important( "No dummy nuclide data provided to be added to {}".format(lib)) return False elif len(lib.xsIDs) > 1: runLog.warning( "Cannot add dummy nuclide data to GAMISO library {} containing data for more than 1 XS ID." .format(lib)) return False dummyNuclideKeysAddedToLibrary = [] for dummyNuclide in dummyNuclides: dummyKey = dummyNuclide.nucLabel if len(lib.xsIDs): dummyKey += lib.xsIDs[0] if dummyKey in lib: continue runLog.debug("Adding {} nuclide data to {}".format(dummyKey, lib)) newDummy = xsNuclides.XSNuclide(lib, dummyKey) # Copy gamiso metadata from the isotxs metadata of the given dummy nuclide for kk, vv in dummyNuclide.isotxsMetadata.items(): if kk in ["jj", "jband"]: newDummy.gamisoMetadata[kk] = {} for mm in vv: newDummy.gamisoMetadata[kk][mm] = 1 else: newDummy.gamisoMetadata[kk] = vv lib[dummyKey] = newDummy dummyNuclideKeysAddedToLibrary.append(dummyKey) return any(dummyNuclideKeysAddedToLibrary)
def _checkBlockHeight(b): if b.p.height < 3.0: runLog.debug( "Block {0:s} ({1:s}) has a height less than 3.0 cm. ({2:.12e})".format( b.name, str(b.p.flags), b.p.height ) ) if b.p.height < 0.0: raise ArithmeticError( "Block {0:s} ({1:s}) has a negative height! ({2:.12e})".format( b.name, str(b.p.flags), b.p.height ) )
def test_setVerbosityBeforeStartLog(self): """The user/dev my accidentally call setVerbosity() before startLog(), this should be mostly supportable""" with mockRunLogs.BufferLog() as mock: # we should start with a clean slate self.assertEqual("", mock._outputStream) runLog.LOG.setVerbosity(logging.DEBUG) runLog.LOG.startLog("test_setVerbosityBeforeStartLog") # we should start at info level, and that should be working correctly self.assertEqual(runLog.LOG.getVerbosity(), logging.DEBUG) runLog.debug("hi") self.assertIn("hi", mock._outputStream) mock._outputStream = ""
def _clearStateOnReactor(self, reactor): """ Delete existing state that will be updated so they don't increment. The summations should start at zero but will happen for all overlaps. """ runLog.debug("Clearing params from source reactor that will be converted.") for rp in self.reactorParamNames: reactor.core.p[rp] = 0.0 for b in reactor.core.getBlocks(): for bp in self.blockParamNames: b.p[bp] = 0.0
def _setParamsToUpdate(self): b = self._sourceReactor.core.getFirstBlock() self.blockParamNames = b.p.paramDefs.inCategory( "detailedAxialExpansion").names self.reactorParamNames = self._sourceReactor.core.p.paramDefs.inCategory( "neutronics").names runLog.debug("Block params that will be converted include: {0}".format( self.blockParamNames)) runLog.debug( "Reactor params that will be converted include: {0}".format( self.reactorParamNames))
def _updateYieldVectorFromNumberDensities(self, numberDensities, fpFiltered=False): """ This method updates the yield distribution of the first lfp to reflect whatever is on the massFrac vector Parameters ---------- numberDensities : dict This is a <material>.p.massFrac format mass fraction vector indexed by nuclide name fpFiltered : bool This is a flag to let this method know whether it needs to filter the mass fraction vector for fission products """ lfp = self.getFirstLfp() lfpNumberFrac = lfp.getNumberFracs() if fpFiltered: fpNumberDensities = numberDensities else: fpNumberDensities = {} # filter massFracs for only fission products lfpNumberDensity = numberDensities.get(self.getName(), 0) for nucName in sorted(self.getAllFissionProductNames()): nb = nuclideBases.byName[nucName] fpNumberDensities[nb] = numberDensities.get( nucName, 0) + lfpNumberDensity * lfpNumberFrac.get(nb, 0) totalFPNumberDensity = sum(fpNumberDensities.values()) if totalFPNumberDensity: # check to see that you want to update the yields AND that there is # a distribution of fission products -- at BOL this has a zero # division bc there are no fission products. for nb in lfp.keys(): lfp[nb] = (fpNumberDensities.get(nb, 0) / totalFPNumberDensity ) * 2.0 # part of ARMI task T331 else: runLog.debug( "fpMassFrac vector should be populated -- not updating the yield vector" ) # update the weight on the nuclide base object # This is a GLOBAL operation, which is a bit problematic if it # is being changed and should be upgraded accordingly. nb = nuclideBases.byName[lfp.name] nb.weight = (2 * sum([yld * nb.weight for nb, yld in lfp.yld.items()]) / sum([yld for yld in self.getFirstLfp().values()]))
def temporarilySet(self, settingName, temporaryValue): """ Change a setting that you will restore later. Useful to change settings before doing a certain run and then reverting them See Also -------- unsetTemporarySettings : reverts this """ runLog.debug("Temporarily changing {} from {} to {}".format( settingName, self[settingName], temporaryValue)) self._backedup[settingName] = self[settingName] self[settingName] = temporaryValue