Copyright (c) 2017-2020 Serpent-Tools developer team, GTRC

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Data files are not included with the python package, but can be downloaded from the GitHub repository. For this tutorial, the files are placed in the directory identified with the SERPENT_TOOLS_DATA environment variable.


In [1]:
import os
resFile = os.path.join(
    os.environ["SERPENT_TOOLS_DATA"],
    "InnerAssembly_res.m")

Results Reader

Basic operations

This notebook demonstrates the capabilities of the serpentTools to read Serpent results files. SERPENT [1] produces a result file (i.e. _res.m), containing general results (e.g. k-eff), metadata (e.g. title) and homogenized cross-sections. The homogenized cross-section sets are printed in the results file for all the requested universes. The ResultsReader is capable of reading this file, and storing the data inside univ objects. Each such object has methods and attributes that should ease the analyses.


In [2]:
import numpy as np
import serpentTools
from serpentTools.settings import rc
rc['serpentVersion'] = '2.1.30'

In [3]:
res = serpentTools.read(resFile)

Metadata

metadata is a collective data that describes the problem. These are values that do not change over burnup and across homogenized universes. The following data is included: titles, data paths, and other descriptive data exist on the reader.


In [4]:
print(res.metadata['version'])  # Serpent version used for the execution
print(res.metadata['decayDataFilePath'])  # Directory path for data libraries
print(res.metadata['inputFileName'])  # Directory path for data libraries


Serpent 2.1.30
/nv/hp22/dkotlyar6/data/Codes/DATA/endfb7/sss_endfb7.dec
InnerAssembly

In [5]:
res.metadata.keys()


Out[5]:
dict_keys(['version', 'compileDate', 'debug', 'title', 'confidentialData', 'inputFileName', 'workingDirectory', 'hostname', 'cpuType', 'cpuMhz', 'startDate', 'completeDate', 'pop', 'cycles', 'skip', 'batchInterval', 'srcNormMode', 'seed', 'ufsMode', 'ufsOrder', 'neutronTransportMode', 'photonTransportMode', 'groupConstantGeneration', 'b1Calculation', 'b1BurnupCorrection', 'implicitReactionRates', 'optimizationMode', 'reconstructMicroxs', 'reconstructMacroxs', 'doubleIndexing', 'mgMajorantMode', 'spectrumCollapse', 'mpiTasks', 'ompThreads', 'mpiReproducibility', 'ompReproducibility', 'ompHistoryProfile', 'shareBufArray', 'shareRes2Array', 'xsDataFilePath', 'decayDataFilePath', 'sfyDataFilePath', 'nfyDataFilePath', 'braDataFilePath'])

In [6]:
res.metadata['startDate']


Out[6]:
'Sat Apr 28 06:09:54 2018'

In [7]:
res.metadata['pop'], res.metadata['skip']  , res.metadata['cycles']


Out[7]:
(5000, 10, 50)

Results Data

Results are stored as a function of time/burnup/index and include integral parameters of the system. Results, such as k-eff, total flux, and execution times are included in .resdata. Some results include values and uncertainities (e.g. criticality) and some just the values (e.g. CPU resources).


In [8]:
sorted(res.resdata.keys())[0:5]


Out[8]:
['minMacroxs', 'dtThresh', 'stFrac', 'dtFrac', 'dtEff']

Values are presented in similar fashion as if they were read in to Matlab, with one exception. Serpent currently appends a new row for each burnup step, but also for each homogenized universe. This results in repetition of many quantities as Serpent loops over group constant data. The ResultsReader understands Serpent outputs and knows when to append "new" result data to avoid repetition.

The structure of the data is otherwise identical to Matlab. For many quantities, the first column indicates expected value, while the second column contains relative uncertainties. For a better reference, please consult the Serpent wiki on structure of the main output file.


In [9]:
res.resdata['absKeff']


Out[9]:
array([[1.29160e+00, 9.00000e-04],
       [1.29500e+00, 9.30000e-04],
       [1.29172e+00, 9.10000e-04],
       [1.29172e+00, 7.80000e-04],
       [1.29312e+00, 6.80000e-04],
       [1.29140e+00, 7.80000e-04]])

In [10]:
res.resdata['absKeff'][:,0]


Out[10]:
array([1.2916 , 1.295  , 1.29172, 1.29172, 1.29312, 1.2914 ])

In [11]:
res.resdata['burnup']


