Mosaic grows as the community adds solver backends, improves configurations, and extends to new domains. This guide covers the contribution paths and the local tooling you need. Any contributions you make are greatly appreciated.
Ensure your contributions adhere to the Code of Conduct.
Mosaic is developed under the Apache 2.0 license. By contributing to the Mosaic project you agree that your code contributions are governed by this license. We require you to sign our Contributor License Agreement to state so.
A solver backend is a Tesseract wrapper that implements the forward map and (optionally) its VJP for an existing benchmark domain. The full walkthrough — directory layout, schemas, apply / abstract_eval / vector_jacobian_product, the metadata.mosaic: config block, and domain-specific overrides (exclusions, input_overrides, explained_anomalies) — is in Part A of the Add a Backend tutorial (source: docs/tutorial.qmd). It uses a short pseudo-spectral solver on the ns-grid domain as a running example.
Results reflect out-of-the-box configurations at the time of each tagged release. If you believe a solver can perform better with different settings, submit a PR with the improved configuration.
Solver behavior can be adjusted at three levels:
- Solver-internal parameters in
tesseract_api.py— sub-stepping counts, relaxation factors, convergence tolerances, time integration order, etc. - Input overrides in the problem config (
mosaic/benchmarks/problems/<domain>/config.py, via_SOLVERS["<solver>"].input_overrides = {...}) — solver-specific values for shared schema fields (e.g. forcing a particulardtordensity). - Schema defaults in
tesseract_config.yamlor the solver'sInputSchemasubclass — changing the default value of an exposed parameter.
-
Snapshot the current state:
mosaic status --format json > before.json -
Make your changes and run the affected suites:
# Quick iteration (small problem size) mosaic run -p <problem> --suites gradient,optimization -s <solver> --debug # Full run (production sizes) mosaic run -p <problem> --suites gradient,optimization -s <solver>
After an edit,
mosaic run --only failed,stale -s <solver>re-runs only the cells that aren't already fresh-ok, leaving the rest of the campaign untouched. -
Compare against the baseline:
mosaic status --format json > after.json mosaic status --format md --diff-against before.json
- The change must not regress any other solver's results.
- CI requires a maintainer to add a benchmark label to any PR that touches
mosaic/code. The options are:benchmark:none— skip benchmarks (maintainer trusts no answer-changing code).benchmark:solver— run benchmarks only on the solvers whose Tesseract code changed, scoped to the problems containing them (any number of solvers; harness/core changes don't widen the run — usebenchmark:allfor that).benchmark:domain— run every solver in the domain whose problem definition changed (derived from the changed files undermosaic/benchmarks/problems/). Use when a change to a problem's physics/config alters the reference the whole domain is benchmarked against. Single-domain by design: a PR changing more than one domain's problem must usebenchmark:all. Solver code changes arebenchmark:solver's job and don't trigger this label.benchmark:all— run the full benchmark suite from scratch.
- Include the diff output in your PR description so reviewers can see the impact at a glance.
A new domain bundles canonical schemas (mosaic/mosaic_shared/problems/<domain>/schemas.py), a Problem package (mosaic/benchmarks/problems/<domain>/, split into config.py, ics.py, physics.py, plus any per-problem optimization.py / plots.py), and at least one reference solver. The fastest way to start is mosaic new-domain <name> --from-template <template>, which scaffolds the file tree from a built-in template. The end-to-end walkthrough — schemas, suite defaults, IC generators, error functions, and a working reference solver — is in Part B of the Add a Backend tutorial (source: docs/tutorial.qmd).
The Quarto site at https://docs.pasteurlabs.ai/projects/mosaic/latest is generated from docs/*.qmd plus two auto-generated files: docs/solvers.qmd (gitignored) and docs/results_*.qmd.
Install the Quarto CLI — this is a standalone binary, not a pip package. Grab it from https://quarto.org/docs/get-started/ (the page autodetects your OS) or:
# Linux x86_64 example — check quarto.org for the latest version
wget https://github.com/quarto-dev/quarto-cli/releases/download/v1.5.57/quarto-1.5.57-linux-amd64.deb
sudo dpkg -i quarto-1.5.57-linux-amd64.deb
quarto --version # should print 1.5.xThen, mirroring .readthedocs.yaml:
uv sync --extra dev
# Generate the solver reference page from tesseract_config.yaml metadata
uv run python docs/generate.py
# (Optional) Regenerate the per-domain results pages from mosaic-results/.
# Skip this if you have no local benchmark results; the tracked copies will be used.
uv run python docs/generate_results.py
# Render the full site to _site/
quarto render
# Or run a live-reloading preview
quarto previewQuarto will error with a missing-file message if you run quarto render before docs/generate.py — docs/solvers.qmd is referenced in _quarto.yml but produced on the fly.
- Python 3.10+, formatted with ruff (
ruff check --fix && ruff format) - Pre-commit hooks enforce formatting on every commit
- Run tests with
pytest
Clone the repository and install the dependencies with pre-commit hooks. With uv this is a single uv sync --extra dev; with pip, install the shared workspace member first:
$ git clone git@github.com:pasteurlabs/mosaic.git
$ cd mosaic
$ python -m venv venv
$ . venv/bin/activate
$ pip install -e mosaic/mosaic_shared
$ pip install -e ".[dev]"
$ pre-commit install- Fork the repository and create a feature branch.
- Make your changes, ensuring
ruff checkandpytestpass. - Open a pull request with a clear description of what you're adding.
- CI runs lint, tests, and tesseract config validation on every PR. If your PR touches
mosaic/code, a maintainer will add one of four benchmark labels (benchmark:none,benchmark:solver,benchmark:domain, orbenchmark:all) to control whether and how benchmarks run.
The project uses several GitHub Actions workflows that work together.
Runs on every PR: linting (ruff), tests (pytest), and Tesseract config validation. Must pass before merge.
Runs on PRs that carry a benchmark label. The workflow has four stages:
- Plan — detects which solvers changed, reads the benchmark label, and builds a (suite, problem, hardware) matrix.
- Build — builds Docker images for any changed solvers and pushes them to GHCR.
- Run — executes the benchmark matrix across CPU and GPU runners.
- Report — merges CPU/GPU results, generates a status snapshot, renders a docs preview, posts a PR comment with the diff, and publishes results.
For dev PRs the report step overlays the PR's results on top of the current rolling baseline so the preview shows all solvers, not just the ones that ran.
For release PRs the report step uploads results the same way; publish-results.yml later creates both the updated baseline and an immutable release-<version> artifact.
Builds the documentation site (production and PR previews). RTD fetches the baseline artifact from GitHub Actions so docs always reflect the latest merged benchmark results. PR preview builds also overlay PR-specific results when available.
RTD builds are triggered explicitly by GitHub Actions workflows (not by the default webhook) to avoid races — results must be published before the docs build starts:
- Push to main —
publish-results.ymluploads the baseline artifact, then triggers an RTD build forlatest. - Release —
publish-results.ymluploads arelease-<version>artifact and triggers RTD builds forstableandlatest. - PR —
benchmark.ymluploads benchmark results, then triggers an RTD PR preview rebuild.
Handles the full release lifecycle (see Release process below).
Benchmark results are stored as GitHub Actions artifacts (not a git branch):
baseline— rolling accumulation of results merged from dev PRs, overwritten atomically on each merge to main.release-<version>(e.g.release-v0.4.0) — immutable release results with long retention.benchmark-results-<sha>— per-PR results uploaded during the benchmark workflow, used for PR preview builds.
We follow the Conventional Commits specification for all commits that reach the main branch. Each commit is crafted from a pull request that is squash-merged. The commit title and message comes from the pull request title and message, respectively. As such, they should be structured following the specification.
The title consists of a type, an optional scope, and a short description: type[(scope)]: description. The types we use are:
chore: for changes that affect the build system, external dependencies, or general housekeeping.ci: for changes in the CI.doc: for documentation only changes.feat: for a new feature.fix: for fixing a bug.perf: for a code change that improves performance.refactor: for a code change that neither adds a feature nor fixes a bug.security: for a change that fixes a security issue.test: for adding new tests or fixing existing ones.
The scopes we use are:
harness: for changes that affect the benchmark harness or CLI.solver: for changes to solver backends.domain: for changes to benchmark domains.deps: for changes in the dependencies.
In case there are breaking changes in your code, this should be indicated in the message either by appending an exclamation mark (!) after the type/scope or by adding a BREAKING CHANGE: trailer to the message.
Mosaic follows semantic versioning.
(code owners only)
Releases are done via GitHub Actions, which automatically build the release artifacts and publish them to PyPI. To create a new release:
- Make sure the code is in a good state, all tests pass, and the documentation is up to date.
- Trigger the release workflow through the GitHub UI. This opens a new pull request with the release notes and the version number. Full benchmarks run automatically on the release PR.
- Add any additional release notes to the pull request message.
- Once the pull request is ready, merge it into
main. - GitHub Actions will then automatically release the new version and publish the benchmark results as both the updated
baselineand an immutablerelease-<version>artifact. Verify that the release artifacts are correctly built and published.