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:
Create a file that has a name that begins with
test_
in thereborn/test
directoryWithin this file, write functions with names that begin with
test_
Within those functions, include assert statements.
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:
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.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.
In the python wrapper code, always transpose
ndarray
inputs before passing them to Fortran routines. This will not copy memory.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…