Reading & Writing ENDF-6

A typical workflow with an ENDF-6 file involves reading the data into a Python dictionary, performing some actions on the data and finally writing the modified data to a new ENDF-6 file. On this page, we explain how to read and write ENDF-6 data along with some considerations to be heeded.

Reading an ENDF-6 file

An ENDF-6 file can be read with the parsefile() method of the EndfParserPy class:

from endf_parserpy import EndfParserPy
parser = EndfParserPy()
endf_dict = parser.parsefile('input.endf')

The variable endf_dict is a dictionary (dict) in which all quantities are associated with the symbol names described in the ENDF-6 formats manual. The data organization within this dictionary is determined by ENDF-6 recipes. An EndfDict object, instantiated by view_dict = EndfDict(endf_dict), can facilitate data navigation.

The parsing process can be influenced by using additional arguments in the instantiation of the EndfParserPy class. The default choices of these arguments are forgiving so that even ENDF-6 files that do not strictly comply with the ENDF-6 format can be read without failure. For the purpose of format validation, one would adopt parameters to enforce strict compliance with the ENDF-6 format.

The only argument that is relevant for reading is accept_spaces whose default is True. With this default, numbers with spaces inserted will be accepted, such as 1.234 +3. However, even ENDF-6 files with some numbers leaking into neighboring fields may be successfully read then, but with nonsensical values. Because this issue can be easily missed by the user, it may be a good idea to enforce failure in such a situation by adopting the following initialization:

parser = EndfParserPy(accept_spaces=False)

When only one part of the ENDF-6 file is relevant to the user, the parsefile() method can be instructed to only parse this part for greater speed. For instance, if you are only interested in MT sections within MF=3,4,5 sections, you can use:

endf_dict = parser.parsefile('input.endf', include=[3, 4, 5])

In the resulting dictionary, only the subdictionaries associated with the included MF numbers contain keys named according to symbol names as described in the ENDF-6 manual. The objects associated with the other MF numbers are lists of strings with the raw content of the ENDF-6 file, e.g. type(endf_dict[6][2]) == list. Instead of include, you can also use the exclude argument, e.g.

endf_dict = parser.parsefile('input.endf', exclude=[3, 4, 5])

to parse everything except MF=3,4,5. Both inclusion and exclusion can be performed more fine-grained at the level of MT sections. For example, to include everything of MF=6 and only MT=1 from MF=3, specify include=[6, [3, 1]].

If one wants to check which MF/MT sections have been parsed, one can utilize the list_parsed_sections() and list_unparsed_sections() function, respectively:

from endf_parserpy import list_parsed_sections
from endf_parserpy import list_unparsed_sections
list_parsed_sections(endf_dict)
list_unparsed_sections(endf_dict)

Writing an ENDF-6 file

Writing an ENDF-6 file is as simple as reading one. Assume that the dictionary endf_dict is of appropriate structure, e.g., as returned by the parsefile() method. The following code snippet demonstrates writing:

from endf_parserpy import EndfParserPy
parser = EndfParserPy()
parser.writefile('output.endf', endf_dict)

Also the writefile() method supports the include and exclude argument. If the include argument is provided, only included MF/MT sections will be written to the file. Similarly, if exclude is provided, only all non-excluded sections will be written.

If data in the endf_dict dictionary have been added or deleted, it is important to update the directory listing in MF1/MT451 first before writing to an ENDF-6 file (see manual page 57). This can be achieved with the update_directory() function.

from endf_parserpy import update_directory
update_directory(endf_dict, parser)

Note

Don’t use the include and exclude argument of the writefile() method if it is important that the ENDF directory in MF1/MT451 is in sync with the file. Rather remove the sections manually before the invocation of update_directory().

Finally, we want to discuss how to control the output precision of numerical data. The ENDF-6 format only provides eleven character slots for the representation of numbers, limiting the achievable precision. By default, numbers are written in a specific floating point notation that appears to be commonly adopted by nuclear data library projects. This notation is of the form ␣1.234567+8, with the first character slot being reserved for a potential minus sign and skipping the e, which would usually be included to indicate the start of the exponent.

If more output precision is required, several options are available to tweak the output format, which can be passed as arguments to the constructor of the EndfParserPy class. With abuse_signpos=True, positive numbers are allowed to consume the first character slot usually reserved for the sign. The argument prefer_noexp=True will switch to a decimal notation (i.e. without exponent) if the number can be represented with more significant digits. The choice skip_intzero=True will skip the zero of a number in decimal notation if the integer part is zero, e.g., 0.1234 will become .12345, giving in some situations one extra digit of precision. Therefore, for maximal output precision (and ugly display) initialize the EndfParserPy instance like this:

parser = EndfParserPy(abuse_signpose=True, prefer_noexp=True, skip_intzero=True)

If you want to increase compatibility with programming languages different from Fortran that don’t accept the omission of the e character in the scientific notation of a number, include the argument keep_E=True.

Finally, if you want to ensure that you haven’t lost (too much) precision, you can make use of the compare_objects() function. Just read the output file again, and compare it with the original endf_dict:

from endf_parserpy import compare_objects

test_dict = parser.parsefile('output.endf')
compare_objects(endf_dict, test_dict, atol=1e-6, rtol=1e-6, fail_on_diff=False)