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 installmatters. The hooks (includingcheck-log-updated, which requires aLOG.mdentry when you changesrc/) 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
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
(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
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:
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
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
- Branch from
main:feat/<topic>,fix/<topic>,docs/<topic>, orrefactor/<topic>. - Make the smallest coherent change; add/update tests in
tests/. - Add a dated entry to
LOG.mdfor any major change. - 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
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.