Out[11]:
array([[0.      , 0.      ],
       [0.1     , 0.100001],
       [1.      , 1.00001 ],
       [2.      , 2.00001 ],
       [3.      , 3.00003 ],
       [4.      , 4.00004 ]])

In [12]:
res.resdata['burnDays']


Out[12]:
array([[ 0.     ],
       [ 1.20048],
       [12.0048 ],
       [24.0096 ],
       [36.0144 ],
       [48.0192 ]])

In [13]:
res.resdata['totCpuTime']


Out[13]:
array([[10.814 ],
       [20.3573],
       [30.0783],
       [39.4965],
       [48.919 ],
       [58.6448]])

Data in the resdata dictionary can be obtained by indexing directly into the reader with


In [13]:
res["burnup"]


Out[13]:
array([[0.      , 0.      ],
       [0.1     , 0.100001],
       [1.      , 1.00001 ],
       [2.      , 2.00001 ],
       [3.      , 3.00003 ],
       [4.      , 4.00004 ]])

In [15]:
res.get("absKeff")


Out[15]:
array([[1.29160e+00, 9.00000e-04],
       [1.29500e+00, 9.30000e-04],
       [1.29172e+00, 9.10000e-04],
       [1.29172e+00, 7.80000e-04],
       [1.29312e+00, 6.80000e-04],
       [1.29140e+00, 7.80000e-04]])

Plotting Results Data

The ResultsReader has a versatile plot method, used to plot primary time-dependent data from the result file. With it, one can plot data from one or more quanties against various metrics of time. Control over formatting axis, legend placement, and label formatting is easily yielded to the user.


In [ ]:
res.plot('absKeff')


Out[ ]:
<matplotlib.axes._subplots.AxesSubplot at 0x7fa54d078990>

In [ ]:
res.plot('burnup', ['absKeff', 'colKeff'])


Out[ ]:
<matplotlib.axes._subplots.AxesSubplot at 0x7fa54cf86d50>

Pass a dictionary of {variable: label} pairs to set plot labels


In [ ]:
# plot multiple values with better labels and formatting
res.plot(
    'burnup', {'absKeff': '$k_{eff}^{abs}$', 'colKeff': '$k_{eff}^{col}$'},
    ylabel=r'Criticality $\pm 3\sigma$',
    legend='above', ncol=2)


Out[ ]:
<matplotlib.axes._subplots.AxesSubplot at 0x7fa54cf1c4d0>

Using the right argument, quantities can be plotted on the left and right y-axis. Similar formatting options are available


In [ ]:
res.plot(
    'burnStep', {'actinideIngTox': 'Actinide Ingestion Tox'},
    right={'totCpuTime': "CPU Time [right]"}, sigma=0, rightlabel='CPU Time', 
    logy=[False, True])


Out[ ]:
(<matplotlib.axes._subplots.AxesSubplot at 0x7fa54cdec9d0>,
 <matplotlib.axes._subplots.AxesSubplot at 0x7fa54cd87d90>)

Homogenized Universes

Universe data is stored for each state point in the universes dictionary. Keys are UnivTuple representing ('univ',burnup, step, time)

  • 'univ': universe ID (e.g., '0')
  • burnup: in MWd/kg
  • step: step index,
  • time: in days.
    and can be indexes by attribute or by position.

In [ ]:
for key in sorted(res.universes):
    break
key


Out[ ]:
UnivTuple(universe='0', burnup=0.0, step=0, days=0.0)

In [ ]:
key[0]


Out[ ]:
'0'

In [ ]:
assert key.burnup == key[1]

Results, such as infinite cross-sections, b1-leakage corrected cross-sections, kinetic parameters, are included in .universes. All the results include values and uncertainties.


In [24]:
sorted(res.universes)[:10]


Out[24]:
[UnivTuple(universe='0', burnup=0.0, step=0, days=0.0),
 UnivTuple(universe='0', burnup=0.1, step=1, days=1.20048),
 UnivTuple(universe='0', burnup=1.0, step=2, days=12.0048),
 UnivTuple(universe='0', burnup=2.0, step=3, days=24.0096),
 UnivTuple(universe='0', burnup=3.0, step=4, days=36.0144),
 UnivTuple(universe='0', burnup=4.0, step=5, days=48.0192),
 UnivTuple(universe='3101', burnup=0.0, step=0, days=0.0),
 UnivTuple(universe='3101', burnup=0.1, step=1, days=1.20048),
 UnivTuple(universe='3101', burnup=1.0, step=2, days=12.0048),
 UnivTuple(universe='3101', burnup=2.0, step=3, days=24.0096)]

