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.

  • Develop code in the git develop.

  • 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.

Integration of Fortran and Numpy

The f2py utility included with numpy makes it quite easy to integrate simple Fortran code with Numpy. Typically, we wish to pass memory buffers from numpy ndarrays into a Fortran subroutine, and we modify those buffers with Fortran. There are some very annoying issues that can arise because the ways in which the Numpy package manipulates the inernal memory buffers of ndarrays, which might surprise you. These under-the-hood manipulations might be harmless until the day you really care about operating directly on memory buffers. Examples of such complications can be found in the example Mixing fortran with numpy using f2py.

Another matter is the way that numpy arrays are passed to fortran routines when you use f2py. The documentation 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.

Given the above we’ve come up with the following recipe to avoid possible issues:

  1. Always work with the default C-contiguous ndarray memory layout in Python code.

  2. Use assert statements in function wrappers: e.g. assert a.flags.c_contiguous == True.

  3. Transpose ndarrays before passing them to Fortran routines. This will not copy memory.

  4. In your Fortran code, simply reverse the ordering of your indices as compared to your Numpy code.

Although it may be 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 array memory, or (b) enforcing a consistent non-default internal memory layout for all Numpy arrays that touch a Fortran routine. Both options (a) and (b) are highly undesirable. We choose option (c), reverse the index order, because it holds the big advantage that we get to 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 and potential memory issues.