def loadYaml(self, filePath, strict=True): """ Update the settings based on the contents of the yaml file .. versionadded:: 0.2.0 Parameters ---------- filePath: str, or FileType Path to config file strict: bool Fail at the first incorrect setting. If false, failed settings will not be loaded and alerts will be raised Raises ------ KeyError or TypeError If settings found in the config file are not valid FileNotFound or OSError If the file does not exist """ messages.debug('Attempting to read from {}'.format(filePath)) with open(filePath) as yFile: configSettings = safe_load(yFile) messages.debug( 'Loading settings onto object with strict:{}'.format(strict)) for key, value in iteritems(configSettings): if isinstance(value, dict): self.__recursiveLoad(value, strict, key) else: self.__safeLoad(key, value, strict) messages.info('Done')
def setValue(self, name, value): """Set the value of a specific setting. Parameters ---------- name: str Full name of the setting value: value to be set Raises ------ KeyError If the value is not one of the allowable options or if the setting does not match an existing setting TypeError If the value is not of the correct type """ if self.__inside: self.__originals[name] = self[name] if name not in self: raise KeyError('Setting {} does not exist'.format(name)) self._defaultLoader[name].validate(value) # if we've made it here, then the value is valid if self._defaultLoader[name].updater is not None: value = self._defaultLoader[name].updater(value) dict.__setitem__(self, name, value) messages.debug('Updated setting {} to {}'.format(name, value))
def inferReader(filePath): """ Attempt to infer the correct reader type. Parameters ---------- filePath: str or path-like File to be read. Raises ------ SerpentToolsException If a reader cannot be inferred """ filePath = str(filePath) for reg, reader in REGEXES.items(): match = re.match(reg, filePath) if match and match.group() == filePath: debug('Inferred reader for {}: {}' .format(filePath, reader.__name__)) return reader raise SerpentToolsException( 'Failed to infer filetype and thus accurate reader from' 'file path {}. Pass one of the below options to ensure ' 'a specific reader:\n{}'.format(filePath, SUPPORTED_READER_MSG) )
def __safeLoad(self, key, value, strict): messages.debug('Attempting to set setting {} to {}'.format(key, value)) try: self.setValue(key, value) except (KeyError, TypeError) as error: if strict: raise error else: messages.error(str(error))
def __processDet(self, name, grids): """Add this detector with it's grid data to the reader.""" if not self._loadAll and name in self.settings['names']: debug( "Skipping detector {} due to setting <detector.names>".format( name)) return if name in self.detectors: raise KeyError("Detector {} already stored on reader".format(name)) self.detectors[name] = detectorFactory(name, grids)
def _postcheck(self): """ensure the parser grabbed expected materials.""" if not self.materials: error("No materials obtained from {}".format(self.filePath)) return for mKey, mat in iteritems(self.materials): assert isinstance(mat, DepletedMaterial), ( 'Unexpected key {}: {} in materials dictionary'.format( mKey, type(mat))) debug(' found {} materials'.format(len(self.materials)))
def read(self): """Read all the files and create parser objects""" self._readAll() if not self.settings['skipPrecheck']: self._precheck() else: debug('Skipping pre-check') self.process() if self.settings['freeAll']: info("Removing all parsers and containers from memory since " "setting <sampler.freeAll> is ``True``") self.free()
def getUniv(self, univ, burnup=None, index=None, timeDays=None): """ Return a specific universe given the ID and time of interest If more than one time parameter is given, the hierarchy of search is: index (highest priority), burnup, timeDays (lowest priority) Parameters ---------- univ: str Unique str for the desired universe burnup: float or int Burnup [MWd/kgU] of the desired universe timeDays: float or int Time [days] of the desired universe index: int Point of interest in the burnup/days index Returns ------- :py:class:`~serpentTools.objects.containers.HomogUniv` Requested universe Raises ------ KeyError: If the requested universe could not be found SerpentToolsException: If burnup, days and index are not given """ if index is None and burnup is None and timeDays is None: raise SerpentToolsException( 'Burnup, time or index are required inputs') searchIndex = (2 if index is not None else 1 if burnup is not None else 3) searchValue = (index if index is not None else burnup if burnup is not None else timeDays) if searchIndex == 2 and searchValue < 1: # index must be non-zero raise KeyError( 'Index read is {}, however only integers above zero are ' 'allowed'.format(searchValue)) for key, dictUniv in iteritems(self.universes): if key[0] == univ and key[searchIndex] == searchValue: debug('Found universe that matches with keys {}'.format(key)) return self.universes[key] searchName = ('index ' if index else 'burnup ' if burnup else 'timeDays ') raise KeyError( 'Could not find a universe that matched requested universe {} and ' '{} {}'.format(univ, searchName, searchValue))
def reshape(self): """ Reshape the tally data into a multidimensional array This method reshapes the tally and uncertainty data into arrays where the array axes correspond to specific bin types. If a detector was set up to tally two group flux in a 5 x 5 xy mesh, then the resulting tally data would be in a 50 x 12/13 matrix in the original ``detN.m`` file. The tally data and relative error would be rebroadcasted into 2 x 5 x 5 arrays, and the indexing information is stored in ``self.indexes`` Returns ------- shape: list Dimensionality of the resulting array Raises ------ SerpentToolsException: If the bin data has not been loaded """ if self.bins is None: raise SerpentToolsException('Tally data for detector {} has not ' 'been loaded'.format(self.name)) if self.__reshaped: warning('Data has already been reshaped') return debug('Starting to sort tally data...') shape = [] self.indexes = OrderedDict() for index, indexName in enumerate(DET_COLS): if 0 < index < 10: uniqueVals = unique(self.bins[:, index]) if len(uniqueVals) > 1: self.indexes[indexName] = array(uniqueVals, dtype=int) - 1 shape.append(len(uniqueVals)) self.tallies = self.bins[:, 10].reshape(shape) self.errors = self.bins[:, 11].reshape(shape) if self.bins.shape[1] == 13: self.scores = self.bins[:, 12].reshape(shape) self._map = { 'tallies': self.tallies, 'errors': self.errors, 'scores': self.scores } debug('Done') self.__reshaped = True return shape
def _processBranchBlock(self): fromHeader = self._processHeader() if fromHeader is None: return thisBranch, totUniv = fromHeader burnup, burnupIndex = self._advance()[:-1] self._whereAmI['buIndx'] = int(burnupIndex) for univNum in range(totUniv): self._whereAmI['universe'] = univNum debug( 'Reading run {runIndx} of {coefIndx} - universe {universe} at ' 'burnup step {buIndx}'.format(**self._whereAmI)) self._processBranchUniverses(thisBranch, float(burnup), self._whereAmI['buIndx'])
def _processChunk(self, chunk, name, variable): if (self.settings['materialVariables'] and variable not in self.settings['materialVariables']): return if name not in self.materials: debug('Adding material {}...'.format(name)) self.materials[name] = DepletedMaterial(name, self.metadata) if len(chunk) == 1: # single line values, e.g. volume or burnup cleaned = self._cleanSingleLine(chunk) else: cleaned = [] for line in chunk: if '[' in line or ']' in line: continue cleaned.append(line[:line.index('%')]) self.materials[name].addData(variable, cleaned)
def _addDetector(self, chunk, detName, binType): if binType is None: data = empty(shape=(len(chunk), NUM_COLS)) else: data = empty(shape=(len(chunk), len(chunk[0].split()))) for indx, line in enumerate(chunk): data[indx] = [float(xx) for xx in line.split()] if detName not in self.detectors: # new detector, this data is the tallies detector = Detector(detName) detector.addTallyData(data) self.detectors[detName] = detector debug('Adding detector {}'.format(detName)) return # detector has already been defined, this must be a mesh detector = self.detectors[detName] detector.grids[binType] = asfortranarray(data) debug('Added bin data {} to detector {}'.format(binType, detName))
def read(self): """Read through the depletion file and store requested data.""" messages.info('Preparing to read {}'.format(self.filePath)) keys = ['MAT', 'TOT'] if self.settings['processTotal'] else ['MAT'] keys.extend(self.settings['metadataKeys']) separators = ['\n', '];', '\r\n'] with KeywordParser(self.filePath, keys, separators) as parser: for chunk in parser.yieldChunks(): if 'MAT' in chunk[0]: self._addMaterial(chunk) elif 'TOT' in chunk[0]: self._addTotal(chunk) else: self._addMetadata(chunk) if 'days' in self.metadata: for mKey in self.materials: self.materials[mKey].days = self.metadata['days'] messages.info('Done reading depletion file') messages.debug(' found {} materials'.format(len(self.materials)))
def _processBranchUniverses(self, branch, burnup, burnupIndex): """Add universe data to this branch at this burnup.""" unvID, numVariables = [int(xx) for xx in self._advance()] univ = branch.addUniverse(unvID, burnup, burnupIndex - 1) for step in range(numVariables): splitList = self._advance( possibleEndOfFile=step == numVariables - 1) varName = splitList[0] varValues = [float(xx) for xx in splitList[2:]] if not varValues: debug("No data present for variable {}. Skipping" .format(varName)) continue if self._checkAddVariable(varName): if self._hasUncs: vals, uncs = splitValsUncs(varValues) univ.addData(varName, array(vals), uncertainty=False) univ.addData(varName, array(uncs), uncertainty=True) else: univ.addData(varName, array(varValues), uncertainty=False)
def addData(self, variable, rawData): """ Add data straight from the file onto a variable. Parameters ---------- variable: str Name of the variable directly from ``SERPENT`` rawData: list List of strings corresponding to the raw data from the file """ newName = convertVariableName(variable) debug('Adding {} data to {}'.format(newName, self.name)) if isinstance(rawData, str): scratch = [float(item) for item in rawData.split()] else: scratch = [] for line in rawData: if line: scratch.append([float(item) for item in line.split()]) self.data[newName] = numpy.array(scratch)
def getUniv(self, univID, burnup=None, index=None): """ Return a specific universe given the ID and time of interest If burnup and index are given, burnup is used to search Parameters ---------- univID: int Unique ID for the desired universe burnup: float or int Burnup [MWd/kgU] of the desired universe index: int Point of interest in the burnup index Returns ------- :py:class:`~serpentTools.objects.containers.HomogUniv` Requested universe Raises ------ KeyError: If the requested universe could not be found SerpentToolsException: If neither burnup nor index are given """ if burnup is None and index is None: raise SerpentToolsException('Burnup or index are required inputs') searchIndex = 2 if index is not None else 1 searchValue = index if index is not None else burnup for key in self.__keys: if key[0] == univID and key[searchIndex] == searchValue: debug('Found universe that matches with keys {}'.format(key)) return self.universes[key] searchName = 'burnup' + ('' if index is None else ' index') raise KeyError( 'Could not find a universe that matched requested universe {} and ' '{} {}'.format(univID, searchName, searchValue))
def _read(self): """Read through the depletion file and store requested data.""" info('Preparing to read {}'.format(self.filePath)) keys = ['E', 'i\d{4,5}', 'm\w'] separators = ['\n', '];', '\r\n'] with KeywordParser(self.filePath, keys, separators) as parser: for chunk in parser.yieldChunks(): if chunk[0][:5] == 'E = [': # The energy grid self.metadata['egrid'] = np.array(chunk[1:], dtype=np.float64) elif chunk[0][:15] == 'majorant_xs = [': # L-inf norm on all XS on all materials self.metadata['majorant_xs'] = np.array(chunk[1:], dtype=np.float64) elif chunk[0][-7:] == 'mt = [\n': debug('found mt specification') xsname = chunk[0][:-8] isiso = True if chunk[0][0] == 'i' else False self.xsections[xsname] = XSData(xsname, self.metadata, isIso=isiso) self.xsections[xsname].setMTs(chunk) elif chunk[0][-7:] == 'xs = [\n': debug('found xs specification') xsname = chunk[0][:-8] self.xsections[xsname].setData(chunk) elif chunk[0][-7:] == 'nu = [\n': debug('found nu specification') xsname = chunk[0][:-8] self.xsections[xsname].setNuData(chunk) elif 'bra_f' in chunk[0]: warning("There is this weird 'bra_f' XS. these seem to be" " constant. recording to metadata instead.") self.metadata[xsname].setData(chunk) else: print(chunk) error('Unidentifiable entry {}'.format(chunk[0])) info('Done reading xsplot file') debug(' found {} xs listings'.format(len(self.xsections)))
def inferReader(filePath): """ Attempt to infer the correct reader type. Parameters ---------- filePath: str File to be read. Raises ------ SerpentToolsException If a reader cannot be inferred """ for reg, reader in six.iteritems(REGEXES): match = re.match(reg, filePath) if match and match.group() == filePath: debug('Inferred reader for {}: {}'.format(filePath, reader.__name__)) return reader raise SerpentToolsException( 'Failed to infer filetype and thus accurate reader from' 'file path {}'.format(filePath))
import os ROOT_DIR = os.path.dirname(__file__) from serpentTools.parsers import read from serpentTools import messages from ._version import get_versions __version__ = get_versions()['version'] del get_versions messages.debug('Using version {}'.format(__version__))
def seedFiles(inputFile, numSeeds, seed=None, outputDir=None, link=False, digits=10): """ Copy input file multiple times with unique seeds. Parameters ---------- inputFile: str Path to input file numSeeds: int Number of files to create seed: int Optional argument to set the seed of the builtin random number generator outputDir: str Path to desired output directory. Files will be copied here. If the folder does not exist, try to make the directory. Assumes path relative to directory that contains the input file link: bool If True, do not copy the full file. Instead, create a new file with 'include <inputFile>' and the new seed declaration. digits: int Average number of digits for random seeds See Also -------- :py:mod:`random` :py:func:`random.seed()` :py:func:`random.getrandbits()` """ if '~' in inputFile: inputFile = os.path.expanduser(inputFile) if not path.exists(inputFile): error('Input file {} does not exist'.format(inputFile)) return if numSeeds < 1: error('Require positive number of files to create') return if digits < 1: error('Require positive number of digits in random seeds') bits = int((digits - OFFSET) / SLOPE) random.seed(seed) inputPath = path.abspath(path.join(os.getcwd(), inputFile)) inputRoot = path.dirname(inputPath) if outputDir is not None: fPrefix = path.abspath(path.join(inputRoot, outputDir)) if not path.isdir(fPrefix): debug('Creating directory at {}'.format(fPrefix)) os.mkdir(fPrefix) else: fPrefix = inputRoot fileFmt = path.join(fPrefix, _makeFileFmt(inputFile)) writeFunc = _include if link else _copy writeFunc(inputPath, numSeeds, fileFmt, bits, digits) return
def spectrumPlot(self, fixed=None, ax=None, normalize=True, xlabel=None, ylabel=None, steps=True, logx=True, logy=False, loglog=False, sigma=3, labels=None, legend=None, ncol=1, title=None, **kwargs): """ Quick plot of the detector value as a function of energy. Parameters ---------- fixed: None or dict Dictionary controlling the reduction in data {ax} normalize: bool Normalize quantities per unit lethargy {xlabel} {ylabel} steps: bool Plot tally as constant inside bin {logx} {logy} {loglog} {sigma} {labels} {legend} {ncol} {title} {kwargs} :py:func:`matplotlib.pyplot.plot` or :py:func:`matplotlib.pyplot.errorbar` Returns ------- {rax} Raises ------ :class:`~serpentTools.SerpentToolsException` if number of rows in data not equal to number of energy groups See Also -------- * :meth:`slice` """ slicedTallies = self.slice(fixed, 'tallies').copy() if len(slicedTallies.shape) > 2: raise SerpentToolsException( 'Sliced data cannot exceed 2-D for spectrum plot, not ' '{}'.format(slicedTallies.shape)) elif len(slicedTallies.shape) == 1: slicedTallies = slicedTallies.reshape(slicedTallies.size, 1) lowerE = self.grids['E'][:, 0] if normalize: lethBins = log(divide(self.grids['E'][:, -1], lowerE)) for indx in range(slicedTallies.shape[1]): scratch = divide(slicedTallies[:, indx], lethBins) slicedTallies[:, indx] = scratch / scratch.max() if steps: if 'drawstyle' in kwargs: debug( 'Defaulting to drawstyle specified in kwargs as {}'.format( kwargs['drawstyle'])) else: kwargs['drawstyle'] = 'steps-post' if sigma: slicedErrors = sigma * self.slice(fixed, 'errors').copy() slicedErrors = slicedErrors.reshape(slicedTallies.shape) slicedErrors *= slicedTallies else: slicedErrors = None ax = plot(lowerE, slicedTallies, ax=ax, labels=labels, yerr=slicedErrors, **kwargs) if ylabel is None: ylabel = 'Tally data' ylabel += ' normalized per unit lethargy' if normalize else '' ylabel += r' $\pm{}\sigma$'.format(sigma) if sigma else '' if legend is None and labels: legend = True ax = formatPlot(ax, loglog=loglog, logx=logx, ncol=ncol, xlabel=xlabel or "Energy [MeV]", ylabel=ylabel, legend=legend, title=title) return ax
def process(self): """Process the repeated files to obtain true uncertainties""" debug('Processing data from {} parsers'.format(len(self.parsers))) self._process() debug('Done')
def depmtx(fileP): """ Read the contents of the ``depmtx`` file and return contents .. note:: If ``scipy`` is not installed, matrix ``A`` will be full. This can cause some warnings or errors if sparse or non-sparse solvers are used. Parameters ---------- fileP: str Path to depletion matrix file Returns ------- t: float Length of time n0: numpy.ndarray Initial isotopic vector zai: numpy.array String identifiers for each isotope in ``n0`` and ``n1`` a: numpy.array or scipy.sparse.csc_matrix Decay matrix. Will be sparse if scipy is installed n1: numpy.array Final isotopic vector """ if not path.exists(fileP): raise IOError('Cannot find depeletion matrix file {}'.format(fileP)) tRegex = re.compile(r't\s+=\s+([\d\.E\+-]+)') nDensRegex = re.compile(r'N\d\(\s*([\d]+).*=\s+([\d\.E\+-]+)') # matches index and quantity for N0 and N1 vectors zaiRegex = re.compile(r'ZAI\(\s*[\d]+\).*=\s+(-?\d+)') # matches index and name for ZAI vector matrixRegex = re.compile(r'A\(\s*(\d+),\s*(\d+)\)\s+=\s+([\dE\.\+-]+)') # matrix row, column, and decay constant for A matrix failMsg = 'File {} is ill-formed for depmtx reader\nLine: '.format(fileP) if not HAS_SCIPY: debug('Decay matrix will be returned as full matrix') with open(fileP) as f: line = f.readline() tMatch = re.match(tRegex, line) if not tMatch: raise SerpentToolsException(failMsg + line) t = float(tMatch.groups()[0]) line = f.readline() nMatch = re.match(nDensRegex, line) if not nMatch: raise SerpentToolsException(failMsg + line) n0Storage, line, numIso = _parseIsoBlock(f, {}, nMatch, line, nDensRegex) debug('Found {} isotopes for file {}'.format(numIso, fileP)) n0 = empty((numIso, 1), dtype=longfloat) for indx, v in six.iteritems(n0Storage): n0[indx] = v zai = [] zMatch = re.match(zaiRegex, line) if not zMatch: raise SerpentToolsException(failMsg + line) while zMatch: zai.append(zMatch.groups()[0]) line = f.readline() zMatch = re.match(zaiRegex, line) if len(zai) != n0.size: debug('Stopping at line -' + line) raise SerpentToolsException( 'Did not obtain equal isotopes in ZAI vector [{}] as in N0 ' 'vector [{}]'.format(len(zai), n0.size)) line = f.readline() aMatch = re.match(matrixRegex, line) if not aMatch: raise SerpentToolsException(failMsg + line) a = zeros((numIso, numIso), dtype=longfloat) while aMatch: row, col, const = aMatch.groups() a[int(row) - 1, int(col) - 1] = float(const) line = f.readline() aMatch = re.match(matrixRegex, line) n1 = empty_like(n0) nMatch = re.match(nDensRegex, line) n1, line, items = _parseIsoBlock(f, n1, nMatch, line, nDensRegex) if items != numIso: debug('Stopping at line -' + line) raise SerpentToolsException( 'Did not obtain equal isotopes in N1 vector [{}] as in N0 ' 'vector [{}]'.format(items, numIso)) return t, n0, array(zai), csc_matrix(a) if HAS_SCIPY else a, n1
def spectrumPlot(self, fixed=None, ax=None, normalize=True, xlabel=None, ylabel=None, steps=True, logx=True, logy=False, loglog=False, sigma=3, labels=None, **kwargs): """ Quick plot of the detector value as a function of energy. Parameters ---------- fixed: None or dict Dictionary controlling the reduction in data {ax} normalize: bool Normalize quantities per unit lethargy {xlabel} {ylabel} steps: bool Plot tally as constant inside bin {logx} {logy} {loglog} {sigma} {labels} {kwargs} :py:func:`matplotlib.pyplot.plot` or :py:func:`matplotlib.pyplot.errorbar` Returns ------- {rax} Raises ------ SerpentToolsException if number of rows in data not equal to number of energy groups See Also -------- :py:meth:`~serpentTools.objects.containers.DetectorBase.slice` """ slicedTallies = self.slice(fixed, 'tallies').copy() if len(slicedTallies.shape) > 2: raise SerpentToolsException( 'Sliced data cannot exceed 2-D for spectrum plot, not ' '{}'.format(slicedTallies.shape)) elif len(slicedTallies.shape) == 1: slicedTallies = slicedTallies.reshape(slicedTallies.size, 1) lowerE = self.grids['E'][:, 0] if normalize: lethBins = log(divide(self.grids['E'][:, -1], lowerE)) for indx in range(slicedTallies.shape[1]): scratch = divide(slicedTallies[:, indx], lethBins) slicedTallies[:, indx] = scratch / scratch.max() if steps: if 'drawstyle' in kwargs: debug( 'Defaulting to drawstyle specified in kwargs as {}'.format( kwargs['drawstyle'])) else: kwargs['drawstyle'] = 'steps-post' if sigma: slicedErrors = sigma * self.slice(fixed, 'errors').copy() slicedErrors = slicedErrors.reshape( slicedTallies.shape) * slicedTallies else: slicedErrors = None ax = plot(lowerE, slicedTallies, ax=ax, labels=labels, yerr=slicedErrors, **kwargs) if loglog or logx: ax.set_xscale('log') if loglog or logy: ax.set_yscale('log') ax.set_xlabel(xlabel or 'Energy [MeV]') ylabel = ylabel or 'Tally data' + (' normalized per unit lethargy' if normalize else '') ax.set_ylabel(ylabel) return ax
def plot(self, xdim=None, what='tallies', sigma=None, fixed=None, ax=None, xlabel=None, ylabel=None, steps=False, labels=None, logx=False, logy=False, loglog=False, **kwargs): """ Simple plot routine for 1- or 2-D data Parameters ---------- xdim: None or str If not None, use the array under this key in ``indexes`` as the x axis what: {'tallies', 'errors', 'scores'} Primary data to plot {sigma} fixed: None or dict Dictionary controlling the reduction in data down to one dimension {ax} {xlabel} If ``xdim`` is given and ``xlabel`` is ``None``, then ``xdim`` will be applied to the x-axis. {ylabel} steps: bool If true, plot the data as constant inside the respective bins. Sets ``drawstyle`` to be ``steps-post`` unless ``drawstyle`` given in ``kwargs`` {labels} {logx} {logy} {loglog} {kwargs} :py:func:`~matplotlib.pyplot.plot` or :py:func:`~matplotlib.pyplot.errorbar` function. Returns ------- {rax} Raises ------ SerpentToolsException If data contains more than 2 dimensions See Also -------- * :py:meth:`~serpentTools.objects.containers.Detector.slice` * :py:meth:`~serpentTools.objects.containers.DetectorBase.spectrumPlot` better options for plotting energy spectra """ data = self.slice(fixed, what) if len(data.shape) > 2: raise SerpentToolsException( 'Data must be constrained to 1- or 2-D, not {}'.format( data.shape)) elif len(data.shape) == 1: data = data.reshape(data.size, 1) if sigma: if what != 'errors': yerr = self.slice(fixed, 'errors').reshape( data.shape) * data * sigma else: warning( 'Will not plot error bars on the error plot. Data to be ' 'plotted: {}. Sigma: {}'.format(what, sigma)) yerr = None else: yerr = None xdata, autoX = self._getPlotXData(xdim, data) xlabel = xlabel or autoX ylabel = ylabel or "Tally data" ax = ax or pyplot.axes() if steps: if 'drawstyle' in kwargs: debug( 'Defaulting to drawstyle specified in kwargs as {}'.format( kwargs['drawstyle'])) else: kwargs['drawstyle'] = 'steps-post' ax = plot(xdata, data, ax, labels, yerr, **kwargs) ax.set_xlabel(xlabel) ax.set_ylabel(ylabel) if loglog or logx: ax.set_xscale('log') if loglog or logy: ax.set_yscale('log') return ax