Development¶
This page documents the expected contributor workflow. The most important rule is that code, tests, and docs should move together: public behavior changes should not land without tests and documentation.
Environment¶
python -m pip install -e '.[yaml,viz,graph,docs]'
python -m pip install pre-commit
pre-commit install
If the pre-commit console script is not on PATH, use python -m pre_commit ....
Required checks¶
Run these before committing:
pytest -q
mypy src
python -m compileall -q src tests
pre-commit run --all-files
sphinx-build -W --keep-going -b dirhtml docs docs/_build/dirhtml
The pre-commit configuration currently runs merge-conflict checks, YAML checks,
end-of-file fixes, trailing whitespace trimming, isort, mypy on src, and black.
Type checking covers src, as in CI and pre-commit. The tests are not held to it:
many predate the annotation policy below, and validation tests pass deliberately
mistyped values, so mypy tests reports errors. New tests should still be annotated
and should wrap intentionally invalid arguments in typing.cast(Any, ...).
Documentation checklist¶
When changing public behavior, update docs in the same change:
Public Python API change: update API reference.
New or changed config field: update Configuration.
New or changed CLI command/option/artifact: update CLI reference.
New workflow or recommended usage: update Getting started or Examples.
Changed conceptual model: update Concepts.
Change that can move a measured figure (a partitioner, an objective weight, the aggregator, a scheduler): rerun
python examples/reproduce_readme_figures.pyand update the README and Entanglement model from its output. A figure goes into the docs only with a section of that script behind it.
Use Index as the navigation source of truth. If you add a new docs page, link it from the index and from the README documentation section.
Type-checking policy¶
QuPort ships py.typed; public annotations are part of the developer-facing API.
Runtime validation should still guard user-provided values because config files,
notebooks, and CLI workflows can pass malformed data even when type checking is not used.
For tests that intentionally pass invalid types to exercise runtime validation,
prefer typing.cast(Any, value) at the call site rather than weakening production
function signatures.
Runtime validation policy¶
Reject booleans for integer/numeric fields unless a field is explicitly boolean.
Validate public API inputs before passing them to Qiskit where possible so users receive QuPort-specific error messages.
Keep validation errors deterministic and specific enough for tests.
Validate serialized artifacts before writing files when invalid data could leave partial or misleading outputs.
Testing guidance¶
Add tests at the level where behavior is guaranteed:
unit tests for pure helpers and validation;
smoke tests for Qiskit-dependent mapping/compilation paths;
regression tests for previously observed edge cases;
CSV/artifact tests for file-writing behavior.
Prefer small deterministic circuits in tests. Random-circuit tests should always use a fixed seed.
Docs site workflow¶
The documentation is a Sphinx site built from Markdown with MyST. Source files live
in docs/, docs/conf.py owns the production build configuration, and GitHub
Actions publishes docs/_build/dirhtml to GitHub Pages from the main branch.
Useful local commands:
make -C docs html # standard local HTML build
make -C docs strict # production-equivalent build with warnings as errors
make -C docs linkcheck # external and internal link validation
The repository does not require a dedicated Markdown linter, but contributors should at least check local links when editing docs. A simple check is:
python - <<'PY'
from pathlib import Path
import re, sys
errors = []
for p in [Path('README.md'), *Path('docs').glob('*.md')]:
for target in re.findall(r'\[[^\]]+\]\(([^)#][^)]*)\)', p.read_text()):
if '://' in target or target.startswith('mailto:'):
continue
local = target.split('#', 1)[0]
if local and not (p.parent / local).exists():
errors.append(f'{p}: missing {target}')
print('\n'.join(errors) or 'all local markdown links exist')
if errors:
sys.exit(1)
PY
Pull request expectations¶
A strong QuPort PR should include:
a concise motivation;
implementation details;
tests/checks run;
docs updates for user-visible behavior;
notes about limitations or modeling assumptions when applicable.
Validation in hot paths¶
Public functions validate their arguments on every call. Search loops must not
pay for that repeatedly, so several modules pair a validating entry point with a
prepared or unvalidated core: quport.network.prepare_routing_tables with
route_prepared_link_loads, accumulate_traffic and
accumulate_boundary_counts, and quport.hypergraph._ebit_objective_fast.
When changing either half, keep two properties:
the prepared path performs the same arithmetic in the same order, so results stay bit-identical rather than merely close;
the validating path keeps its checks and its error messages, since those are what the tests pin.
Changing a partitioning objective¶
The objective terms are not independent knobs. w_port and w_cong were tuned
against a w_dist term that charges every cut gate; an e-bit count is smaller by
the aggregation factor, so a penalty left at its old scale stops merely biasing
the objective and competes with it. That is not hypothetical – before its terms
were rescaled, the ebit strategy left 12.6% of the e-bits it is named for above the
proved optimum, against 1.6% after (examples/reproduce_readme_figures.py calibration). When adding or reweighting a term, check the terms are commensurate
on a real instance, not just that the code runs.
quport.exact is the tool for that check. optimal_partition gives the number a
strategy should be compared against, and partition_gap raises outright if a
heuristic scores below a proved optimum, since one of the two would then be wrong.
It is deliberately independent of the partitioners: a shared bug would have to
appear in both a branch-and-bound cost and an annealing objective to go unnoticed.
The branch and bound is itself validated against exhaustive enumeration over every
feasible assignment in tests/test_exact.py, which is the only real argument that
its pruning and canonical form never lose an optimum.