One can directly index into the universes dictionary to obtain data for a specific universe.


In [ ]:
print(res.universes['0', 0, 0, 0])


<HomogUniv 0: burnup: 0.00000E+00 MWd/kgU, 0.00000E+00 days>

However this requires knowledge of all four parameters which may be difficult. The getUniv method retrieves a specific universe that matches universe id and time of interest. While all four identifers, (universe id, burnup, step, and time) can be provided, the latter three are usually redundant.


In [ ]:
univ0 = res.getUniv('0', index=0)  
print(univ0)


<HomogUniv 0: burnup: 0.00000E+00 MWd/kgU, 0.00000E+00 days>

In [ ]:
univ3101 = res.getUniv('3101', index=3)
print(univ3101)


<HomogUniv 3101: burnup: 2.00000E+00 MWd/kgU, 2.40096E+01 days>

In [ ]:
univ3102 = res.getUniv('3102', burnup=0.1)
print(univ3102)


<HomogUniv 3102: burnup: 1.00000E-01 MWd/kgU, 1.20048E+00 days>

In [ ]:
print(res.getUniv('0', timeDays=24.0096))


<HomogUniv 0: burnup: 2.00000E+00 MWd/kgU, 2.40096E+01 days>

Working with homogenized universe data

Each state contains the same data fields, which can be obatined by using the following attributes on the HomogUniv objects:

  • infExp: infinite values, e.g. INF_ABS,

  • infUnc: infinite uncertainties,

  • b1Exp: b1 (leakage corrected) values, e.g. B1_ABS,

  • b1Exp: b1 (leakage corrected) uncertainties,

  • gc: variables that are not included in 'inf' or 'b1', e.g. BETA

  • gcUnc: group uncertainties

  • groups: macro energy group structure, MeV

  • microGroups: micro energy group structure, MeV

The parser reads all variables by default, and the HomogUniv objects stores data in various dictionaries. Data are energy dependent, exactly as they would appear in Serpent outputs.


In [30]:
sorted(univ0.infExp)[:10]


Out[30]:
['infAbs',
 'infCapt',
 'infChid',
 'infChip',
 'infChit',
 'infDiffcoef',
 'infFiss',
 'infFissFlx',
 'infFlx',
 'infI135MicroAbs']

In [31]:
univ0.infExp['infAbs']


Out[31]:
array([0.0170306 , 0.0124957 , 0.00777066, 0.00773255, 0.00699608,
       0.00410746, 0.00334604, 0.00296948, 0.0030725 , 0.00335412,
       0.00403133, 0.00506587, 0.00651475, 0.00737292, 0.00907442,
       0.0113446 , 0.0125896 , 0.0164987 , 0.0181642 , 0.0266464 ,
       0.0292439 , 0.0315338 , 0.0463069 , 0.0807952 ])

In [ ]:
univ0["infAbs"]


Out[ ]:
array([0.0170306 , 0.0124957 , 0.00777066, 0.00773255, 0.00699608,
       0.00410746, 0.00334604, 0.00296948, 0.0030725 , 0.00335412,
       0.00403133, 0.00506587, 0.00651475, 0.00737292, 0.00907442,
       0.0113446 , 0.0125896 , 0.0164987 , 0.0181642 , 0.0266464 ,
       0.0292439 , 0.0315338 , 0.0463069 , 0.0807952 ])

In [ ]:
univ0.infExp['infFlx']


Out[ ]:
array([1.10460e+15, 1.72386e+16, 7.78465e+16, 1.70307e+17, 2.85783e+17,
       4.61226e+17, 8.04999e+17, 1.17536e+18, 1.17488e+18, 1.26626e+18,
       1.03476e+18, 7.58885e+17, 4.95687e+17, 5.85369e+17, 2.81921e+17,
       1.16665e+17, 8.06833e+16, 2.26450e+16, 6.51541e+16, 2.79929e+16,
       8.87468e+15, 1.70822e+15, 8.87055e+14, 6.22266e+13])

Uncertainties can be obtained by using the infUnc, b1Unc, and gcUnc dictionaries. Uncertainties are relative, as they appear in the output files.


