def _expandElementMassFracs(self): """ Expand the custom isotopics input entries that are elementals to isotopics. This is necessary when the element name is not a elemental nuclide. Most everywhere else expects Nuclide objects (or nuclide names). This input allows a user to enter "U" which would expand to the naturally occurring uranium isotopics. This is different than the isotopic expansion done for meeting user-specified modeling options (such as an MC**2, or MCNP expecting elements or isotopes), because it translates the user input into something that can be used later on. """ elementsToExpand = [] for nucName in self.massFracs: if nucName not in nuclideBases.byName: element = elements.bySymbol.get(nucName) if element is not None: runLog.info( "Expanding custom isotopic `{}` element `{}` to natural isotopics" .format(self.name, nucName)) # include all natural isotopes with None flag elementsToExpand.append((element, None)) else: raise InputError( "Unrecognized nuclide/isotope/element in input: {}". format(nucName)) densityTools.expandElementalMassFracsToNuclides( self.massFracs, elementsToExpand)
def _moveFiles(self): # check for orificed flow bounds files. These will often be named based on the # case that this one is dependent upon, but not always. For example, testSassys # is dependent on the safety case but requires physics bounds files. now copy # the files over copyFilesFrom = [ filePath for possiblePat in self.cs["copyFilesFrom"] for filePath in glob.glob(possiblePat) ] copyFilesTo = self.cs["copyFilesTo"] if len(copyFilesTo) in (len(copyFilesFrom), 0, 1): # if any files to copy, then use the first as the default, i.e. len() == 1, # otherwise assume '.' default = copyFilesTo[0] if any(copyFilesTo) else "." for filename, dest in itertools.zip_longest(copyFilesFrom, copyFilesTo, fillvalue=default): pathTools.copyOrWarn("copyFilesFrom", filename, dest) else: runLog.error( "cs['copyFilesTo'] must either be length 1, 0, or have the same number of entries as " "cs['copyFilesFrom']. Actual values:\n" " copyFilesTo : {}\n" " copyFilesFrom : {}".format(copyFilesTo, copyFilesFrom)) raise InputError("Failed to process copyFilesTo/copyFilesFrom")
def gridContents(self, value): # pylint: disable=method-hidden if value is None: return True if not all(isinstance(key, tuple) for key in value.keys()): raise InputError( "Keys need to be presented as [i, j]. Check the blueprints." )
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 ) )
def _initializeMassFracs(self): self.massFracs = dict() # defaults to 0.0, __init__ is not called if any(v < 0.0 for v in self.values()): raise ValueError("Custom isotopic input for {} is negative".format( self.name)) valSum = sum(self.values()) if not abs(valSum - 1.0) < 1e-5 and "fractions" in self.inputFormat: raise ValueError( "Fractional custom isotopic input values must sum to 1.0 in: {}" .format(self.name)) if self.inputFormat == "number fractions": sumNjAj = 0.0 for nuc, nj in self.items(): if nj: sumNjAj += nj * nucDir.getAtomicWeight(nuc) for nuc, value in self.items(): massFrac = value * nucDir.getAtomicWeight(nuc) / sumNjAj self.massFracs[nuc] = massFrac elif self.inputFormat == "number densities": if self._density is not None: raise InputError( "Custom isotopic `{}` is over-specified. It was provided as number " "densities, and but density ({}) was also provided. Is the input format " "correct?".format(self.name, self.density)) M = { nuc: Ni / units.MOLES_PER_CC_TO_ATOMS_PER_BARN_CM * nucDir.getAtomicWeight(nuc) for nuc, Ni in self.items() } densityTotal = sum(M.values()) if densityTotal < 0: raise ValueError("Computed density is negative") for nuc, Mi in M.items(): self.massFracs[nuc] = Mi / densityTotal self._computedDensity = densityTotal elif self.inputFormat == "mass fractions": self.massFracs = dict(self) # as input else: raise ValueError( "Unrecognized custom isotopics input format {}.".format( self.inputFormat))
def autoSelectElementsToKeepFromSettings(cs): """ Intelligently choose elements to expand based on settings. If settings point to a particular code and library and we know that combo requires certain elementals to be expanded, we flag them here to make the user input as simple as possible. This determines both which elementals to keep and which specific expansion subsets to use. Notes ----- This logic is expected to be moved to respective plugins in time. Returns ------- elementalsToKeep : set Set of NaturalNuclideBase instances to not expand into natural isotopics. expansions : dict Element to list of nuclides for expansion. For example: {oxygen: [oxygen16]} indicates that all oxygen should be expanded to O16, ignoring natural O17 and O18. (variables are Natural/NuclideBases) """ elementalsToKeep = set() oxygenElementals = [nuclideBases.byName["O"]] hydrogenElementals = [nuclideBases.byName[name] for name in ["H"]] endf70Elementals = [nuclideBases.byName[name] for name in ["C", "V", "ZN"]] endf71Elementals = [nuclideBases.byName[name] for name in ["C"]] endf80Elementals = [] elementalsInMC2 = set() expansionStrings = {} mc2Expansions = { "HE": ["HE4"], # neglect HE3 "O": ["O16"], # neglect O17 and O18 "W": ["W182", "W183", "W184", "W186"], # neglect W180 } mcnpExpansions = {"O": ["O16"]} for element in elements.byName.values(): # any NaturalNuclideBase that's available in MC2 libs nnb = nuclideBases.byName.get(element.symbol) if nnb and nnb.mc2id: elementalsInMC2.add(nnb) if "MCNP" in cs["neutronicsKernel"]: expansionStrings.update(mcnpExpansions) if int(cs["mcnpLibrary"]) == 50: elementalsToKeep.update(nuclideBases.instances) # skip expansion # ENDF/B VII.0 elif 70 <= int(cs["mcnpLibrary"]) <= 79: elementalsToKeep.update(endf70Elementals) # ENDF/B VII.1 elif 80 <= int(cs["mcnpLibrary"]) <= 89: elementalsToKeep.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"]: elementalsToKeep.update(endf70Elementals) expansionStrings.update(mc2Expansions) elif cs["xsKernel"] == "DRAGON": # Users need to use default nuclear lib name. This is documented. dragLib = cs["dragonDataPath"] # only supports ENDF/B VII/VIII at the moment. if "7r0" in dragLib: elementalsToKeep.update(endf70Elementals) elif "7r1" in dragLib: elementalsToKeep.update(endf71Elementals) elif "8r0" in dragLib: elementalsToKeep.update(endf80Elementals) elementalsToKeep.update(hydrogenElementals) elementalsToKeep.update(oxygenElementals) else: raise ValueError( f"Unrecognized DRAGLIB name: {dragLib} Use default file name.") elif cs["xsKernel"] == "MC2v2": # strip out any NaturalNuclideBase with no mc2id (not on mc2Nuclides.yaml) elementalsToKeep.update(elementalsInMC2) expansionStrings.update(mc2Expansions) # convert convenient string notation to actual NuclideBase objects expansions = {} for nnb, nbs in expansionStrings.items(): expansions[nuclideBases.byName[nnb]] = [ nuclideBases.byName[nb] for nb in nbs ] return elementalsToKeep, expansions
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. """ symmetry = ( geometry.SymmetryType.fromStr(self.symmetry) if self.symmetry else None ) 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 regrettably 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 in (geometry.HEX, geometry.HEX_CORNERS_UP): pitch = self.latticeDimensions.x if self.latticeDimensions else 1.0 # add 2 for potential dummy assems spatialGrid = grids.HexGrid.fromPitch( pitch, numRings=maxIndex + 2, pointedEndUp=geom == geometry.HEX_CORNERS_UP, ) 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) ) # Specifically in the case of grid blueprints, where we have grid contents # available, we can also infer "through center" based on the contents. # Note that the "through center" symmetry check cannot be performed when # the grid contents has not been provided (i.e., None or empty). if self.gridContents and symmetry.domain == geometry.DomainType.FULL_CORE: nx, ny = _getGridSize(self.gridContents.keys()) if nx == ny and nx % 2 == 1: symmetry.isThroughCenterAssembly = True isOffset = symmetry is not None and not symmetry.isThroughCenterAssembly 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: str = str(self.geom) self.symmetry = str(symmetry) spatialGrid._symmetry: str = self.symmetry return spatialGrid