Thank you for your interest in contributing to the markov-chain-monte-carlo crate! This document is a practical guide for contributors. AI
agents and autonomous tooling should follow AGENTS.md, which is the canonical rule set; this file mirrors the human-facing parts of those rules.
- Code of Conduct
- Getting Started
- Development Environment Setup
- Project Structure
- Development Workflow
- Code Style and Standards
- Testing
- Documentation
- Performance and Benchmarking
- Submitting Changes
- Types of Contributions
- AI-Assisted Development
- Release Process
- Getting Help
This project is governed by CODE_OF_CONDUCT.md. The community is built on:
- Respectful collaboration in scientific computing and statistics
- Inclusive participation regardless of background or experience level
- Excellence in numerical correctness and idiomatic Rust
- Open knowledge sharing about MCMC, Metropolis–Hastings, and proposal design
Before you begin, ensure you have:
- Rust 1.98.0 (pinned via
rust-toolchain.toml— automatically handled by rustup) - Git for version control
- Just (command runner):
cargo install just - uv (Python 3.14 tooling): install the repository-pinned version with
curl -LsSf "https://astral.sh/uv/$(just --evaluate uv_version)/install.sh" | sh(see astral.sh/uv)
-
Fork and clone the repository:
git clone https://github.com/yourusername/markov-chain-monte-carlo.git cd markov-chain-monte-carlo -
Setup the development environment (installs repository-managed tools and verifies system prerequisites — see Development Environment Setup for what gets installed or checked):
just setup
-
Run tests:
just test # Focused unit + doc tests (fast) just test-all # Broad release Rust + doc + Python tooling tests
-
Try the examples:
just examples # Runs all six examples, including additive-target and delayed-telemetry workflows -
Run benchmarks (optional):
just bench-compile # Compile Criterion benchmarks without measuring just bench # Run Criterion benchmarks
-
Code-quality checks:
just check # Run all non-mutating linters / validators just ci # Full local CI simulation (mirrors .github/workflows/ci.yml) just fix # Apply formatters / auto-fixes (mutating)
This project pins its Rust toolchain via rust-toolchain.toml. When you enter the project directory, rustup will automatically:
- install the correct Rust version (1.98.0) if you don't have it
- switch to the pinned version for this project
- install required components (clippy, rustfmt, rust-docs, rust-std, rust-src, rust-analyzer)
No manual toolchain setup is needed — just have rustup installed (rustup.rs).
Install Git, Bash, rustup/Cargo with a native compiler/linker, the pinned just and uv, and jq before running setup.
The expected tool versions live in the root justfile. Release discovery and publication additionally require an authenticated
GitHub CLI; setup does not install gh.
just setup installs repository-managed tools and verifies the system prerequisites the project relies on:
- actionlint — GitHub Actions workflow linter, installed through
actionlint-pyin the uv dev environment - cargo-edit — Cargo dependency requirement updates
- cargo-llvm-cov — coverage reports
- cargo-nextest — Rust unit and integration test runner
- cargo-update — installed Cargo tool updates
- dprint — YAML formatter
- git-cliff — changelog generation
- jq — system-provided JSON validator; install it with your package manager or the official instructions
- rumdl — Markdown formatter / linter
- taplo — TOML formatter / linter
- typos — spell checker
- uv — Python package and tool runner (used for
semgrep,ruff,ty, and the changelog/tagging Python helpers inscripts/) - zizmor — GitHub Actions security analyzer
The recipe checks uv and jq before managed installation work begins. If either system prerequisite is unavailable or the pinned uv version does not
match, it exits with installation guidance; Cargo and uv then install or synchronize the repository-managed tools.
This crate is a single Rust library (no src/main.rs). The detailed file/module map lives in docs/code_organization.md; use it
when asking, "I'm adding a new function/type/trait, which file owns it?"
At a high level:
src/contains the public library modules and the canonical crate-level//!documentation.examples/contains complete runnable workflows.tests/andbenches/contain integration validation and Criterion benchmarks.docs/contains topic guides such as scientific scope, proposal validation, roadmap, release, and Rust tooling notes.scripts/contains Python helpers for changelog and release workflows.- Root configuration files (
justfile,Cargo.toml,rust-toolchain.toml,semgrep.yaml,dprint.json,cliff.toml,typos.toml) define automation, build metadata, validation, formatting, and release behavior.
This file (CONTRIBUTING.md) covers contributor workflow and tooling. docs/code_organization.md covers the narrower
architectural placement question.
This project uses Just as the primary task automation tool. The justfile defines every dev workflow.
Run bare just for the curated workflow guide and just --list for the complete grouped recipe reference. Public recipes are documented, grouped, and kept
in lexicographic source order so both views remain easy to scan.
Essential Just commands:
just setup # Install managed tools / verify system prerequisites
just update # Update dependencies, managed Cargo tools, and tool pins
just check # Run linters / validators (non-mutating)
just ci # Full local CI simulation (mirrors .github/workflows/ci.yml)
just fix # Apply formatters / auto-fixes (mutating)
just test # Focused unit + doc tests (fast)
just test-rust # Broad release Rust tests + doctests
just test-all # Broad Rust + Python tooling tests
just examples # Run all examples
just bench # Run Criterion benchmarks
just bench-compile # Compile benchmarks without measuring
just changelog # Regenerate CHANGELOG.md from git history
just clean # Clean build artifactsjust update advances Cargo dependency requirements and lockfile entries, resolves the latest compatible versions for exact Python development-tool pins,
upgrades the Cargo-installed CLI tools managed by just setup, and reconciles their root justfile pins together with the active uv version. Review the
resulting manifest, lockfile, and tool-pin changes before committing them.
Workflow help:
just --list # All available commands
just help-workflows # Detailed workflow guidance-
Start a feature/fix branch. Prefer
{type}/{issue}-descriptor, e.g.fix/307-acceptance-rate,feat/315-thinning-helpers,doc/329-citation-notes:git checkout -b feat/your-feature
-
Iterate with the smallest changed-surface validator:
# edit code and docs just test-unit # library unit-test changes just test-doc # doctest-only changes just test-integration # one integration-test crate or the integration bucket just notebook-lint # notebook or notebook-checker changes just fix # apply formatters
-
Compose each relevant focused bucket once for final non-core changes. For core Rust changes or a GitHub-equivalent local run, use the full gate:
just check # non-mutating linters and validators just ci # flat GitHub-equivalent validation union
-
Submit:
git commit # see commit-message rules below git push origin feat/your-feature # open a pull request
- Edition: Rust 2024
- MSRV: 1.98.0 (pinned in
rust-toolchain.toml) - Formatting:
cargo fmt --all(configured inrustfmt.toml) - Linting: strict clippy with warnings as errors
- Keep Python helpers and tests portable across Linux, macOS, and Windows. Use
pathlib, avoid platform-reserved fixture names, compare paths using nativePathvalues or normalized relative POSIX text as appropriate, and sort filesystem-derived output with explicit platform-neutral keys.
The fast just clippy recipe checks the core library surface used by just check:
CARGO_BUILD_WARNINGS=deny cargo clippy --locked --workspace --all-features --lib -- -W clippy::pedantic -W clippy::nursery -W clippy::cargo -A clippy::multiple_crate_versionsCargo owns the warning-as-error policy so changing the policy does not invalidate compiled artifacts. The explicit -W flags remain because they enable
Clippy lint groups; CARGO_BUILD_WARNINGS=deny changes the severity of enabled lints but does not select those groups. The crate forbids unsafe_code and
warns on missing_docs; broken intra-doc links are denied. The full just ci gate uses just clippy-all-targets to match the GitHub Clippy SARIF workflow.
Focused test, example, and benchmark validators still provide execution or compile-contract evidence because ordinary compilation does not execute Clippy
lints.
- Prefer borrowed APIs by default. Take references (
&T,&mut T,&[T]) and return borrowed views when possible. Take ownership or returnVeconly when required. - Log-space numerics. Targets and proposal ratios cross the API boundary as
f64log weights.NaNand+∞are explicit error conditions (McmcError);-∞is a legal "impossible state" marker. - Defined floating-point semantics. The five
f64::algebraic_{add,sub,mul,div,rem}methods are forbidden throughout repository-owned Rust because their unspecified transformations can change precision, non-finite and signed-zero behavior, acceptance decisions, and reproducibility. Ordinary IEEE-754 arithmetic and deliberatef64::mul_adduse remain allowed. Any other relaxed or fast-math facility requires a separate tracked scientific review. - Rollback safety.
ProposalMut::propose_mutmust pair withundoso that a rejected mutation leaves state observably unchanged.DelayedProposal::commiterrors are reserved for genuinely exceptional failures applying an already-accepted concrete move. - Detailed balance. New proposal kinds should ship with a
verify_detailed_balance*test for representative discrete transitions.
The repo enforces some project conventions via Semgrep (semgrep.yaml). They cover things like avoiding stdio diagnostics in src/, banning Box<dyn Error>
in src//examples/benches/doctests, banning unwrap()/expect() in doctests/examples/benches (prefer ? and concrete errors, or a benches fixture helper),
requiring expect() reasons, forbidding unwrap-default-on-non-finite, and rejecting f64::algebraic_* operations while preserving mul_add. Run them with
just semgrep and just semgrep-test.
-
Library tests — inline
#[cfg(test)] mod testsin each source file:just test-unit
-
Doctests — examples in
///and//!doc comments:just test-doc
-
Integration tests —
tests/directory:just test-integration
-
Python tooling tests —
pytestover thescripts/helpers:just test-python
-
Benchmark compilation (no measurement):
cargo bench --no-run
-
Broad Rust CI tests — all-feature library unit and integration tests share one optimized build, while doctests remain separate:
just test-rust-ci just test-doc
-
Notebooks — validate JSON, stable cells, output hygiene, cell compilation, Ruff formatting/linting, and Ty before headless execution:
just notebook-lint just notebook-check just notebook-check-slow # only explicitly configured heavy notebooks just notebook-clear-outputs-all # mutating cleanup before committing
Executed notebooks and runtime caches are written under
target/notebooks/; source notebooks are never overwritten by validation.
just test runs focused library tests through nextest plus rustdoc doctests through cargo test --doc; just test-all runs the broad release-profile Rust
test bucket, rustdoc doctests, and Python tooling tests.
For numerical/statistical invariants, use proptest (already a dev-dependency). Put property-based Rust tests in integration
files named tests/proptest_*.rs; keep src unit tests focused on deterministic local behavior unless a private helper cannot be reached otherwise. Seed
RNGs explicitly so failing cases reproduce.
- Use deterministic seeds (
StdRng::seed_from_u64(...)) for randomized tests. - Keep individual unit tests under ~1 second.
- For detailed-balance diagnostics, exercise both the per-transition and batch helpers.
- Doctests are part of the API contract — when you change behavior, update the doctest, don't
,no_run/,ignoreit away.
This crate carries a layered documentation set: a README landing page that is included at the top of docs.rs, a long-form //! block appended below it on
docs.rs, per-topic docs under docs/, and academic references in REFERENCES.md.
src/lib.rs includes the README during rustdoc builds:
#![cfg_attr(any(doc, doctest), doc = include_str!("../README.md"))]Rules:
- Edit
README.mddirectly for the public landing page: badges, pitch/status, install snippet, MSRV, Cargo features, minimal quick start, API-choice guide, examples, docs, contributing, citation, and ecosystem links. - Keep README code examples valid as doctests. The README is included during
cargo test --doc. - Keep
src/lib.rs //!focused on programming-contract material that should appear below the README on docs.rs: API semantics, numerical behavior, proposal responsibilities, checkpoint behavior, detailed-balance diagnostics, and streaming statistics. - Avoid duplicating long-form content between README and
src/lib.rs //!. Short orientation overlap is fine; scientific scope belongs indocs/, API behavior belongs insrc/lib.rs //!, and landing-page prose belongs in README.
For the full agent-facing rule set, see the ## Documentation generation section of AGENTS.md.
- Public APIs: every public function, struct, trait, and module needs a
///(or//!for modules) doc comment. The crate hasmissing_docs = "warn". - Worked examples: include a runnable doctest for non-trivial public items.
- Mathematical context: explain the statistical / numerical meaning of values, especially log-space conventions and where
NaN/±∞are meaningful. - References: when adding a new method or paper, add an entry to
REFERENCES.mdand cite it from the relevant doc.
just doc # cargo doc --no-deps --document-private-items
RUSTDOCFLAGS="-D warnings" cargo doc # fail on rustdoc warningsLong-form discussion lives under docs/. Update these alongside code changes that affect them.
docs/code_organization.md— per-module "where does new code go?" guidance forsrc/*.rsdocs/BENCHMARKING.md— local regression checks, release comparisons, durable assets, and report promotiondocs/reviewer_guide.md— short reading path for scientific and engineering reviewersdocs/scientific_basis.md— Metropolis–Hastings contract and scope discussion that expands the README scientific-basis summarydocs/proposal_validation.md— proposal-author testing patterns andverify_detailed_balance*usagedocs/roadmap.md— planned feature workdocs/dev/rust.md— Rust toolchain notes and tooling deep-divedocs/RELEASING.md— release procedure
Benchmarks live in benches/ and use Criterion.
just bench-latest # run the fixed-seed release-signal set
just bench-latest-vs-last # rerun and compare with the saved local baseline
just performance-local # compare the current tree with the latest stable release
just performance-doc # rebuild the curated report from saved release measurements
just performance-readme # publish the README table and SVG from retained evidence
just bench # run all benchmarks for broader profiling
just bench-compile # compile benchmark harness without measuring
cargo bench --bench stepping <filter> # run a subsetUse just bench-save-last before the first bench-latest-vs-last run. Release maintainers use just performance-release to save validated CSV/JSON evidence
and update the curated report, just performance-doc to reproduce it, just performance-readme to publish its table and plot without remeasuring,
and just performance-github-assets for comparisons
that consume durable release artifacts without local measurements. See
docs/BENCHMARKING.md for the command contracts and interpretation limits.
Performance guidelines:
- profile before optimizing
- prefer borrowed APIs and constant-memory accumulators (
OnlineStats,BinningAnalysis) for hot loops - avoid intermediate
Vecallocations in step / observe paths - be honest about what hot-path work crosses the API boundary as log weights vs. internal exact arithmetic
Coverage:
just setup and the Codecov workflow install llvm-tools-preview because cargo-llvm-cov needs the LLVM coverage tools. The pinned toolchain keeps
that coverage-only component out of the default rustup install path.
just coverage # local HTML report (target/llvm-cov/html/index.html)
just coverage-ci # cobertura.xml for CI- Fork and create a feature branch (
{type}/{issue}-descriptor). - Make changes following the coding standards above.
- Add tests for new functionality.
- Run
just check(orjust cifor the full local simulation). - Update relevant
docs/*.mdand add toREFERENCES.mdif you introduced a new method/paper. - Open a pull request with a descriptive title and body.
Commit subjects and bodies feed CHANGELOG.md through git-cliff. Use Conventional Commits:
<type>: <summary>
Optional body explaining the change in plain prose.
- specific change
- another specific change
Refs: #123
Valid <type> values:
feat— Added (new feature)fix— Fixed (bug fix)perf— Performancedocs— Documentationrefactor/test/style— Changedbuild/chore/ci— Maintenance
Breaking changes use bang notation (feat!: remove deprecated API) or a BREAKING CHANGE: footer trailer so git-cliff detects them.
Avoid Markdown headings (# through ###) in the body — they conflict with changelog section headings. Use plain labels like Refs: or Migration: instead.
Do not include test commands, validation results, or Tests: sections in commit messages unless explicitly requested. Put validation summaries in PR
descriptions or review notes instead.
- Tests pass (
just test-all) - Code is formatted (
cargo fmt) - No clippy warnings (
just clippy) - Doctests pass (
cargo test --doc) - Relevant
docs/*.mdupdated - No long-form API/contract content duplicated between the README and
src/lib.rs //!; short landing-summary overlap is fine -
just checkpasses (fmt-check,clippy,python-check,notebook-lint,validate-json,yaml-check,action-lint,zizmor,justfile-fmt-check,toml-fmt-check,toml-lint,markdown-check,spell-check,release-check,semgrep,semgrep-test) - Commit message follows the Conventional Commits format above
- File an issue on GitHub
- Provide a minimal reproduction, ideally as a failing test or doctest
- Include relevant numerical context (state size, seed, dimensions, etc.)
- Open a discussion or issue first for non-trivial features
- Describe the use case and the proposed API surface
- Consider whether it belongs in this crate (sampling) or in a sibling crate (
delaunay,causal-triangulations)
- Start with a focused PR (one feature or one fix)
- Add tests, including detailed-balance checks for new proposal kinds
- Document the numerical/statistical contract (log-space conventions, when
NaN/±∞is rejected, what rollback guarantees hold)
- Fix typos and improve clarity
- Add worked examples to
///or//!doc comments - Improve
docs/*.mdtopic guides - Add references to
REFERENCES.mdand cite them from the relevant docs
This repository contains an AGENTS.md file, which defines the canonical rules and invariants for AI coding assistants and autonomous agents
working on this codebase.
AI tools, including ChatGPT, Claude, CodeRabbit, and Codex, are expected to read and follow AGENTS.md when proposing or applying changes.
Portions of this library were developed with the assistance of these tools:
All AI-assisted work must be reviewed and validated by a human maintainer before it is merged.
For full tool citation metadata, see the AI-assisted development tools section of
REFERENCES.md.
The full release procedure lives in docs/RELEASING.md. Highlights:
- Run
just update; review, validate, and land dependency/tool upgrades separately before preparing the release PR. - Set
TAG=vX.Y.Zonce, then runjust update-version "$TAG"andjust changelog-unreleased "$TAG". - Run
just performance-release, review the retained evidence, and publish the README table and SVG withjust performance-readme. - Confirm the report reproduces with
just performance-doc, runjust ci, and runcargo publish --locked --allow-dirty --dry-run. - Commit and push the release PR. After merge, sync
main, create and verify the annotated tag withjust tag "$TAG", then push it. - Publish to crates.io, create the GitHub Release, verify the durable Criterion baseline attachment, and delete the merged release branch.
Doc-only changes still require a version bump on crates.io, so prefer to land documentation updates before publishing.
Breaking API changes and MSRV bumps are allowed in any release up to and including v1.0.0, including patch releases. The release number during this
pre-stability period reflects project scope rather than compatibility impact alone; breaking changes still use feat! or a BREAKING CHANGE: trailer.
After v1.0.0, this project follows Semantic Versioning:
- MAJOR: breaking API changes (also flagged by
feat!/BREAKING CHANGE:in commit messages) - MINOR: new features (backwards compatible)
- PATCH: bug fixes and improvements
- GitHub Issues — bug reports and feature requests
- GitHub Discussions — general questions and design discussion
docs/— topic guidesREFERENCES.md— academic references and AI-assisted-development tool citationsAGENTS.md— canonical rules for AI assistants (mirrors a subset here)just help-workflows— local workflow guidance
For mathematical / statistical questions about the underlying algorithms (Metropolis–Hastings, detailed balance, autocorrelation analysis), see the references
cited from docs/scientific_basis.md and REFERENCES.md.
Thank you for contributing!