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, which is how the typeset output is proven to be a real document.
  • pixi run ci: lint, tests, a strict docs build and the LaTeX compile, in the order a failure is cheapest to read. 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.

I have updated the README.md

The home page includes named sections of the README rather than a copy: the badges, the diagram, the model, the load snippet, the development install and the status note. A section is delimited in the README by and docs/index.md pulls it in with --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

Six pages carry a block that a tool writes, 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

Without a title in nav, the page's first heading names it.

I want to add images to my docs

Put the image under resources/ and reference it from the Markdown:

![accessible alternative text](../resources/filename.png)

For a caption, use a <figure> with an <img> and a <figcaption>.

I want to update the Python API docs

These pages are generated. A new class or module appears in the next build.

I want to process files into pages automatically

Add the workflow to docs/hooks.py, beside the one that builds the Python API pages.

I want to view my documentation changes locally

pixi run docs-serve builds the site and serves it, usually at http://127.0.0.1:8000. It rebuilds when a page changes.

I want to do something else

The MkDocs and Material documentation answers what this page does not.

Naming across the layers#

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

Layer Suffix Example
YAML block (math_spec.model) Block VariableBlock, PiecewiseBlock
Core AST (math_spec.*_parser) Node VariableNode, DimensionComparisonNode
Program (math_spec.program) none / Declaration Variable, VariableDeclaration

Two rules follow, and a PR that adds a construct keeps them:

  • A node names the coordinate map, not a spelling in the file. The translation node is Translate, and it stayed that way when the file's spelling became a single shift(…, edge=).
  • Nothing is abbreviated. Cmp became ParameterComparison, and vtype became domain.

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, validation and lowering all read it from there. Then write the dimension rule in dimensions.py, the degree verdict in degree.py, the node it lowers to in program.py, and the entry in the language reference.

A consumer that builds models cannot lower an operator that the version it pins does not parse, so the operator lands here before any consumer's half. What a consumer owns is the building: the query or call it makes for the node.

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. 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, chore, test, ci, build, style and revert; the first five appear in the changelog and the rest 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.