DISCLAIMER: This style guide is a living document. While we continue aligning the codebase with it, please view the guide as aspirational rather than fully implemented.
NOTE: If a coding situation arises that is not explicitly covered here, it is best to default to the PEP 8 style guide as the baseline standard. For the official PEP 8 guide, see: https://peps.python.org/pep-0008/.
- Our group currently uses 88 characters for our maximum line length (both for code and documentation). When overflow occurs, the statement should be broken out over multiple lines for readability as determined in code review.
- Indentations should be done with 4 spaces.
- Two blank lines after:
- Imports (section)
- Classes
- Functions
- One blank line after:
- Docstrings
- Methods
- Strings should use single quotes,
'string', when possible. - Exceptions:
- Docstrings
- If your string must contain apostrophes/single quotes
- There should generally be a single space after, but not before, commas and colons.
- For commas, no space should be included if there is a trailing comma in, say, a tuple initialization.
# GOOD
x, y = (0, 1)
# GOOD
x = (0,)
# BAD
x = (0, )- For colons, don't include spacing before or after the colon if using for slicing (with exception of spaces added due to commas).
# GOOD
X2_example_array = array_things[:, ::2]
# BAD
X2_example_array = array_things[: ,: : 2]- Binary operators should generally have a space on either side.
- Exceptions to this may be made to indicate order of operations.
# GOOD
result = a + b**2
# ACCEPTABLE (but less clear)
result = a + b ** 2
# BAD
result = a+b**2 - Assignment and comparison operators (e.g.
=,+=,==,>) should always have a space on either side, except in the case of default value assignment.
# GOOD
def calc_energy(temp=300, pressure=1.0):
# BAD
def calc_energy(temp = 300): - Any printable ASCII character is valid. Non-printable ASCII characters (e.g. Greek letters) are forbidden.
# GOOD
phi
# BAD
Φ - Always use the following abbreviated forms outside of documentation inside your code:
| Word/Phrase | Abbreviation |
|---|---|
| Absolute | abs |
| Auxiliary | aux |
| Bath Correlation Function | bcf |
| Calculate | calc |
| Correlation | corr |
| Deletion | del |
| Derivative | deriv |
| Destination | dest |
| Diagonal | diag |
| Dictionary | dict |
| Dimension | dim |
| Drude Lorentz | dl |
| Dyadic | dyad |
| Expectation | expect |
| Exponential | exp |
| Fast Fourier Transform | fft |
| Function | func |
| Hierarchy | hier |
| Hilbert | hilb |
| Index | idx |
| L-Operator | lop |
| Low-Temperature Correction | ltc |
| Memory | mem |
| Normalize | norm |
| Parameters | param |
| Physical | phys |
| Random | rand |
| Relative | rel |
| Squared | sq |
| System | sys |
| Temporary | tmp |
| Temperature | temp |
| Threshold | thresh |
| Underdamped | ud |
Note: any grammatical version version of these words use the same abbreviation (e.g. normalize/normalized/normalizing/norm)
# GOOD
def calc_energy_deriv(temp_phys):
# BAD
def calculate_energy_derivative(temperature_physical):- Classes should be named in CapWords (CamelCase) style.
- Methods, functions, and most variables should be named in lowercase.
- Global constants should be in all caps (e.g.,
HBAR)
- The name of a list should begin with
list_(e.g.,list_pigments). - The name of N-D Arrays should have a capital letter describing the variable followed by a number describing the dimensionality of the variable. Lastly, a descriptive name follows separated by an underscore (e.g.,
H2_sys_hamiltonian). - If an array's primary use is to be iterated over in the fashion of a list, the list naming convention is acceptable.
# GOOD
H2_sys_hamiltonian
H1_energy_levels
# BAD
hamiltonian_2d
# BAD (missing dimensionality)
sys_hamiltonian
# BAD (lowercase variable letter)
h2_sys_hamiltonian - For variables representing mathematical objects (matrices, vectors, scalars) that use established notation, separate the mathematical symbol from descriptive suffixes with underscores.
# GOOD
K_tmp
theta_initial
mu_hat
# BAD
Ktmp
thetainitial
muhat - For classes:
"""
Brief description of class.
"""-
For methods/functions:
- Triple quotes directly under
def. - Order: Brief description → Parameters → Returns.
- Headings:
Parameters,Returns(title case, underlined with----------). - One space after colons, aligned indentation.
- One blank line between sections.
Description
- Descriptions are concise.
Parameters
- Numbered list:
1. name: type1 | type2 [units: cm^-1]- Note: we use brackets, not parens, for units
- Description on the next indented line, starting aligned with the first character after the colon.
Returns
- Same numbered format as parameters.
- When a method/function has no returns, do not number returns in docstring. Instead, use the following format:
Returns ------- None
- Triple quotes directly under
Example
# GOOD
"""
Brief description of what the function does.
Parameters
----------
1. generic_var: float | np.ndarray [units: cm^-1]
Brief description.
2. rizzler: str [options: 'rizz_yes', 'rizz_no']
Brief description.
Returns
-------
1. fojangle: float [units: fs^-1]
Brief description.
"""
# BAD
"""
Brief description of what the function does.
Parameters
----------
1. generic_var: float | np.ndarray (units: cm^-1)
Brief description.
2. rizzler: str [options: 'rizz_yes', 'rizz_no']
Brief description.
Returns
-------
1. fojangle: float [fs^-1]
Brief description.
"""- Always document the nesting structure of list variables explicitly.
# GOOD
"""
Parameters
----------
1. list_states: list[list[int]]
Nested list where outer list contains trajectories
and inner lists contain state indices.
"""
# BAD (unclear nesting)
"""
Parameters
----------
1. list_states: list
List of states.
"""-
For published articles, cite the relevant section (if applicable) and format references in abbreviated journal style, including:
- Full title
- Author list
- Abbreviated journal name
- Volume, page, year
- DOI (if available)
Example:
- This equation corresponds to Eq. 23 in Section II of "MesoHOPS: Size-invariant scaling calculations of multi-excitation open quantum systems." Brian Citty, Jacob K. Lynd, et al. J. Chem. Phys. 160, 144118 (2024). (DOI: 10.1063/5.0197825)
-
For preprints, cite the relevant section and provide the full title and link. If a DOI is assigned, include it as well.
Example:
- This function corresponds to Section S2.B from the SI of "Characterizing the Role of Peierls Vibrations in Singlet Fission with the Adaptive Hierarchy of Pure States," available at https://arxiv.org/abs/2505.02292 (DOI: 10.48550/arXiv.2505.02292)
- Comments will be placed directly above the piece of code they describe and will begin with a space followed by a capital letter.
- Exception: when assigning
__slots__, inline comments should be used.
# GOOD
# Calculate the trace of the density matrix
trace_value = np.trace(rho2_density)
# GOOD (slots)
__slots__ = (
'basis', # Basis management (HopsBasis)
'storage', # Storage management (HopsStorage)
'dsystem_dt', # System derivative function
'_phi', # Full hierarchy vector (current state)
)
# BAD (inline comment after code)
trace_value = np.trace(rho2_density) # Calculate trace
# BAD (lowercase start)
# calculate the trace of the density matrix
trace_value = np.trace(rho2_density)
# BAD (no space)
#Calculate the trace
trace_value = np.trace(rho2_density)- We require clear comments for any of the following logic:
- Array Manipulation
- Indexing Schemes
- Memory Management Tricks
- Physical Justifications
- Loop Logic
- Complicated Mathematical Expressions
- Ambiguous Code (if the code is not easily understood, it should be commented)
- Classes should be structured in the following order:
- Docstring
__slots____init__- Other Magic Methods (e.g.
__repr__,__new__,__str__, etc.) - Public Methods (
public_method) - Protected Methods (
_protected_method) - Private Methods (
__private_method) - Properties (
@property) - Setters / Getters (
@property.setter/@property.getter)
# GOOD
class HamiltonianSystem:
"""
Represents a quantum Hamiltonian system.
"""
__slots__ = ['_energy', '_states']
def __init__(self, energy, states):
self._energy = energy
self._states = states
def __repr__(self):
return f'HamiltonianSystem(energy={self._energy})'
def calc_eigenvalues(self):
# Public method implementation
pass
def _validate_states(self):
# Protected method implementation
pass
@property
def energy(self):
return self._energy- Return logic should be as clear as possible. This means two things:
- Returned variables should be descriptively named.
- Where possible, simple logic should be returned without first declaring a variable.
# GOOD (complex logic with descriptive variable)
# Parameters have complicated defaults that aren't similar, so we
# need to build a block of logic for each one:
# thus, we construct the dictionary they live in before returning it.
def define_param_dict(a=None, b=None):
dict_params = {}
if a in params:
dict_params['a'] = a
else:
dict_params['a'] = 0
if b in params:
dict_params['b'] = b
else:
dict_params['b'] = np.sin(a)
return dict_params
# GOOD (simple logic returned directly)
# Simple definition of parameters can be done in few lines,
# no need to pre-define the return dictionary
def define_param_dict_simple(a, b):
return {'a': a,
'b': b}
# BAD (unnecessary variable for simple logic)
def calc_sq_magnitude(vec):
sq_mag = np.dot(vec, vec)
return sq_mag
# BAD (unclear variable name)
def calc_energy_spectrum_max_gap(H2_hamiltonian):
# Ignore that this is a dumb way of doing this, it's an example.
result = np.max(np.linalg.eigvalsh(H2_hamiltonian))-np.min(np.linalg.eigvalsh(H2_hamiltonian))
return result- Always import the specific submodule required, and reference its members explicitly (e.g.,
submodule.function). - If importing from external modules, the from statement should be used sparingly for cases in which a specific object or set of objects will exclusively be used. If you are not sure, import the whole module or relevant submodule.
- If importing from internal modules, the from statement should generally be used unless there are namespace conflicts.
- NEVER use
from module import *. List all objects explicitly.
# GOOD
import numpy as np
import scipy.sparse as sparse
from our_package.hops import HopsTrajectory, HopsBasis
# BAD
from numpy import *
from our_package.hops import *- Imports should be divided into 3 sections, divided by a single line of whitespace. The sections are:
- Python Standard Library Modules
- External Modules
- User-Defined Modules
- Within each section, imports are sorted alphabetically after ordering by category:
import moduleimport module.submodule as subfrom module import object
- Imports should also generally include the specific submodule required (e.g.,
submodule.functionovermodule.submodule.function).
# GOOD
import os
import sys
from pathlib import Path
import numpy as np
import scipy.linalg as la
from matplotlib import pyplot as plt
from mesohops.dynamics import HopsTrajectory
from mesohops.util.physical_constants import HBAR
# BAD (sections not separated, not alphabetical)
import numpy as np
import os
from mesohops.dynamics import HopsTrajectory
import sys- We will use the following terms when describing our unit tests:
- Test suite: a cluster of unit test functions that checks all functionalities of a method / function
- Test: a single unit test function that checks a single functionality of a method / function
- Case: a portion of a unit test function that checks a specific scenario of a single functionality of a method / function
General Rules
- Each unit test file contains all tests for its corresponding source file.
Example:test_hops_trajectory.py→hops_trajectory.py. - Each test suite (function group) tests a single function from the module.
- Each test consists of:
- A comment header describing the functionality being tested.
- A single test function that includes all case checks for that functionality.
- Each case within the test function has its own inline comment describing the specific scenario.
Formatting Requirements
-
Use three consistent comment levels to visually organize files:
- Suite header:
Placed above each test suite, describing which function is being tested.# ============================================================ # TEST SUITE: <function_name>() # ============================================================
- Test header:
Placed above each test function, describing the functionality being tested.# ------------------------------------------------------------ # TEST: <short description of functionality being tested> # ------------------------------------------------------------
- Case comments:
Placed inside the test function, before each logical block of assertions.# This case tests <specific scenario under this functionality>
- Suite header:
-
Maintain consistent spacing and comment alignment.
-
Group related assertions under each case.
Example Structure
# test_<module>.py
# This file contains all tests for <module>.py
import pytest
from <module> import <func_a>, <func_b>
# ============================================================
# TEST SUITE: <func_a>()
# ============================================================
# ------------------------------------------------------------
# TEST: <short description of functionality being tested>
# ------------------------------------------------------------
def test_<func_a>_<functionality>():
# This case tests <scenario under this functionality>
# <assertions>
# This case tests <scenario under this functionality>
# <assertions>
# This case tests <scenario under this functionality>
# <assertions>- In general, you should test anything more complex than simple arithmetic operations or variable/object initializations.
- The only occasions in which unit tests are not necessary are:
- If using a method/function you've imported from somewhere else.
- If it is already tested in a parent class.
- We maintain authorship history within our code in each file header. We will use two authorship distinctions:
__author__and__maintainer__. - The structure of the file header is as follows:
__title__ = "ToadObliterator Class"
__author__ = "Itsam Emario, Wall U. Ouija"
__maintainer__ = "Wall U. Ouija, Toe Debt"- These are developers responsible for a significant portion of code in a file. Lavish them with praise for anything that goes right.
- These are active members of the lab responsible for maintaining the relevant section of the code. Execute them if anything is wrong.