Code Style And Syntax
Guidance for writing readable, maintainable TARDIS code.
Code Quality
Quick Reference
TARDIS follows PEP 8 using Ruff. Run Ruff directly from the active TARDIS development environment.
Rule locations:
Permanent Ruff rules:
pyproject.tomlNon-permanent Ruff rules:
.ruff.toml
Naming conventions: https://peps.python.org/pep-0008/#naming-conventions
Naming Conventions
We prefer names to be descriptive. For example, tardis/io/model/parse_density_configuration.py uses descriptive
function names such as parse_density_section_config,
parse_density_from_csvy, and calculate_power_law_density. These names are
preferable to short generic names because they state the operation being performed
and the model component being operated on.
While functions and methods should use a verb-noun style, class properties
and variables should use a noun style. For example,
calculate_power_law_density is a good function name,
while power_law_density is a good variable or property name.
Explanation: Variable and Function Names
Use names that describe the scientific or codebase concept, not only the local
implementation detail. Prefer names such as density_configuration,
time_explosion, velocity_field_index, and density_field_index when those
objects refer to specific model quantities or schema locations. Ensure that if in
other parts of the codebase similar concepts exist, use those names.
If you use an object property e.g. packet.velocity, it is reasonable to
assign it to velocity in a local scope if it is used more than once or twice.
Avoid names that are too generic for review, such as data, arr, x, or
idx, unless the scope is very small and the meaning is obvious from the
surrounding line.
Index vs. IDX in TARDIS
In the TARDIS codebase, we attempt to use index and idx as separate designations with different meanings. We use {var_name}_idx to specify that a variable refers to the integer position in some array-like object, with pythonic zero-indexed location (i.e., any pandas object accessed via .iloc would be done so with an idx variable). Alternatively, we use {var_name}_index as a variable that is explicitly not an integer location, but is still used for lookup via a key to a dictionary-like object (e.g., a pandas object accessed with .loc).
Consequently, an object named index in the tardis codebase will often store idx values. The dictionary-like is the index, and the values are idxs.
Good examples:
block_start_idx = opacity_state.macro_block_edge_index[
absorbing_activation_level_idx
]
block_end_idx = opacity_state.macro_block_edge_index[
absorbing_activation_level_idx + 1
]
emission_transition_probability = 0.0
probability_emission_event = np.random.random()
for deactivation_channel_idx in range(block_start_idx, block_end_idx):
deactivation_probability = opacity_state.transition_probabilities[
deactivation_channel_idx, current_shell_id
]
emission_transition_probability += deactivation_probability
Type Hinting
Use type hints for all function definitions in new or touched code. Type hints improve readability and maintainability, and they enable static type checking.
from tardis.opacities.opacity_state_numba import (
OpacityStateNumba,
)
def macro_atom_interaction(
activation_level_id: int,
current_shell_id: int,
opacity_state: OpacityStateNumba,
):
pass
Reference: NumPy Array Typing
Use numpy.typing.NDArray for typed NumPy array annotations in new or touched
code when the function accepts or returns concrete NumPy arrays. NumPy provides
numpy.typing.NDArray as a runtime-available alias for annotating arrays with a
dtype and unspecified shape.
from typing import Any
import numpy as np
import numpy.typing as npt
def normalize(values: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]:
return values / np.sum(values)
def as_array(values: npt.ArrayLike) -> npt.NDArray[Any]:
return np.asarray(values)
Prefer typed arrays over bare np.ndarray.
def calculate_density_after_time(
density_0: npt.NDArray[np.float64],
time_0: float,
time_explosion: float,
) -> npt.NDArray[np.float64]:
...
The typed NumPy API is stricter than runtime NumPy. For example, type checkers
will discourage patterns that create object arrays accidentally or mutate array
dtypes directly. If a function accepts broad array-like input, use
npt.ArrayLike for the input and return a typed npt.NDArray[...] after
conversion.
See the official NumPy typing reference: https://numpy.org/doc/stable/reference/typing.html