Mappings by ENDF-6 Recipes
Endf-parserpy relies on a formal description of the ENDF-6 format
for parsing ENDF-6 files and mapping their data into Python dictionaries
(dict).
From the user perspective, the formal description
contains all the information for understanding the structure
of the dictionaries. Also, appropriately structured
dictionaries can be written out as ENDF-6 files.
This section explains the link between the formal ENDF-6
description and the corresponding structure of dictionaries.
What is an ENDF-6 recipe?
The entire ENDF-6 format description is subdivided into ENDF-6 recipes. An ENDF-6 recipe often contains the format description for an entire MF section (dedicated to a certain nuclear quantity type). However, sometimes the format description is different for different MT sections within the same MF section and then one ENDF-6 recipe is provided for each of those MT sections. To get a first idea of how these ENDF-6 recipes look like, take a glimpse at this recipe explained later on this page. All of the currently implemented recipes are available in this subdirectory of endf-parserpy’s GitHub repository. In the following, we explain how the corresponding structure of the Python dictionary can be inferred from such recipes.
Note
ENDF-6 recipes are an essential part of endf-parserpy to enable a proper parsing of ENDF-6 files. However, the following information is not a prerequisite for a satisfactory understanding of the guides and tutorials.
Structure of Python dictionaries with ENDF-6 data
The structure of a dictionary returned by a call to
the parsefile() method
or expected by writefile() method
of the
EndfParserPy class
contains as first level the integer keys corresponding to
MF numbers and as second level
integer keys corresponding to MT numbers, see the ENDF-6 formats manual (page 32).
Suppose the variable endf_dict contains
such a dictionary, then endf_dict[3][1] would refer
to the dictionary that includes the variables associated with
MF=3 (cross section) and MT=1 (total cross section).
The structure of a dictionary associated with an MF/MT combination is determined by the form of the corresponding ENDF-6 recipe. Each ENDF-6 recipe contains a sequence of ENDF records and potentially syntactic elements for expressing repetitions and conditional presence of such records. As you will see, detailed knowledge of ENDF records is not strictly necessary to understand how a specific ENDF-6 recipe is linked to the structure of the associated dictionary.
Basic mapping of variables
The following ENDF-6 recipe for MF=1/MT=451 (general description) will serve as the basis for the explanation of how the dictionary structure is determined by a recipe:
[MAT, 1,451/ ZA, AWR, LRP, LFI, NLIB, NMOD]HEAD
[MAT, 1,451/ ELIS, STA, LIS, LISO, 0, NFOR]CONT
[MAT, 1,451/ AWI, EMAX, LREL, 0, NSUB, NVER]CONT
[MAT, 1,451/ TEMP, 0.0, LDRV, 0, NWD, NXC]CONT
[MAT, 1,451/ ZSYMAM{11}, ALAB{11}, EDATE{10}, {1}, AUTH{33} ]TEXT
[MAT, 1,451/ {1}, REF{21}, DDATE{10}, {1},
RDATE{10}, {12}, ENDATE{8}, {3} ]TEXT
for i=1 to 3:
[MAT, 1,451/ HSUB[i]] TEXT
endfor
for i=1 to NWD-5:
[MAT, 1,451/ DESCRIPTION[i]]TEXT
endfor
for i=1 to NXC:
[MAT, 1,451/ blank, blank, MFx[i], MTx[i], NCx[i], MOD[i]]DIR
endfor
SEND
This recipe is a formalized version of the format specification in
the ENDF-6 formats manual (page 61).
Let’s assume that the corresponding dictionary
is named mf1_mt451_dict referring to endf_dict[1][451].
Each dictionary associated with a specific MF and MT number
will contain the keys MAT, MF and MT
(manual page 30) with
the appropriate integer values (data type int).
Lines of the form [ ... / ... ] <record type> in an
ENDF-6 recipe represent ENDF records (manual page 52). The variable names
in the six slots after the first slash in an ENDF record
are directly mapped onto equally named keys in the
corresponding dictionary.
For example, the six slots of the first line (the HEAD record)
contain the variable names ZA, AWR, LRP,
LFI, NLIB and NMOD, and equally named keys are expected
to be present in mf1_mt451_dict.
More generally, the variable names introduced in the six
comma-separated slots after the first / of
any ENDF record specification (HEAD, CONT, TEXT, etc.)
are expected to exist as dictionary keys. The only particularity is the
special keyword blank that represents a blank slot and not a variable name.
Simple variable names are linked to scalar values of type int or float.
The only exception to this rule are the variables introduced in a TEXT record, which
are associated with character strings (str). Variable names suffixed by a string
with square brackets denote arrays. In a TEXT record,
variables and also arrays can be additionally suffixed
by curly braces enclosing an integer, which defines the length of the string.
Variables with indices: Arrays
The ENDF-6 recipe listed above contains
several examples of array specifications, such as DESCRIPTION[i],
MFx[i] and MTx[i]. Their names, DESCRIPTION,
MFx, etc. are expected to be available as keys in the Python dictionary
corresponding to the ENDF-6 recipe. The objects associated with those
keys are expected to be dictionaries with integer keys.
The range of the available integer keys can be inferred from the
loop statement that involves the variable appearing in the pair of
square brackets. For instance, the counter variable i runs from
1 to the value of variable NWD and hence the dictionary
stored under the DESCRIPTION key in mf1_mt451_dict (see above)
is expected to contain all integers between 1 and the value of NWD
as keys. The third element in DESCRIPTION could then
be accessed via mf1_mt451_dict['DESCRIPTION'][3].
Also multidimensional arrays are possible, e.g. arr2d[i, j]
would indicate a two-dimensional array.
Multidimensional arrays are realized by nesting dictionaries
with integer keys.
For example, an array of size 2x2 could be set up like this:
arr2d = {1: dict(), 2: dict()}
arr2d[1] = {1: 1, 2: 2}
arr2d[2] = {1: 3, 2: 4}
Data types
The data types of objects linked to the various keys can also be
inferred from an ENDF-6 recipe. The variables in
the first two slots of an ENDF record are of type float
and those in the next four slots of type int:
[ ... / float, float, int, int, int, int ] RECORD_TYPE
The only exception are TEXT records whose variables are
associated with type str.
Therefore, considering again the ENDF-6 recipe above as example,
the values under the keys ZA, AWR, ELIS, STA, etc.
are stored as data type float, whereas LRP, LFI, NLIB, NMOD,
LIS, LISO, etc. stored as data type int.
In contrast, the array elements of DESCRIPTION have the
data type str due to the variable name being introduced
in the slot of a TEXT record. Noteworthy, variable names in
TEXT record specifications may be suffixed by an integer
enclosed by curly braces to indicate the length of the
associated string. For example:
[MAT, 1,451/ ZSYMAM{11}, ALAB{11}, EDATE{10}, {1}, AUTH{33} ]TEXT
Here, ZSYMAM is associated with a string with 11 characters,
EDATE with 10 characters, etc.
A TEXT record specification may contain a single
variable name without the curly brace suffix, e.g.:
[MAT, 1,451/ DESCRIPTION[i] ]TEXT
The variable is then associated with a string spanning the full line in the ENDF-6 file (66 character slots).
Particularities of TAB1 and TAB2 records
There are a couple of particularities in how TAB1 and TAB2 records (manual page 54) are mapped into a Python dictionary. To explain, let’s consider a slightly adjusted version of the ENDF-6 recipe for MT sections of MF=3 (cross sections), compare also with the ENDF-6 formats manual (page 123):
[MAT, 3, MT/ ZA, AWR, 0, 0, 0, 0] HEAD
[MAT, 3, MT/ QM, QI, 0, LR, NR, NP / E / xs] TAB1
SEND
For the sake of illustration, let’s assume we are dealing
with the dictionary for a total cross section (MT=1),
mf3_mt1_dict = endf_dict[3][1]. As seen, a TAB1
record specification contains two extra slots, separated by
a slash, after the six regular comma-separated slots.
The variable names in these extra slots are expected to
be present in the dictionary, i.e. mf3_mt1_dict['E']
and mf3_mt1_dict['xs']. These keys are associated with
one-dimensional arrays that are stored as data type list.
Furthermore, there are two additional keys NBT and INT
expected to be present. These variables establish
the definition of a piecewise interpolation scheme (see manual page 44
for details). The associated objects also need to be of type list.
Regarding the regular six slots, the variable names
of the first four slots are mapped into the dictionary
as described in an earlier section on this page. Variable names of the last two slots,
here NR and NP are ignored because they can be inferred from
the length of the list datatypes NBT, INT and (here)
E and XS.
Matters for the TAB2 record are similar. Consider the following example:
[MAT, 6, MT/ SPI, 0.0, LIDP, 0, NR, NE / Eint ]TAB2
The presence of a TAB2 record specification means that
keys NBT and INT must be present in the dictionary
and the associated objects are of type list.
The variable name in the 5th slot (here NR) is ignored
as its value can be inferred from the length of the list
stored in NBT.
Furthermore, the variable name after the second slash,
here Eint, is ignored. This string can be regaded as a hint,
which variable name in the following TAB1 record contains
the values of the mesh points. The other variable names
in the remaining slots are mapped into the dictionary
as explained in an earlier section on this page. So for the given example
of a TAB2 record specification, keys with names
SPI, LIDP, NE, NBT and INT
are expected to be present in the dictionary.
Finally, there is a feature called table body section. To explain it, let’s consider a slightly extended version of the MF3/MT1 recipe introduced above:
[MAT, 3, MT/ ZA, AWR, 0, 0, 0, 0] HEAD
[MAT, 3, MT/ QM, QI, 0, LR, NR, NP / E / xs] TAB1 (xstable)
SEND
If a variable name is provided in brackets after a TAB1 or
TAB2 record specification, an equally named key is expected to be present
in the Python dictionary. This key is then associated with
another dictionary that contains NBT, INT and the two keys named
according to the variable names in the last two slots,
here E and XS. These variables could be accessed by
mf3_mt1_section['xstable']['NBT'], etc.
Particularities of LIST records
Let’s consider the following LIST record specification (see also manual page 53):
[MAT, 4, MT/ T, E[i] , LT, 0, NL[i], 0/ {a[i,l]}{l=1 to NL[i]} ]LIST
As this line is extracted
from the ENDF-6 recipe for MF=4,
let’s assume we deal with an MF=4/MT=2
section whose data is stored in a dictionary mf4_mt2_section.
Variable names introduced after the second slash exist as equally named
keys in the Python dictionary, so mf4_mt2_section['a'] needs to be
available and represents an array.
Notation such as {...}{l=1 to NL[i]} indicates repetitions
(see also here),
and helps to infer the ranges of indices for arrays introduced inside
the first curly bracket pair.
Sections
ENDF-6 recipes can also make use of sections. Let’s consider the following recipe to see how it affects the structure of the corresponding dictionary:
[MAT, 10, MT/ ZA, AWR, LIS, 0, NS, 0]HEAD
for k=1 to NS:
(subsection[k])
[MAT, 10, MT/ QM, QI, IZAP, LFS, NR, NP/ E / sigma ]TAB1
(/subsection[k])
endfor
The notation (subsection[k]) opens an array of sections
and (/subsection[k]) indicates the end of the section block.
Assume that the corresponding dictionary is given by mf10_mt1_section.
The presence of an opening and closing section statement
leads to the creation of a key whose name is given by the section
name in the section opening statement. In the current example, we have
mf10_mt1_section['subsection']. Because the section name
is given by an array, the dictionary mf10_mt1_section['subsection']
contains contiguous integer keys and each of them is linked to
a dictionary, so mf10_mt1_section['subsection'][1],
mf10_mt1_section['subsection'][2], etc. are dictionaries as well.
The range of the contiguous integer keys can be inferred from the
loop statement containing the variable of the index, so in the
example considered keys from 1 to NS exist.
Variables introduced between the opening and closing section
statement are mapped into the subdictionaries as explained
in the previous section on this page. In the current example,
the following elements are expected to exist:
mf10_mt1_section['subsection'][1]['QM'],
mf10_mt1_section['subsection'][1]['QI'], etc.
Conditional blocks
Conditional blocks are associated with if/elif/else statements. Consider the recipe for an MF=1/MT=452 section (see also manual page 63):
[MAT, 1, 452/ ZA, AWR, 0, LNU, 0, 0]HEAD
if LNU == 1:
[MAT, 1, 452/ 0.0, 0.0, 0, 0, NC, 0/ {C[k]}{k=1 to NC} ] LIST
elif LNU == 2:
[MAT, 1, 452/ 0.0, 0.0, 0, 0, NR, NP/ Eint / nu ]TAB1
endif
SEND
Assume the dictionary linked to MF=1/MT=452 is named
mf1_mt452_section.
Variable names introduced inside a conditional block will only
be present in this dictionary if the logical expression in the
if-statement is true. In the current example: The variable NC
will only be present as key in mf1_mt452_section if
mf1_mt452_section['LNU'] is equal to 1. Similarly,
keys named Eint and nu will only exist if
mf1_mt452_section['LNU'] == 2.