Skip to content

Contributing#

How to contribute#

The good first issues are the bugs and feature requests to start with.

Setting up a development environment#

The project runs in pixi.

  1. Install pixi following the official instructions.
  2. In your clone of the repository, install the environment and the commit hooks:
pixi install
pixi run pre-commit-install

The hooks run on every commit. They format Python, Markdown, YAML and TOML, lint and type-check the Python, and check the licence headers. These commands run the same checks and the rest of the gate by hand:

  • pixi run lint: every commit hook, over every file.
  • pixi run test: the test suite. pixi run test-coverage adds coverage.
  • pixi run compile-tex: print every model in the tree to standalone LaTeX and compile it.
  • pixi run ci: lint, tests, a strict docs build and the LaTeX compile. This is what CI runs. Run it before you push.

Documentation#

The pages under docs/ are Markdown, built by MkDocs with the Material theme. The build is strict: a page with no nav entry in mkdocs.yml, a dead link or a stale anchor fails it. pixi run docs-serve builds the site and serves it at http://127.0.0.1:8000, rebuilding when a page changes.

I have updated the README.md

The home page includes named sections of the README rather than a copy: the badges, the model and the status note. A section --8<-- "README.md:name". Edit inside the markers, and the site follows.

Keep the sections link-free, or link absolutely. A relative link resolves against docs/index.md on the site and against the repository root on GitHub, and only one of those can be right.

I have changed what a model prints

Every page that carries a block a tool writes is listed in tests/test_docs.py's GENERATED table, and a test compares each block to its generator. Regenerate rather than edit, and read the diff:

pixi run python -m tools.home_math   # docs/index.md and README.md, from examples/dispatch.yaml
pixi run python -m tools.notation    # docs/reference/notation.md, from tests/typesetting/golden/model.yaml
pixi run python -m tools.spec_math   # the operator table on docs/reference/language/operators.md
pixi run python -m tools.gallery     # the example pages, from examples/

Each tool takes --check to report drift without writing.

I want to add a new page

Add a Markdown file under docs/, then add it to the nav key in mkdocs.yml:

nav:
  - Home: index.md
  - My Page: my-page.md

The module pages under Development are generated from the docstrings, so a new module appears in the next build. A new public name also needs its own ::: entry on the Python API page.

Naming across the layers#

The same construct passes through three layers, and each names it in full. The suffix says which layer:

Layer Suffix Example
YAML block (math_spec.model) Block VariableBlock, PiecewiseBlock
Syntax (math_spec.*_parser) Node NameNode, UnresolvedComparisonNode
Program (math_spec.program) none / Declaration Variable, VariableDeclaration

A node names the operation, not the verb a file writes. One verb can resolve to two nodes, so the file's spelling cannot decide the name.

File verb Node What the node names
sum(over=) Sum dims removed from the result
sum(by=) GroupSum a sum through a relation
at(by=) Pullback a read through a relation
shift(along=) Translate a re-index along one dimension
sum_back(along=) WindowSum a sum over a trailing window

Nothing is abbreviated.

Adding an operator#

Start with the grammar, which is usually free because f(x, k=v) already parses. Then declare the signature in operators.BUILTINS. It holds the number of arguments and says which arguments name dimensions, and resolution reads it from there. Then write the node in program.py and how resolution builds it, the dimension rule in dimensions.py, the degree verdict in degree.py, and the entry in the language reference.

Submitting changes#

To contribute changes:

  1. Fork the project on GitHub.
  2. Create a feature branch to work on in your fork (git checkout -b new-fix-or-feature).
  3. Test your changes using pixi run test, or pixi run ci for everything CI will check.
  4. Commit your changes to the feature branch (you should have pre-commit installed to ensure your code is correctly formatted when you commit changes).
  5. Push the branch to GitHub (git push origin new-fix-or-feature).
  6. On GitHub, create a new pull request from the feature branch.

When you contribute for the first time, ensure your reviewer adds you as a contributor!

Pull requests#

Before submitting a pull request, check whether you have:

  • Written the PR title as a conventional commit subject (see below) — this, not a hand-written entry, is what appears in CHANGELOG.md.
  • Added or updated documentation for your changes (see The docs).
  • Added tests if you implemented new functionality.

When opening a pull request, please provide a clear summary of your changes!

The docs#

docs/ is both the site and what you read on GitHub. What a page is for decides where it goes, in the nav and in the tree: a tutorial (docs/), a how-to guide (docs/howto/), reference (docs/reference/, and the model pages in docs/examples/) or explanation (docs/about/) — the four kinds of Diátaxis — and one page is one kind. A page a model writer does not need goes under Development in the nav: building on math-spec, contributing, or a proof of concept. The rules each kind has to meet, and the sentence-level bar, are in the docs-writing skill. Every page needs a nav: entry in mkdocs.yml, links inside docs/ are relative, and a link outside it is the full GitHub URL; pixi run docs-build is --strict and refuses the rest.

Commit messages#

Merges are squashed, and the resulting subject on main is what release-please reads to build the changelog. So the PR title must be a conventional commit subject:

<type>[(scope)]: <subject>

feat: AST parsing for indexed constraints
fix(parser): where clauses with a trailing comma
docs: describe the two expression tiers

Types are feat, fix, perf, refactor, docs and revert, which appear in the changelog, and chore, test, ci, build and style, which are hidden. A subject the parser cannot read is not an error — the entry simply never appears — so the Conventional commit subject check enforces the format on every pull request.

While the version is pinned to the alpha stream, a breaking marker (!, or a BREAKING CHANGE: footer) is refused, because it moves the base version rather than the alpha counter. Describe the break in the PR body instead. See RELEASING.md.

Beyond the subject line, write whatever body the change deserves — a paragraph or bullet list covering what changed and its impact.

Code conventions#

Start reading our code and you'll get the hang of it.

We mostly follow the official Style Guide for Python Code (PEP8).

We have chosen to use the uncompromising code formatter and linter ruff. When run from the root directory of this repo, pyproject.toml should ensure that formatting and linting fixes are in line with our custom preferences (e.g., maximum line length). To make this a smooth experience, you should run pixi run pre-commit-install after setting up your development environment. If you prefer, you can also set up your IDE to run these two tools whenever you save your files, and to have ruff highlight erroneous code directly as you type. Take a look at their documentation for more information on configuring this.

We require all new contributions to have docstrings for all modules, classes and methods. When adding docstrings, we request you use the Google docstring style.

Releases#

Nothing here is done by hand. release-please opens a release PR from the conventional-commit subjects on main; merging it tags the release, and the tag is what builds and publishes the package. While the project is on the alpha stream that release PR is merged automatically, so every merge to main cuts a version.

The version is never written down in the source tree — it comes from the git tag at build time, and math_spec.__version__ reads it back from the installed package metadata.

See RELEASING.md for the full pipeline, the alpha-stream rules, and the one-time repository setup it still needs.