Contributing¶
Thanks for your interest in this library. This guide covers the development
setup, the checks every change must pass, and how a change gets from your
machine into master. Repository conventions that are easy to get wrong
(two StudentT classes, probability vs. log space, single-use online
likelihood objects, device handling) are in AGENTS.md; read
it before touching the algorithms.
Development setup¶
The library depends only on torch. The test suite also needs numpy and
scipy, which the dev extra provides.
git clone https://github.com/hildensia/bayesian_changepoint_detection.git
cd bayesian_changepoint_detection
python -m venv .venv && source .venv/bin/activate # or: uv venv && source .venv/bin/activate
pip install -e ".[dev]" # or: uv pip install -e ".[dev]"
pytest
Tests live in tests/, one file per area (test_online_detection.py,
test_priors.py, test_studentt_exactness.py, ...). To run a subset:
pytest tests/test_online_detection.py # one file
pytest -k "multivariate and not slow" # by name
pytest -m "not slow" # skip tests marked slow
pytest -m gpu # only the GPU tests (they skip without one)
pytest -m math # only the tests that check the mathematics
pytest --cov=bayesian_changepoint_detection # with coverage (needs pytest-cov, in the dev extra)
CI measures coverage on every Python version and fails below 94% of
statements (about 95% in CI when the floor was last raised; a local run on
macOS reads about one point higher). The table for Python 3.12 is in the
summary of each CI run. If a PR raises coverage, raise the floor in
.github/workflows/ci.yml with it. Never lower the floor to make a PR pass.
Every test carries exactly one of two kind markers, and
tests/conftest.py fails collection when a selected test is missing one
or has both (it runs after -k/-m deselection, so a scoped run only
judges the tests it selected; the full run checks all of them):
math: the value is checked against an independent computation of the same quantity: ascipyevaluation, an exhaustive enumeration of segmentations, a closed form from a cited paper, or a derivation inside the library that shares no code with the one under test (the offline marginal against the product of online predictives; the negative binomial prior atk = 1against the geometric prior). These are the tests that fail when the mathematics is wrong.behavior: the test pins how the library behaves today: API contracts, input validation, device handling, the same formula through two code paths (pdf_rowsagainstpdf, one multivariate call against the sum of per-dimension calls), detection on synthetic data with known changepoints, goldens generated by an earlier version. These tests say when behavior changes; they do not say whether the change is correct.
Tag a whole file with pytestmark = pytest.mark.math when it is
homogeneous, otherwise decorate each test. A behavior test that pins a
number still says in a comment or docstring where the number came from.
slow, gpu, math and behavior are the only registered markers
(pyproject.toml, [tool.pytest.ini_options]). --strict-markers is on,
so decorating a test with an unregistered marker fails collection; a typo
inside a -m expression does not error, it silently selects nothing, so
check the collected count.
The full suite takes well under a minute on CPU. On CUDA or Apple's MPS it
can take minutes, because small tensors are slower on an accelerator. The
default device is the CPU, but tests still pass device="cpu" explicitly
unless they are about device handling, and new tests should do the same.
New tests that need a GPU must carry @pytest.mark.gpu and skip themselves
when none is present; one existing
test, test_device_consistency in tests/test_integration.py, predates
the marker and only skips at runtime.
CI runs pytest tests/ on Python 3.9 to 3.13 with CPU-only PyTorch, a
lint job (ruff check and ruff format --check), an examples job that
runs every script in examples/ headless, a docs job that builds the
documentation site with mkdocs build --strict, and a build job that
makes the sdist and wheel, runs twine check, and installs the wheel into a
clean environment to run both detectors once. Run the linter and formatter
locally before pushing, or let the pre-commit hooks do it on each commit:
pre-commit install # once; hooks come from .pre-commit-config.yaml
ruff check . && ruff format . # what CI checks (ruff is in the dev extra)
Configuration lives in pyproject.toml under [tool.ruff]: rule sets
E, F, W, I, B, UP, line length 88, Python 3.9 as the target, notebooks
excluded. mypy settings are kept there too but mypy is not enforced yet.
Documentation site¶
mkdocs.yml describes the site; the pages are in docs/. Most of them pull
sections of README.md in through snippet section markers, HTML comments
in the README that open and close a named section (start:usage,
end:usage; a comment is invisible on GitHub and PyPI). Edit the README,
not the page, and keep the markers when you move text around.
The API pages are generated from the docstrings. To preview:
pip install -e ".[docs]"
mkdocs serve # http://127.0.0.1:8000, rebuilds on save
mkdocs build --strict # what CI runs; warnings fail the build
CI builds the site on every PR. The published site, https://estcarisimo.github.io/bayesian_changepoint_detection/,
is built from master and deployed by a workflow on the maintainer's fork
(estcarisimo/bayesian_changepoint_detection, .github/workflows/pages.yml),
daily and on demand, because enabling Pages on this repository needs its
owner. A merged docs change is live within a day; to publish at once, run
that workflow by hand.
Conventions¶
- American English for identifiers (functions, methods, classes, arguments,
test markers), docstrings, and the agent instructions in
AGENTS.md:normalize,behavior,color. Reviewers flag British spellings in new names; renaming an existing public name needs a deprecation alias. - NumPy-style docstrings on public functions and classes. When a docstring states a formula, cite the paper and equation it comes from.
- Every behavior change gets a test under
tests/and a line inCHANGELOG.mdunder[Unreleased]. Tests that pin a numerical result say where the expected value comes from (an independent reference implementation, ascipyevaluation, a paper); a test that compares the code against itself proves nothing. Mark the testmathorbehavior(see "Development setup" above). - Changes to the mathematics follow the rules in the "Changing the math"
section of
AGENTS.md: name the source, prove the new form against an independent path, and say in the PR whether outputs change numerically. - Keep PRs small and single-purpose. Do not mix refactors with statistical fixes.
Making a change¶
master is not to be pushed to directly. GitHub does not currently enforce
this on the upstream repository, so it is policy, and it applies to
maintainers and to any automated agent acting for them:
- Every change, however small, lands through a pull request opened from a fork or a topic branch.
- Never force-push to
master. If something has to be undone, revert it through a pull request. - A PR is merged only when CI is green on
lint, everytest (3.x)leg,examplesandbuild, and the review is done: request whatever automated code review the repository has enabled, or ask a person where none is available, and address every comment, either with a fix or with a reply in the thread saying why it does not apply.
Step by step:
- Branch from the current
master:git switch -c <type>/<short-name>(fix/,feat/,docs/,chore/,perf/). - Commit in small, coherent steps.
- Push the branch to your fork and open the PR against
hildensia/bayesian_changepoint_detection:master. Explain what changes, why, and how it was verified; for numerical changes include the numbers. - Iterate until CI is green and the review is addressed, then merge (squash for a single logical change, merge commit when the individual commits matter). Delete the branch afterwards.
Merging needs write access to the upstream repository. Branch protection, repository secrets, PyPI credentials and review-tool settings need the repository owner.
Releasing¶
versioninpyproject.tomlis the only place the version lives;bayesian_changepoint_detection.__version__reads it from the installed package metadata. Bump it there.- Move the
[Unreleased]section ofCHANGELOG.mdunder a new[X.Y.Z] — YYYY-MM-DDheading. - Update
versionanddate-releasedinCITATION.cffand the version in the README's BibTeX entry, and re-measure the README performance table (benchmarks/performance.py --versions current, seebenchmarks/README.md). Open a PR with these changes and merge it. - Optionally rehearse: run the
CDworkflow manually (gh workflow run cd.yml -f target=testpypi) to build and upload to TestPyPI, and install the result in a clean environment. - Tag and create the GitHub release on the upstream repository, not on a
fork: the workflow file is in every clone, but the PyPI publisher is bound
to
hildensia/bayesian_changepoint_detection:git tag vX.Y.Z && git push <upstream-remote> vX.Y.Z, thengh release create vX.Y.Z --repo hildensia/bayesian_changepoint_detection --generate-notes. - The
CDworkflow builds the sdist and wheel, runstwine check --strict, refuses to continue if the version inpyproject.tomldoes not match the tag, uploads to PyPI with Trusted Publishing (OpenID Connect, no stored token or password), and attaches both files to the GitHub release. The distribution isbayesian-changepoint; its PyPI publisher is configured for this repository and thecd.ymlworkflow with the environment left blank. The upload job runs in thepypiGitHub environment (testpypifor rehearsals) so that the upload shows up under "Deployments" on the repository page. A new distribution name needs a matching publisher on PyPI before the first upload.
Reporting issues¶
Open a GitHub issue with a minimal reproducible example: the data or a generator with its seed, the likelihood and hazard/prior used, the device, and the versions of Python and PyTorch. For something security-sensitive, contact a maintainer privately through their GitHub profile rather than posting the details in a public issue.