Developers

Before you modify any code:

  • The “Zen of Python” captures the essence of Python programming norms. Please follow norms for the sake of consistency.

  • Follow the PEP8 guidelines for consistency.

  • Use Black to format your code consistently.

  • Write pytest unit tests for nearly every added feature.

  • Document your code using the Google format.

  • Learn how to use git if you haven’t already.

  • All units are SI (angles in radians) for the sake of consistency.

  • The scope of this project is diffraction under the Born approximation.

Testing

It is very simple to make a unit test with pytest:

  1. Create a file that has a name that begins with test_ in the reborn/test directory

  2. Within this file, write functions with names that begin with test_

  3. Within those functions, include assert statements.

  4. Run pytest in the test directory.

Generation of documentation

Docstrings within the python code automatically find their way into this documentation via Sphinx. Please adhere to the Google format. Here is an example of a decently written doc string:

r"""
Some basic description at the top.  Web `links <www.google.com>`_ can be created.
You might link to other documentation inside of reborn, such as
:class:`reborn.detector.PADGeometry` .  Some classes have shortcuts defined in ``doc/conf.py``, such as
|PADGeometry|.  You can also link to external code docs, for example :func:`np.median`.

Here is a random equation for the purpose of demonstration:

.. math::
    a_i = \sum_n f_n \exp(-i \vec{q}_i \cdot (\mathbf{R} \vec{r} + \vec{U}))

You can create a note that will be emphasized:

.. note::
    This is a note, to emphasize something important.  For example, note that the return type is a tuple in this
    example, which requires special handling if you wish to specify the contents as if there are multiple returns.

It is possible to create lists:

* Item number one.
* Item number two.

Arguments:
    data (|ndarray| or list of |ndarray|): Data to get profiles from.
    beam (|Beam|): Beam info.
    pad_geometry (|PADGeometryList|): PAD geometry info.
    mask (|ndarray| or list of |ndarray|): Mask (one is good, zero is bad).
    n_bins (int): Number of radial bins.
    q_range (tuple of floats): Centers of the min and max q bins.
    statistic (function): The function you want to apply to each bin (default: :func:`np.mean`).

Returns:
    (tuple):
        - **statistic** (|ndarray|) -- Radial statistic.
        - **bins** (|ndarray|) -- The values of q at the bin centers.
"""

If you modify code and wish to update this documentation, the easiest way to do so is to run the script update_docs.sh from within the doc directory.

Speeding up code with numba and f2py

Numba is one way to speed up Python code in cases where there is not an existing numpy function. It is used within reborn in a few places and appears to be reasonably stable, though still lacking some very basic functionality.

Making Qt GUIs in reborn

The graphical user interfaces (GUIs) in reborn make extensive use of the pyqtgraph package. In order to maintain compatibility with PyQt5, PyQt6, PySide2, and PySide6, we adopted the same abstraction layer created by the pyqtgraph developers. However, we found that pyqtgraph itself broke backward compatibility (e.g. with pyqtSignals), and thus reborn had to adopt an abstraction layer (reborn.external.pyqt) on top of pyqtgraph’s. If you attempt to make Qt-based GUIs in reborn, you should always import your Qt modules as in this example:

# Do this
from ..external.pyqt import QColorDialog, QComboBox
# Do **NOT** do this:
from PyQt5.QtWidgets import QColorDialog, QComboBox

If something in Qt is not yet included in the reborn.external.pyqt module, then you should add it yourself (it is very easy to do). If you try to import directly from PyQt5 or similar, your code will not work for other people and will not be accepted into the reborn develop or master branches.

Integration of Fortran and Numpy

The f2py utility included with numpy is used within reborn to integrate Fortran code with Numpy. For maximum speed, we often wish to pass (by reference) ndarray memory into Fortran subroutines, and then modify the memory inplace with the Fortran code (i.e. we want fast Fortran code that avoids making pointless memory copies that are typical of numpy). Unfortunately, there are some serious issues that can arise because the ways in which the numpy silently manipulates the internal ndarray memory. Examples of such complications can be found in Mixing fortran with numpy using f2py. Part of the issue is made clear in the f2py documentation, which states the following:

In general, if a NumPy array is proper-contiguous and has a proper type then it is directly passed to wrapped Fortran/C function. Otherwise, an element-wise copy of an input array is made and the copy, being proper-contiguous and with proper type, is used as an array argument.

In other words, f2py will perform silent memory manipulations just like the rest of numpy. Although you might want to pass your ndarray memory to Fortran by reference, f2py might instead make a copy, which will make inplace operations impossible. For the purposes of the reborn package, we use the following recipe to avoid memory issues:

  1. If inplace operations are important, the python code using reborn must always work with the default C-contiguous ndarray memory layout in the Python code. The data types must also be correct.

  2. As needed, use assert statements in reborn Python code that wraps Fortran routines: e.g. assert a.flags.c_contiguous == True. Don’t make it difficult for the user to determine that inplace operations are not functioning as expected.

  3. In the python wrapper code, always transpose ndarray inputs before passing them to Fortran routines. This will not copy memory.

  4. In your Fortran code, you will need to reverse the ordering of your indices as compared to your Numpy code. (And, of course, note that the default Fortran indexing starts with 1 rather than 0).

Although it may seem inconvenient to reverse your indexing when going between the Fortran and Python code, bear in mind that this can only be avoided by (a) making copies of ndarray memory, or (b) forcing everyone to use non-default f-contiguous memory layout for all ndarray objects operated on by a Fortran routine. Both options (a) and (b) are highly undesirable. Reversing the indexing order is actually advantageous because the programmer can think about memory in the most natural way for both Numpy and Fortran coding, rather than insisting that Fortran and Numpy syntax look the same at the expense of speed. If speed is unimportant, then Fortran is altogether a needless complication…