In [ ]:
univ0.infUnc['infFlx']


Out[ ]:
array([0.02125, 0.0287 , 0.00901, 0.00721, 0.00441, 0.00434, 0.00448,
       0.0007 , 0.00369, 0.00071, 0.00045, 0.00133, 0.00061, 0.00341,
       0.00674, 0.00197, 0.00802, 0.00368, 0.00127, 0.00046, 0.02806,
       0.0491 , 0.19529, 0.16476])

Serpent also outputs the B1 cross-sections. However, the user must enable the B1 option by setting the fum card. If this card is not enabled by the user, the B1_ variables will all be zeros.


In [36]:
sorted(univ0.b1Exp)[:10]


Out[36]:
['b1Abs',
 'b1B2',
 'b1Capt',
 'b1Chid',
 'b1Chip',
 'b1Chit',
 'b1Diffcoef',
 'b1Err',
 'b1Fiss',
 'b1FissFlx']

In [37]:
univ0.b1Exp['b1Flx']


Out[37]:
array([1.39800e+15, 2.22121e+16, 1.00380e+17, 2.19050e+17, 3.61071e+17,
       5.61244e+17, 9.33086e+17, 1.28335e+18, 1.21094e+18, 1.24502e+18,
       9.62426e+17, 6.70263e+17, 4.23191e+17, 4.69164e+17, 2.14577e+17,
       8.77042e+16, 5.98583e+16, 1.67291e+16, 4.72542e+16, 1.96319e+16,
       6.00800e+15, 1.12777e+15, 5.79412e+14, 3.93280e+13])

In [ ]:
univ0["b1Flx"]


Out[ ]:
array([1.39800e+15, 2.22121e+16, 1.00380e+17, 2.19050e+17, 3.61071e+17,
       5.61244e+17, 9.33086e+17, 1.28335e+18, 1.21094e+18, 1.24502e+18,
       9.62426e+17, 6.70263e+17, 4.23191e+17, 4.69164e+17, 2.14577e+17,
       8.77042e+16, 5.98583e+16, 1.67291e+16, 4.72542e+16, 1.96319e+16,
       6.00800e+15, 1.12777e+15, 5.79412e+14, 3.93280e+13])

In [ ]:
univ0.b1Exp['b1Abs']


Out[ ]:
array([0.0170306 , 0.0124977 , 0.00777041, 0.00773244, 0.00700272,
       0.00410942, 0.00335046, 0.00296957, 0.00307219, 0.00335169,
       0.00402825, 0.00506175, 0.00651283, 0.00736592, 0.00907107,
       0.0113432 , 0.012588  , 0.0165002 , 0.018157  , 0.0266812 ,
       0.029204  , 0.0315299 , 0.0462994 , 0.0807952 ])

Data that does not contain the prefix INF_ or B1_ is stored under the gc and gcUnc fields. Criticality, kinetic, and other variables are stored under this field.


In [41]:
sorted(univ0.gc)[:5]


Out[41]:
['betaEff', 'cmmDiffcoef', 'cmmDiffcoefX', 'cmmDiffcoefY', 'cmmDiffcoefZ']

In [42]:
univ3101.gc['betaEff']


Out[42]:
array([3.04272e-03, 8.93131e-05, 6.59324e-04, 5.62858e-04, 1.04108e-03,
       5.67326e-04, 1.22822e-04])

In [ ]:
univ3101["betaEff"]


Out[ ]:
array([3.04272e-03, 8.93131e-05, 6.59324e-04, 5.62858e-04, 1.04108e-03,
       5.67326e-04, 1.22822e-04])

Macro- and micro-group structures are stored directly in the universe in MeV as they appear in the Serpent output files. This means that the macro-group structure is in order of descending energy, while micro-group are in order of increasing energy.


In [ ]:
univ3101.groups


Out[ ]:
array([1.0000e+37, 1.0000e+01, 6.0653e+00, 3.6788e+00, 2.2313e+00,
       1.3534e+00, 8.2085e-01, 4.9787e-01, 3.0197e-01, 1.8316e-01,
       1.1109e-01, 6.7380e-02, 4.0868e-02, 2.4788e-02, 1.5034e-02,
       9.1188e-03, 5.5309e-03, 3.3546e-03, 2.0347e-03, 1.2341e-03,
       7.4852e-04, 4.5400e-04, 3.1203e-04, 1.4894e-04, 0.0000e+00])

