Skip to content

Contributing & Developer Guide

The single guide for working in spatial_graph_algorithms: setup, daily commands, which files to load for each task, the engineering contract, and the pull-request process. New here? Start at START_HERE.md first, then use this guide for the details.


1. One-time setup

git clone https://github.com/DavidFernandezBonet/spatial-graph-algorithms
cd spatial-graph-algorithms
conda env create -f environment.yml
conda activate spatial-graph-algorithms
pip install -e ".[dev]"
pre-commit install        # activate the local hooks (ruff, mypy, API + LOG checks)
pytest tests/ -q          # expect: all green

After that, every session starts with conda activate spatial-graph-algorithms.

pre-commit install matters. The hooks (including check-log-updated, which requires a LOG.md entry when you change src/) only run once installed in your clone. The same LOG rule is also enforced in CI, so a PR that skips it will fail the Quality Gate regardless.

2. Daily commands

pytest tests/ -q          # run all tests — always green before you commit
ruff check src/           # lint — zero errors expected
mkdocs serve              # preview docs at http://127.0.0.1:8000
mkdocs build --strict     # check for broken doc links
mkdocs gh-deploy --force  # publish docs to GitHub Pages

Targeted test runs while developing:

pytest tests/test_simulate_modes.py -v   # one file
pytest -k "reconstruct and mds"          # by keyword
pytest --tb=short -x                      # stop on first failure, compact traceback

3. Context map — which files to load for each task

Load the listed files as context before writing any code.

Task Load these files
Add a simulation mode ARCHITECTURE.md · CODE_STANDARDS.md · src/spatial_graph_algorithms/simulate/graphs.py · tests/test_simulate_modes.py
Add a reconstruction method NEW_METHOD_CHECKLIST.md · ARCHITECTURE.md · CODE_STANDARDS.md · src/spatial_graph_algorithms/reconstruct/__init__.py · src/spatial_graph_algorithms/reconstruct/mds.py
Add a quality metric CODE_STANDARDS.md · src/spatial_graph_algorithms/reconstruct/quality.py · src/spatial_graph_algorithms/metrics/__init__.py
Add a plot CODE_STANDARDS.md · src/spatial_graph_algorithms/plot/network.py · tests/test_plot_simulation.py
Work on denoise ARCHITECTURE.md · CODE_STANDARDS.md · docs/modules/denoise.md · src/spatial_graph_algorithms/denoise/__init__.py
Work on spatial coherence ARCHITECTURE.md · CODE_STANDARDS.md · docs/modules/spatial_coherence.md · src/spatial_graph_algorithms/spatial_coherence/__init__.py
Fix a bug CODE_STANDARDS.md · the failing source file · the failing test
Update documentation docs/api/<module>.md · docs/modules/<module>.md · the source file
Understand the data model src/spatial_graph_algorithms/network.py · docs/modules/network.md
Understand the pipeline ARCHITECTURE.md

4. Task recipes

Add a new simulation mode

1. simulate/graphs.py    → def mode_<name>(positions, ...) -> Set[Edge]; add a branch in build_edges()
2. simulate/__init__.py  → add "<name>" to SUPPORTED_MODES
3. tests/test_simulate_modes.py → add "<name>" to the parametrize list
4. docs/api/simulate.md  → add a row to the modes table
Checklist: returns Set[Tuple[int, int]] in canonical (min, max) form · deterministic given positions (or accepts rng) · handles n=2 · one-line docstring.

Add a new reconstruction method

1. reconstruct/<method>.py → def run_<method>(adjacency, dim, random_state, ...) -> np.ndarray  # (n, dim)
2. reconstruct/__init__.py → add: elif method == "<method>": coords = run_<method>(...)
3. tests/test_reconstruction_report.py → shape check (n, dim)
4. docs/api/reconstruct.md → add ::: line + methods-table row
Checklist: returns (n_nodes, dim) · deterministic with random_state · does not mutate input adjacency · handles disconnected graphs (fill infinite distances with 2 × max_finite) · full NumPy docstring. See NEW_METHOD_CHECKLIST.md for the complete version.

Add a new quality metric

1. reconstruct/quality.py → def my_metric(true_pos, recon_pos) -> float
2. metrics/__init__.py     → results["my_metric"] = my_metric(...); add None fallback in else branch
3. docs/api/metrics.md     → add range + interpretation row
Checklist: rotation/reflection/translation-invariant · handles identical positions (return 0.0, not NaN) · documented range.

Fix a bug

1. Reproduce with a minimal failing test
2. Check CODE_STANDARDS.md for the relevant convention
3. Fix the code
4. Confirm the new test passes and the full suite stays green
5. Commit: fix(<module>): one-line description

Update docs after a code change

1. Edit the docstring in the source file  → mkdocstrings auto-renders it
2. Edit docs/api/<module>.md             → structural changes (new tables/sections)
3. Edit docs/modules/<module>.md         → design-decision changes
4. mkdocs build --strict                 → check links
5. mkdocs gh-deploy --force              → publish

5. Engineering contract

