.. _data_navigation:
Data Navigation
===============
The core functionality of ``endf-parserpy`` is to
map the nuclear data stored in an ENDF-6 file into a nested
Python dictionary. These dictionaries can be somewhat
deeply nested and hence the navigation in them to access or
manipulate data cumbersome. endf-parserpy introduces
the concept of an :class:`~endf_parserpy.EndfPath`
for referencing a piece of data in a dictionary associated
with ENDF-6 data.
.. _endf_path_class:
EndfPath
--------
Most people are familiar with the hierarchical organization
of data in file systems by nesting directories, and how
a file can be referenced by the provision of a *path*.
Similarly, it is convenient to use a path for referring to a
variable or section in an ENDF-6 dictionary. The path is
constructed by combining the keys of the nested dictionaries
to a single string using a ``/`` character as separator.
For instance, there may be a variable ``AJ`` in a Python dictionary
``endf_dict`` accessible via:
.. code::
endf_dict[2][151]['isotope'][1]['range'][1]['spingroup'][1]['AJ']
The corresponding :class:`~endf_parserpy.EndfPath` would be given by
``2/151/isotope/1/range/1/spingroup/1/AJ``
Some objects in a nested dictionary can be considered as an array
of objects and we may want to use `numpy `_-like syntax.
Therefore, a path should also support notation
of the form ``isotope[1]``, which is equivalent to ``isotope/1``.
Making use of this alternative notation, the path above
could be equally written as:
``2/151/isotope[1]/range[1]/spingroup[1]/AJ``
The class :class:`~endf_parserpy.EndfPath` provides
a container for the storage of a path. However, paths become
only useful when they can actually be used
to navigate dictionaries with ENDF-6 data and retrieve the desired data
from them. The :class:`~endf_parserpy.EndfDict` class helps with this requirement.
.. _endf_dict_class:
EndfDict
--------
An instance of :class:`~endf_parserpy.EndfDict`
behaves like a standard :class:`dict` but
enables the use of paths (as described above) to access
data. When the class is instantiated with a dictionary
as argument, it can be regarded as a `view object` of the
original dictionary. Any modification of data through the
:class:`~endf_parserpy.EndfDict` object will lead to the same modification
of the original dictionary (technically speaking:
:class:`~endf_parserpy.EndfDict`
objects manage a reference to the original :class:`dict` or other :class:`~collections.abc.MutableMapping` object).
Suppose that ``endf_dict`` is an instance of
:class:`~endf_parserpy.EndfDict`.
Using the example given above, we could access the variable ``AJ``
via:
.. code:: python
aj = endf_dict['2/151/isotope[1]/range[1]/spingroup[1]/AJ']
An :class:`~endf_parserpy.EndfDict` instance also
allows keys to be separated by commas
so the same variable could also be accessed via the notation:
.. code:: python
aj = endf_dict[2, 151, 'isotope', 1, 'range', 1, 'spingroup', 1, 'AJ']
It's also possible to mix these two notation forms, e.g.
.. code:: python
aj = endf_dict[2, 151, 'isotope/1', 'range[1]', 'spingroup', '1/AJ']
The flexible notation allows to write down specific
instructions in a very intuitive form. Assume that you want
to modify a covariance matrix in the MF=33/MT=1 section.
You could use the following code:
.. code:: python
F = endf_dict['33/1/subsection[1]/ni_subsection[1]/F']
F[2, 3] = 0.5
This code works because any dictionary-like object retrieved
from an :class:`~endf_parserpy.EndfDict` object
will be automatically wrapped into an
:class:`~endf_parserpy.EndfDict` object itself
before being returned. Consequently, the extended indexing
capabilities are available for these retrieved objects,
such as demonstrated here by the assignment involving ``F``.
Another useful design feature regarding the construction
of dictionaries is the implicit creation of missing
dictionaries. For example, the assignment
.. code:: python
endf_dict['2/151/isotope[1]/range[2]/spingroup[3]/AJ'] = 12.
will create all intermediate dictionaries, hence this instruction
even works for an empty dictionary ``endf_dict = EndfDict({})``.
See the documentation of the :class:`endf_parserpy.EndfDict` class
for further details.
Finally, we may want to use abbreviations to read and modify data in
a dictionary with ENDF-6 data. Perhaps we would like to assign
a new value to the ``aj`` variable and expect that the
same value is also assigned to the corresponding location in ``endf_dict``.
However, this will not be the case. The
:class:`~endf_parserpy.EndfVariable` class
provides a mechanism to achieve this behavior.
EndfVariable
------------
An instance of :class:`~endf_parserpy.EndfVariable`
possesses a ``.value`` attribute
that is always kept in sync with a specific location in
a nested dictionary with ENDF-6 data.
It can be instantiated by providing an :class:`~endf_parserpy.EndfPath` object
and a dictionary:
.. code:: python
ajvar = EndfVariable('2/151/isotope[1]/range[1]/spingroup[1]/AJ', endf_dict)
Any change of the value of ``AJ`` in ``endf_dict`` will be
reflected in ``ajvar.value`` and vice-versa. This class may
be a good basis for implementing
:ref:`higher-level functionality `,
such as linear interpolation of cross sections with the
link to the original data being preserved.
More technical details are provided in the documentation
of the :class:`endf_parserpy.EndfVariable` class.