Software contribution and development guide

Python

We mainly use PEP8 for all python code.

The main additions outside of PEP8 are:

A summary of the most commonly used conventions are:

For a bonus illusration of the case-naming conventions, see the bottom of the page.

Linting

It it recommended to have an automatic linter installed such as flake8 that can be integrated directly into the IDE and the ci/cd pipelines.

For example, for Sublime Text, using the package SublimeLinter with the following settings:

// SublimeLinter Settings - User
{
    "linters": {
        "flake8": {
            "disable": false,
            "args": [
                "--ignore=E251,E126,E226",
                "--max-line-length=79", 
            ],
        },
    },
}

The above will highligt most code-style issues. Here the ignored items are

With regards to E226, PEP8 also states that

If operators with different priorities are used, consider adding whitespace around the operators with the lowest priority(ies). Use your own judgment; however, never use more than one space, and always have the same amount of whitespace on both sides of a binary operator.

Most IDE’s also have the option of column rulers and this should also be used. Again for the example of Sublime Text this is set by adding "rulers": [79,110], to the general configuration.

Example

Style guides are important for scientific software for readability of code. If the code is readable, contributions are easier, maintinance is easier and validation of the implemented physics or mathemetics is easier. As an example, consider parsing these two pieces of code that perform the exact same operations:

import numpy as n
import scipy.constants as c
def gmf(x, z_tx, z_rx, o):
    r0,v0,a0=x[0],x[1],x[2]
    rng=r0+v0*o.tau+0.5*a0*o.tau**2.0
    phase=n.exp(-1j*n.mod(2.0*n.pi*rng/o.lam,2.0*n.pi))
    rng_samples=n.array(n.round(o.sample_rate*rng/c.c),dtype=n.int)
    idx=n.arange(o.n_ipp*o.ipp)
    mf=z_rx[rng_samples+idx]*phase*z_tx
    s=n.abs(n.sum(mf))
    return(-s)

versus

import numpy as np
import scipy.constants as constants


def generalized_match_function(
                parameters, 
                transmitted_waveform, 
                recived_waveform, 
                radar
            ):
    '''Calculates the generalized match function by shifting the transmitted 
    waveform according to a given phase offset model, dependent on the input 
    parameters in `x`, and convolving with the received waveform[1]_.

    Parameters
    ----------
    parameters : numpy.ndarray or list
        Phase offset model parameters, must contain three elements.
    transmitted_sig : numpy.ndarray
        The transmitted waveform.
    recived_sig : numpy.ndarray
        The received waveform.
    radar : this_package.Radar
        Radar instance containing the radar specific information.

    Returns
    -------
    float
        Negative of the generalized match function absolute value.

    .. [1] Markkanen, J., Lehtinen, M., Landgraf, M., 2005. 
        Real-time space debris monitoring with EISCAT, 
        https://doi.org/10.1016/j.asr.2005.03.038
    '''
    range0 = parameters[0]
    vel0 = parameters[1]
    accel0 = parameters[2]

    dt = radar.sample_time
    target_range = range0 + vel0*dt + 0.5*accel0*dt**2.0

    phase_angle = 2.0*np.pi*target_range/radar.wavelength
    phase_angle = np.mod(phase_angle, 2.0*np.pi)
    phase = np.exp(-1j*phase_angle)

    range_in_samples = np.array(
        np.round(radar.sample_rate*target_range/constants.c),
        dtype=np.int,
    )

    # IPP stands for Inter Pulse Period
    total_ipps = radar.ipp_number*radar.ipp_length
    waveform_inds = range_in_samples + np.arange(total_ipps)

    match_function = recived_waveform[waveform_inds]*phase*transmitted_waveform
    match_value = -np.abs(np.sum(match_function))

    return match_value

The first code listing is shorter and more compact, but much harder to decypher for a fresh pair of eyes.

.editorconfig

Below is an example editor config that supports the PEP8 standard

root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true

[*.py]
max_line_length = 79
indent_style = space
indent_size = 4
trim_trailing_whitespace = true

Some editors require plugins to support the editorconfig file as is.