Core principles

  • Keep changes small enough to review. One PR solves one user-visible problem.
  • Preserve the public API unless the PR explicitly declares a breaking change.
  • Make behaviour measurable: new code needs validation, tests, and clear failure modes.
  • Prefer boring implementation over clever implementation unless performance requires otherwise.
  • Don't add dependencies, global state, randomness, or expensive defaults casually.

Function design

Public functions must: - Use explicit type hints on all parameters and the return value. - Carry a docstring in project style (NumPy-style when parameters need explanation, otherwise a one-line summary — see CODE_STANDARDS.md). - Avoid mutating inputs unless the name/docstring makes mutation explicit. - Accept deterministic seed/random_state when randomness is involved. - Validate inputs at the boundary of the public API. - Return project-native objects where appropriate, especially SpatialGraph. - Avoid hidden I/O, network access, plotting side effects, and global config changes.

Internal helpers start with _ unless intended for users.

Public API rules

The public API includes documented modules, names exported in __init__.py, and behaviour shown in examples/docs. For public API changes: add/update docs in docs/, add a test covering the documented behaviour, keep backward compatibility when reasonable, and document any migration path in the PR.

Validation

Validate at public boundaries and fail early with useful messages: - Square adjacency matrices and matching node counts across adjacency/positions/labels/metadata. - Non-negative counts/dimensions/neighbourhood sizes; fractions constrained to [0, 1]. - Optional dependencies with clear install guidance.

Raise specific exceptions: ValueError (bad values), TypeError (wrong types), ImportError with an install hint (missing optional deps). Avoid silent coercion unless documented and tested.

Testing requirements

Every behaviour change needs a test that fails before the change and passes after.

Test type Required when
Unit Adding a helper, metric, validator, or branch
Integration Connecting modules or changing pipeline behaviour
Regression Fixing a bug (minimal reproducer)
Parameterized Same behaviour across modes/options
Optional-dependency Feature depends on an extra (STRND, Leiden, GSE…)

Keep tests deterministic (seed stochastic algorithms). Assert meaningful behaviour, not implementation details. Test edge cases: empty, tiny, disconnected, dense, sparse, invalid. Use pytest.approx/numpy.testing/explicit bounds for floats; prefer invariant checks when exact values are unstable.

Performance & memory

Performance-sensitive changes require evidence. Add benchmark coverage when a change introduces a new algorithm, alters graph construction / reconstruction / shortest paths / nearest-neighbour logic, adds a loop over nodes/edges/pairs, or converts sparse matrices to dense.

  • Benchmark the smallest meaningful operation, not fixture setup.
  • Preserve sparse representations; avoid toarray() / dense pairwise matrices unless input size is intentionally bounded.
  • Document expected memory complexity for algorithms that scale poorly; make hard limits explicit in validation or docs.

The project uses ASV (benchmarks/). Standard pre-PR check:

pip install -e ".[bench]"
asv continuous main HEAD --factor 1.20 --split --only-changed

A 20% regression in any time_*/peakmem_* benchmark should be investigated before merge.

Dependencies

Runtime deps go in [project.dependencies]; optional feature deps in [project.optional-dependencies] (pyproject.toml). Don't add a dependency for a small helper. Discuss new runtime deps first. Prefer optional extras for algorithm-specific integrations. Add tests for both the missing-extra path and the successful optional path. Update install docs when extras change.

TODOs

Use # TODO(owner, issue #NN): explain the constraint, not just the desired change. Prefer an issue when the work is larger than a local follow-up. Use FIXME only for known-incorrect behaviour that must not be forgotten.


6. Conventions

Commit messages

<type>(<module>): short description
Types: feat, fix, docs, test, refactor, chore. Examples: feat(simulate): add hexagonal-lattice construction mode · fix(plot): replace removed numpy.ptp() with max() - min().

Imports

No module imports from a module further right in the pipeline: simulate → reconstruct → metrics → plot → verify. network.py is the only file imported by everyone.


7. Pull-request process

  1. Branch from main: feat/<topic>, fix/<topic>, docs/<topic>, or refactor/<topic>.
  2. Make the smallest coherent change; add/update tests in tests/.
  3. Add a dated entry to LOG.md for any major change.
  4. Run local validation, then open a PR filling in the template.

Minimum local checks before review:

ruff check src/                                              # zero errors
pytest tests/ -q                                            # all green
pytest --cov=spatial_graph_algorithms --cov-fail-under=70   # coverage gate
python scripts/check_public_api.py                          # docstrings + type hints
mkdocs build --strict                                       # docs links (if docs changed)

PR description structure

## What changed
## Why
## API impact
## Validation
## Tests run
## Performance/memory impact
## Follow-ups
If performance or memory is not relevant, say why.

Reviewer checks

  • The test would fail without the implementation.
  • The implementation matches the documented contract; error messages help users fix bad input.
  • Sparse data stays sparse unless dense conversion is justified.
  • The PR doesn't mix unrelated refactors with feature/bug work.
  • New TODOs are actionable and traceable; new dependencies are justified and placed correctly.

8. Reporting bugs

Open an issue with: Python version and OS, a minimal reproducible example, and expected vs actual output. For security issues, email davferdz@gmail.com directly.