Contributing to sequince-density#
Contributions to sequince-density are welcome, including bug reports, tests, performance improvements, and new density estimators and flow adapters. This guide describes the development workflow and conventions we use to keep changes easy to review and maintain.
Community and responsibility#
Treat other contributors with respect and follow the Python Community Code of Conduct. Contributions must comply with the project’s license. Preserve required attribution when incorporating third-party material.
AI-assisted contributions are welcome under our AI policy. Contributors are responsible for understanding, validating, and explaining their changes, including their numerical correctness and performance.
Scope of contributions#
Keep each pull request focused on one problem or a closely related set of changes. Discuss substantial API changes, new dependencies, or new algorithms in an issue before investing significant implementation effort. Small fixes can go directly to a pull request.
sequince-density provides fitted proposal distributions and normalizing-flow adapters for Sequince. Core sampling algorithms belong in Sequince; application-specific likelihoods and workflows belong in downstream packages. Prefer existing interfaces and helpers; introduce abstractions when they simplify the implementation or serve a concrete shared need. Avoid unrelated cleanup in bug fixes and feature changes.
Development setup#
Fork sequince-dev/sequince-density on GitHub,
then clone your fork and configure local main to track upstream/main:
git clone git@github.com:YOUR_USERNAME/sequince-density.git
cd sequince-density
git remote add upstream https://github.com/sequince-dev/sequince-density.git
git fetch upstream
git switch main
git branch --set-upstream-to=upstream/main main
Keep local main for upstream changes and do development on separate branches.
Before starting each contribution, update main and create a branch:
git switch main
git pull --ff-only
git switch -c fix-descriptive-name
Here, upstream is the sequince-density repository and origin is your fork. The tracking
setting makes git pull --ff-only on main fetch from upstream. Push contribution
branches explicitly to origin, as shown in the pull request instructions below.
Create a Python 3.11 or later virtual environment and install the package with its development dependencies:
python -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e . --group dev
pre-commit install
The dev dependency group includes the test dependencies for scikit-learn,
Zuko/PyTorch, and FlowJAX/JAX, plus pre-commit and mypy. The editable installation
makes source changes available without reinstalling the package. Dependencies
and tool configuration are defined in pyproject.toml.
When developing against a local Sequince checkout, install it first, for example
with python -m pip install -e ../sequince, before installing sequince-density.
CI tests the built wheel against Sequince 0.1.0b1 on Python 3.11–3.13, with
an additional Python 3.13 compatibility run against Sequince’s main branch.
All density backends are installed and checked for import errors before pytest
runs. Both test and release workflows build a source distribution and wheel,
then validate their metadata with twine check --strict.
Use Pixi for the documentation environment, as described below. Generated lock files and local environments should stay out of pull requests.
Code style#
Follow the existing module’s conventions and use Ruff for formatting and import ordering. Python code uses four-space indentation and a 79-character line limit.
Use descriptive names. Conventional mathematical names are appropriate when their meaning is clear locally.
Document public functions and classes with NumPy-style docstrings, including array shapes, return values, and important assumptions where relevant.
Use comments to explain numerical reasoning, constraints, and non-obvious choices. Avoid comments that simply restate the code.
Preserve supported array backends, devices, and dtypes. Use the existing array helpers and RNG interfaces rather than introducing implicit NumPy conversions or global random state.
Keep eager and functional behavior consistent where both are supported. Keep host scalar conversions outside compiled operations and validate results at appropriate boundaries.
Avoid unnecessary array copies, repeated target evaluations, and retention of unused histories. Expensive diagnostics and logging should not add work when disabled. Support performance claims with reproducible measurements.
Typing#
Add parameter and return annotations to new functions and methods, including private helpers and tests where practical. When changing an existing interface, update its annotations and affected callers together. Annotate stored attributes when their types are not clear from initialization.
Use modern Python syntax supported by Python 3.11, such as
list[str],dict[str, float], andParticleState | None. Prefer collection interfaces such asMappingorSequencewhen callers need not supply a concrete container.Use the existing
ArrayandArrayLikealiases fromsequince.typingfor backend-neutral numerical interfaces. Reserve backend-specific types such asnumpy.ndarrayfor code that requires that backend. UseArrayLikeonly when the function actually accepts and converts those inputs.Describe array shapes, dtype restrictions, and scalar-versus-batch behavior in docstrings. An
Arrayannotation alone does not express these constraints; validate them at appropriate runtime boundaries.Give callbacks explicit signatures and reuse existing protocols and type aliases. Prefer a precise union or protocol to
Anywhen the accepted values have a known contract. KeepAnylimited to genuinely dynamic interfaces.Use
TYPE_CHECKINGfor annotation-only imports when needed to avoid import cycles or loading optional dependencies. Ensure annotations do not introduce new runtime dependency requirements.Treat
castand# type: ignore[error-code]as exceptions. Keep them narrowly scoped and explain non-obvious reasons; neither replaces runtime validation. Fix inaccurate annotations instead of broadly suppressing errors.
The pre-commit hooks run mypy using the configuration in pyproject.toml.
For a standalone type check during development, use python -m mypy.
Passing type checks complements numerical and runtime tests; it does not replace
them.
Docstring example#
Include types in NumPy-style docstrings even when the signature is annotated.
Keep both consistent, and use the docstring to explain shapes, constraints, and
meaning. Use array for backend-native arrays and array_like only when the
function also accepts and converts inputs such as lists.
from sequince.typing import Array
from sequince.array_api import namespace
def particle_mean(particles: Array) -> Array:
"""Compute the unweighted mean of a particle population.
Parameters
----------
particles : array, shape (n_particles, n_dimensions)
Floating-point particle coordinates. The population must be nonempty.
Returns
-------
mean : array, shape (n_dimensions,)
Arithmetic mean along the particle axis, using the input array backend.
"""
xp = namespace(particles)
return xp.mean(particles, axis=0)
Building the documentation#
The documentation uses Sphinx and lives in docs/. From the repository root,
build the HTML documentation with the dedicated Pixi environment:
pixi run -e docs docs
Pixi installs the documentation dependencies defined in pyproject.toml when
preparing this environment. Open docs/_build/html/index.html in a browser to
review the result. Rebuild after editing documentation or API docstrings.
With Sphinx available in your environment, the standard Makefile also supports:
make -C docs html
make -C docs clean
Run make -C docs help to list available targets. Use SPHINXOPTS to pass
additional options, for example make -C docs html SPHINXOPTS=-W.
The developer guide embeds this file using MyST and links to the shared Sequince AI policy. Edit this root Markdown file to update the guidelines on GitHub and in the documentation.
Checks and tests#
Run the following checks before requesting review:
pre-commit run --all-files
python -m pytest tests/
Pre-commit covers Ruff linting and formatting, mypy type checking, spelling,
whitespace, YAML validation, and large-file checks. It does not run the test
suite, so run pytest separately with python -m pytest tests/.
Pre-commit may modify files to fix formatting or other issues. Review those
changes, stage the relevant files, and rerun the checks before committing.
Configuration in .pre-commit-config.yaml and pyproject.toml is authoritative.
Tests use pytest and live in tests/, including tests of integration with
Sequince proposal and transform interfaces. Follow the existing test layout, fixtures, and
parameterization. During development, run the relevant file or individual test:
python -m pytest tests/test_gmm.py
python -m pytest tests/test_gmm.py -k reproducible
Add regression tests for bug fixes and tests for new behavior. Test observable outcomes and numerical invariants rather than implementation details. Use fixed RNG seeds and justified numerical tolerances for stochastic tests. Cover relevant edge cases, such as zero support, degenerate weights, particle shapes, and dtype precision. Changes to checkpointing should exercise save/resume behavior; changes to sampling algorithms should include checks against a known target.
For changes affecting a particular backend, run its tests explicitly:
python -m pytest tests/test_gmm.py
python -m pytest tests/test_zuko.py
python -m pytest tests/test_flowjax.py
The development dependencies install all three density backends. GPU testing requires a compatible PyTorch or JAX installation and hardware. Report the backends, devices, and dtypes tested, along with any skips or unavailable backends. A skipped backend test does not verify that backend.
Commit messages#
Use uppercase SciPy-style prefixes with optional parenthesized scopes for commit subjects and pull request titles:
PREFIX(scope): short imperative summary
PREFIX: short imperative summary
PREFIX/PREFIX(scope): short imperative summary
The scope identifies the affected module or area, such as gmm, zuko,
or flowjax. Start it with a lowercase letter and use only lowercase letters,
digits, dots, underscores, or hyphens. Keep it short, and omit it when the change
spans the project or a scope would add little information.
Choose the prefix that best describes the change:
Prefix |
Use |
|---|---|
|
Fix incorrect behavior |
|
Add or extend functionality |
|
Improve runtime or memory use without changing intended behavior |
|
Change a public interface or introduce an incompatibility |
|
Deprecate functionality |
|
Refactor or maintain code without changing intended behavior |
|
Add or improve tests |
|
Update documentation |
|
Change packaging, dependencies, or build configuration |
|
Change continuous integration workflows |
|
Prepare or maintain a release |
|
Make formatting-only changes |
Use BUG consistently for bug fixes rather than alternating with FIX. A bug fix
that includes a regression test still uses BUG; choose the prefix for the main
purpose of the change.
Use one prefix by default, or two separated by / when both describe the change,
for example MAINT/PERF(resampling): avoid duplicate reductions. The optional
scope follows both prefixes. More than two prefixes are not supported.
Keep the subject concise, preferably within 72 characters, and omit its trailing
period. Keep the prefix uppercase and start the summary after : with a
lowercase letter. Use imperative wording such as “guard”, “add”, or “avoid”.
When useful, add a blank line and a body explaining the problem, the resulting
behavior, and any important tradeoffs. Reference related issues; use
Closes #123 only when the change fully resolves that issue.
Examples:
BUG(gmm): reject nonuniform fitting weights
The scikit-learn mixture fit does not support sample weights. Reject
nonuniform weights explicitly and add a regression test for this case.
PERF(gmm): reduce copies during density evaluation
DOC: clarify contribution guidelines
REL: prepare release 0.1.0
Make commits in coherent units. Pull requests are squash-merged, so their titles should also follow this format and describe the final change.
Pull requests and review#
For each pull request with user-visible changes, update
CHANGELOG.md under Unreleased in the same pull request. Follow
the entry categories and guidance in the releases section below.
Push your branch and open a pull request targeting sequince-dev/sequince-density:main:
git push --set-upstream origin fix-descriptive-name
Use a draft pull request for work in progress or early feedback. In the description:
Explain the problem and resulting behavior, and link related issues.
Describe relevant API changes, numerical assumptions, and compatibility effects.
Summarize validation, including test commands and results. Include reproducible benchmark conditions for performance changes.
Respond to reviewer feedback and keep the description aligned with the final implementation. Reviewers should distinguish required changes from suggestions, explain their reasoning, and raise unrelated findings in separate issues.
Maintainers should wait for applicable CI checks and review discussions to be resolved before merging. Check the squash commit title and message, link or close resolved issues, and delete merged branches in the main repository when no longer needed.
Releases#
Maintain CHANGELOG.md using
Keep a Changelog. Add user-visible changes
under Unreleased, grouped as Added, Changed, Deprecated, Removed,
Fixed, or Security; omit empty groups. For the first release, summarize the
initial features under Added. Describe the
effect on users, especially changes to densities, weighting, RNG behaviour,
APIs, or checkpoint compatibility. Routine internal refactoring and CI
maintenance do not normally need entries.
Releases are coordinated by maintainers. Versions are derived from Git tags by
setuptools-scm; do not maintain a separate hard-coded version number. Use tags
such as v0.1.0 and release notes describing user-visible changes and any
incompatibilities.
Until the first release, keep the initial feature summary under Unreleased
and link that heading to the repository’s commit history. When preparing a
release, move the unreleased entries into a section headed
## [VERSION] - YYYY-MM-DD, using the actual release date, and leave a fresh
Unreleased section at the top. For the first beta, use a PEP 440 version such
as 0.1.0b1 with the Git tag v0.1.0b1. Link its heading to the GitHub release,
and update the Unreleased link to compare that tag with HEAD. For later
releases, link each version heading to the comparison with the preceding tag.
Use the version’s changelog section as the GitHub release notes, and mark beta
releases as prereleases on GitHub.
Before publishing, verify the intended commit, applicable CI results, and the
built distribution in a clean environment. Check that the package version matches
the intended tag, run python -m twine check --strict dist/*, and verify wheel
installation with each supported extra independently. Confirm that the GitHub
release environment and PyPI trusted publisher are configured for this
repository.
Publishing a GitHub release triggers the PyPI workflow in
.github/workflows/publish.yml. Check that the workflow completes successfully
and the published package can be installed from PyPI. Published distributions
cannot be overwritten, so verify the tag and artifacts before publishing.