Code Guidelines#

This page lists the concrete rules that TorX code should follow. Unlike the Design Principles, which describe how to think about the library, everything here is mechanical and almost all of it is checked automatically by the pre-commit hook and the CI pipeline. If a rule is enforced, the tool that enforces it is named alongside it.

The pre-commit hooks are installed automatically when installing the TorX Environment. You can also install the hooks manually with:

pre-commit install

You can run every check by hand at any time with:

pre-commit run --all-files

If the pre-commit hooks are installed, they run automatically when you make a commit.

Layout#

Line length is capped at 80 characters.

Blank lines should never be doubled. Use a single blank line everywhere: between top-level definitions, between methods, and to separate blocks within a function. This includes after the import block and before decorators.

Indentation and whitespace. Indent with four spaces instead of using tab (\t) characters. There should be no trailing whitespace at the end of a line. These are recommended rather than CI-enforced for now, but most editors can apply these automatically by changing their settings (“indent with spaces”, “trim trailing whitespace on save”).

End of file: every file must end with exactly one newline. A missing final newline and a trailing blank line are both errors. Enforced by infra/pre_commit/check_eof.py, which repairs the files itself when run with --fix (the pre-commit hook does this for you). Binary files and the fixtures in tests/resources/ are exempt.

Unicode characters should not appear in any file, including source, comments, docstrings and markdown. Use LaTeX or plain ASCII instead: write rho, theta, psi (or \rho, \theta, … inside docstrings) rather than the Greek letters, and ->, <=, >=, ~= rather than arrows, inequality glyphs or the approximation sign. Use - rather than an em or en dash, and * rather than the multiplication sign.

Comments must never share a line with code. Put the comment on its own line above the code it describes, never as a trailing comment after a statement, and start it with a capital letter, like a sentence:

# Nominal axis major radius
r0 = 1.0

not r0 = 1.0    # nominal axis major radius. This keeps the code column clean, avoids alignment padding added by hand that goes stale as soon as a name changes, and keeps lines within the 80 character cap.

File naming#

Every Python source file in torx/ and storx/ must be named either __init__.py or end with _m.py, where the _m suffix stands for “module”. Test files are exempt. Enforced by infra/check_py_files.sh.

Imports#

See packages and imports for the conventions used in this codebase.

Docstrings#

Every public module, class, and function needs a docstring following the numpy convention, with Parameters, Returns and other sections as applicable. The summary line must start on the line after the opening """, never on the same line:

def compute_flux(psi, normalization):
    """
    Compute the toroidal flux for a given poloidal flux label.

    Longer description of how the algorithm works, what it is useful for,
    and any edge cases where it does not apply.

    Parameters
    ----------
    psi : xr.DataArray
        Poloidal flux label.
    normalization : Normalization
        Normalization used to convert the result to SI units.

    Returns
    -------
    xr.DataArray
        The toroidal flux.
    """

Enforced by pydocstyle, which you can run directly with:

pydocstyle --convention=numpy --add-select=D213 torx/ storx/

Note that D213 (summary on the second line) is not part of the numpy convention built into pydocstyle, and has to be selected explicitly, so use the full command above rather than a bare pydocstyle torx/ storx/.

This is also run automatically on commit by a single pydocstyle pre-commit hook that checks both torx/ and storx/ together.

LaTeX#

Write inline math with LaTeX between $ delimiters, using standard LaTeX macros for symbols rather than the Unicode glyphs (see Unicode above):

$B_R = \partial V / \partial R$

Because LaTeX macros use backslashes, any docstring or comment that contains them must be a raw string (r"""...""" or r"# ..." where applicable) so Python does not try to interpret sequences like \p as escape characters:

def critical_point(field):
    r"""
    Find the critical point of $\psi(R, Z)$.
    """

The Sphinx build does not currently render $...$ as math (no dollarmath-style extension is enabled), so treat this as a plain-text convention for readability in the source, not as rendered documentation output.

References#

When a formula, algorithm, or constant is drawn from a paper or other external source, cite it with the numpy convention’s References docstring section: an inline [1]_ marker (note the trailing underscore) at the point of use, and the citation listed under a References heading at the end of the same docstring:

def flux_surf_avg(field):
    r"""
    Return the flux-surface average of a field.

    Integrates the field over an infinitesimal volume shell centered on
    the flux surface [1]_.

    Parameters
    ----------
    field : xr.DataArray
        The field to be averaged.

    References
    ----------
    .. [1] A. Stegmeir et al., CPC 2026, Appendix C.
    """

Each docstring that cites a source should carry its own References section rather than pointing at a citation defined elsewhere, since tools like pydocstyle and the API reference render each docstring on its own. A plain code comment (outside any docstring) that needs to point at the same source should describe it in prose instead of reusing the [1]_ marker, since reST citation syntax is only rendered inside docstrings.

Type hints#

Annotate function signatures with type hints where you reasonably can:

def compute_flux(psi: xr.DataArray,
                 normalization: Normalization) -> xr.DataArray:

This is encouraged but not yet enforced, so it is fine to leave hints off where they would be awkward or add no clarity (for example heavily overloaded helpers or code that predates this guideline). Add them to new code and fill them in as you touch existing code. When you do annotate, the hint and the Parameters type in the docstring should agree.

Keep hints readable: import the names you reference rather than spelling out long dotted paths, and prefer the plain built-in generics (list[int], dict[str, float]) available on the supported Python versions.

Packages#

Use pathlib rather than os for file system paths. Prefer Path and its methods (Path(...).mkdir(parents=True, exist_ok=True), p / name, p.open(...), p.read_text(), p.exists()) over os.makedirs, os.path.join, open(...), and os.path.exists. When you touch existing code that still uses os for path handling, migrate it to pathlib.

Use h5netcdf rather than netCDF4 for reading and writing netCDF files (h5netcdf.File(...), or xr.open_dataset(..., engine="h5netcdf")). h5netcdf reads and writes the same files through h5py without requiring the netCDF C library, so it has no system dependency beyond what the torx environment already installs.

Spelling#

Comments, docstrings and prose are spell-checked against a dictionary built from the code itself. This covers Python comments and docstrings, markdown and rst prose, and the comments in shell and config files.

Use American English: write normalize, behavior, center and analyze, not normalise, behaviour, centre or analyse. The dictionary is American, so most British spellings are flagged as plain misspellings, but a few (such as coloured or travelling) are real words the checker recognizes on their own and will not catch, so review by eye as well.

See Spell checking for how to run the checker, how to teach it a new word, and how to exempt a line that is genuinely not prose.

Readability#

The remaining guidance is not machine-checked, but reviewers will look for it:

  • Put spaces around operators, and break up long lines.

  • Use clear, descriptive variable names that do not shadow built-in types or functions. For example, rename integral to integ so it does not collide with int.

  • Pull reused code out into functions rather than duplicating it, and prefer an existing library routine over a new copy. Exceptions may be made where performance is critical.

  • Comment on why the code is written the way it is, rather than restating what it does. Over-documentation makes code harder to read, not easier.

  • A good principle for comments is they should contain as much as necessary and as little as possible to get the information across.

Fail-fast run-time checking (assert, isinstance) is a related habit that reviewers look for. Being a design habit rather than a formatting rule, it lives with the Design Principles.