High-level Functionality

The paramount focus of endf-parserpy is to provide a low-level interface to ENDF-6 formatted data, which includes reading and writing entire ENDF-6 formatted files as well as convenient read and write access to any piece of data stored in an ENDF-6 file.

Some nuclear quantities are stored in compact but not very human-friendly representations, such as the R-matrix formalism employed for the storage of cross sections (and associated angular disributions) in the resonance region and the storage of angular distributions as Legendre coefficients. For the plotting of these quantities, however, angular distributions as functions of angle are easier to understand. At present, endf-parserpy doesn’t include any functionality to transform the compact and more abstract representations into human-friendly ones.

In this section, we want to discuss one possible approach to the implementation of high-level functionality.

A nice feature of endf-parserpy is that it keeps the bidirectional link between data in the ENDF-6 format and the same data in a Python dictionary, permitting the reliable translation from one representation into the other. It would be a pertinent design feature if any high-level functionality also preserves the link between the original data and the derived/simplified representation. While it won’t be possible to maintain a bidirectional link due to the loss of information in all cases, e.g. when calculating a group-wise cross section from point-wise cross sections, it will certainly be possible in some cases. The advantage would be that a user could change the nuclear quantities using the human-friendly representation, and the modifications could be automatically propagated back to the original low-level representation.

We want to demonstrate one possibility to accomplish this bidirectional linking by implementing a prototype class for linear interpolation of cross sections in MF3. The class should support the computation of interpolated values at energies requested by the user and also enable the user to update the cross section values at the mesh points. We will make use of the EndfVariable class to preserve the link to the original data in the ENDF-6 dictionary. A simple prototype class implementation could look something like this:

import numpy as np
from copy import copy
from endf_parserpy import EndfVariable


class LinearCrossSectionInterpolation:

    def __init__(self, xarr, yarr, nbt_arr, int_arr):
        if not (isinstance(xarr, EndfVariable)
                and isinstance(yarr, EndfVariable)
                and isinstance(nbt_arr, EndfVariable)
                and isinstance(int_arr, EndfVariable)):
            raise TypeError('expecting EndfVariables')
        self._xarr = xarr
        self._yarr = yarr
        self._nbt_arr = nbt_arr
        self._int_arr = int_arr
        self.table = (xarr.value, yarr.value)

    @property
    def table(self):
        return copy(self._xarr.value), copy(self._yarr.value)

    @table.setter
    def table(self, value):
        x = list(value[0])
        y = list(value[1])
        if len(x) != len(y):
            raise IndexError('length of x and y differs')
        if not isinstance(x, list) or not isinstance(y, list):
            raise TypeError('both x and y must be a `list`')
        self._xarr.value = x
        self._yarr.value = y
        self._nbt_arr.value = [2]
        self._int_arr.value = [len(x)]

    def __call__(self, xout):
        return np.interp(xout, self._xarr.value, self._yarr.value)

To see what this class is accomplishing, let’s set up an example that uses it for the linear interpolation of a total cross section (MF3/MT1):

from endf_parserpy import EndfParserPy
from endf_parserpy import EndfVariable

parser = EndfParserPy()
endf_dict = parser.parsefile('input.endf')

xvar = EndfVariable('3/1/xstable/E', endf_dict)
yvar = EndfVariable('3/1/xstable/xs', endf_dict)
int_arr = EndfVariable('3/1/xstable/INT', endf_dict)
nbt_arr = EndfVariable('3/1/xstable/INT', endf_dict)

interpobj = LinearCrossSectionInterpolation(xvar, yvar, nbt_arr, int_arr)

After loading the data of an ENDF-6 file into the dictionary endf_dict, EndfVariable objects are created and linked to the locations of the variables associated with the total cross section in MF3/MT1. The objects xvar, yvar, int_arr and nbt_arr serve the same purpose as regular variables: They can flow through the program logic to support a certain analysis or data processing. We use these variables to set up a LinearCrossSectionInterpolation object that we associate with the variable interpobj. This object allows us to compute cross section values at arbitrary energies within the permissible energy range, e.g.

interp_ens = [10, 100, 1000]
interp_xs = interpobj(interp_ens)

Importantly, also the energy mesh and cross sections at the mesh points can be updated, e.g.:

new_ens = [1, 50, 100]
new_xs = [20, 40, 60]
interpobj.table = (new_ens, new_xs)

Because the variables used by the LinearCrossSectionInterpolationClass are EndfVariable objects, any adjustment performed using an object of that class will also lead to a corresponding update of the endf_dict.

This approach represents one possible way how higher-level functionality could be implemented, which is to some extent decoupled from the detailed structure of the dictionary with ENDF-6 data. Yet, modifications via the high-level object will be propagated back to the original dictionary via the EndfVariable mechanism.