In [ ]:
univ3101.microGroups[:5:]


Out[ ]:
array([1.0000e-10, 1.4894e-04, 1.6525e-04, 1.8156e-04, 1.9787e-04])

Plotting universes

HomogUniv objects can plot group constants using their plot method. This method has a range of formatting options, with defaults corresponding to plotting macroscopic cross sections. This is manifested in the default y axis label, but can be easily adjusted.


In [ ]:
univ0.plot(['infAbs', 'b1Abs']);


Macroscopic and microscopic quantities, such as micro-group flux, can be plotted on the same figure. Note The units and presentation of the micro- and macro-group fluxes are dissimilar, and the units do not agree with that of the assumed group constants. This will adjust the default y-label, as demonstrated below.


In [ ]:
univ0.plot(['infTot', 'infFlx', 'infMicroFlx'], legend='right');


For plotting data from multiple universes, pass the returned matplotlib.axes.Axes object, on which the plot was drawn, into the plot method for the next universe. The labelFmt argument can be used to differentiate between plotted data. The following strings are replaced when creating the labels:

String Replaced value
{k} Name of variable plotted
{u} Name of this universe
{b} Value of burnup in MWd/kgU
{d} Value of burnup in days
{i} Burnup index

These can be used in conjunction with the matplotlib $\LaTeX$ rendering system.


In [ ]:
fmt = r"Universe {u} - $\Sigma_{abs}^\infty$"
ax = univ3101.plot('infFiss', labelFmt=fmt)
univ3102.plot('infFiss', ax=ax, labelFmt=fmt, legend='above', ncol=2);


User Defined Settings

The user is able to filter the required information by using the settings option.

A detailed description on how to use the settings can be found on: http://serpent-tools.readthedocs.io/en/latest/settingsTop.html


In [51]:
rc.keys()


Out[51]:
dict_keys(['branching.intVariables', 'branching.floatVariables', 'depletion.metadataKeys', 'depletion.materialVariables', 'depletion.materials', 'depletion.processTotal', 'detector.names', 'verbosity', 'sampler.allExist', 'sampler.freeAll', 'sampler.raiseErrors', 'sampler.skipPrecheck', 'serpentVersion', 'xs.getInfXS', 'xs.getB1XS', 'xs.reshapeScatter', 'xs.variableGroups', 'xs.variableExtras', 'microxs.getFlx', 'microxs.getXS', 'microxs.getFY'])

The rc object and various xs.* settings can be used to control the ResultsReader. Specifically, these settings can be used to store only specific bits of information. Here, we will store the version of Serpent, various cross sections, eigenvalues, and burnup data.


In [52]:
rc['xs.variableGroups'] = ['versions', 'xs', 'eig', 'burnup-coeff']

Further, instruct the reader to only read infinite medium cross sections, not critical spectrum B1 cross sections.


In [53]:
rc['xs.getB1XS'] = False

In [54]:
resFilt = serpentTools.read(resFile)

In [55]:
resFilt.metadata.keys()


Out[55]:
dict_keys(['version', 'compileDate', 'debug', 'title', 'confidentialData', 'inputFileName', 'workingDirectory', 'hostname', 'cpuType', 'cpuMhz', 'startDate', 'completeDate'])

In [56]:
resFilt.resdata.keys()


Out[56]:
dict_keys(['geomAlbedo', 'absKinf', 'absKeff', 'colKeff', 'impKeff', 'anaKeff', 'nubar', 'burnDays', 'burnup', 'burnStep', 'burnMode', 'burnMaterials'])

In [57]:
univ0Filt = resFilt.getUniv('0', index=1)

In [58]:
univ0Filt.infExp.keys()


Out[58]:
dict_keys(['infTot', 'infCapt', 'infAbs', 'infFiss', 'infNsf', 'infNubar', 'infKappa', 'infInvv', 'infScatt0', 'infScatt1', 'infScatt2', 'infScatt3', 'infScatt4', 'infScatt5', 'infScatt6', 'infScatt7', 'infTranspxs', 'infDiffcoef', 'infRabsxs', 'infRemxs', 'infChit', 'infChip', 'infChid', 'infS0', 'infS1', 'infS2', 'infS3', 'infS4', 'infS5', 'infS6', 'infS7'])

In [59]:
univ0Filt.b1Exp


Out[59]:
{}