# Development Guide

Ready to contribute to the project? This guide will get you started.

## Initial Setup

1. Get the code

   ### External Contribution

   If you do not have write access to the [marcelwa/aigverse](https://github.com/marcelwa/aigverse) repository,
   fork the repository on GitHub (see [https://docs.github.com/en/get-started/quickstart/fork-a-repo](https://docs.github.com/en/get-started/quickstart/fork-a-repo))
   and clone your fork locally.
   ```console
   $ git clone git@github.com:your_name_here/aigverse.git
   ```

   ### Internal Contribution

   If you do have write access to the [marcelwa/aigverse](https://github.com/marcelwa/aigverse) repository,
   clone the repository locally.
   ```console
   $ git clone git@github.com/marcelwa/aigverse.git
   ```
2. Change into the project directory
   ```console
   $ cd aigverse
   ```
3. Create a branch for local development
   ```console
   $ git checkout -b name-of-your-bugfix-or-feature
   ```

   Now you can make your changes locally.
4. We highly recommend using [`uv`](https://docs.astral.sh/uv/). It is an extremely fast Python package and project manager, written in Rust and developed by [Astral](https://astral.sh/) (the same team behind [`ruff`](https://docs.astral.sh/ruff/)).
   It can act as a drop-in replacement for `pip` and `virtualenv`, and provides a more modern and faster alternative to the traditional Python package management tools.
   It automatically handles the creation of virtual environments and the installation of packages, and is much faster than `pip`.
   Additionally, it can even set up Python for you if it is not installed yet.

   If you do not have `uv` installed yet, you can install it via:

   ### macOS and Linux

   ```console
   $ curl -LsSf https://astral.sh/uv/install.sh | sh
   ```

   ### Windows

   ```console
   $ powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
   ```

   Check out their excellent [documentation](https://docs.astral.sh/uv/) for more information.
5. We also highly recommend installing and setting up [prek](https://prek.j178.dev/) to automatically
   run a set of checks before each commit.

   ### via `uv`

   The easiest way to install prek is via [uv](https://docs.astral.sh/uv/).
   ```console
   $ uv tool install prek
   ```

   ### via `pipx`

   If you prefer to use [pipx](https://pypa.github.io/pipx/), you can install prek with
   ```console
   $ pipx install prek
   ```

   ### via `pip`

   If you prefer to use regular `pip` (preferably in a virtual environment), you can install prek with
   ```console
   $ pip install prek
   ```

   Afterward, you can install the Git hooks with
   ```console
   $ prek install
   ```

## Working on the bindings (C++)

Building the project requires a C++ compiler supporting *C++17* and CMake with a minimum version of *3.23*.
Our CI pipeline on GitHub continuously tests the library under Windows, macOS, and Linux.

### Configure and Build

#### TIP
We recommend using an IDE like [CLion](https://www.jetbrains.com/clion/) or [Visual Studio Code](https://code.visualstudio.com/) for development.
Both IDEs have excellent support for CMake projects and provide a convenient way to run CMake and build the project.
If you prefer to work on the command line, the following instructions will guide you through the process.

The project uses *CMake* as the main build configuration tool.
Building a project using CMake is a two-stage process.
First, CMake needs to be *configured* by calling

```console
$ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
```

This tells CMake to

- search the current directory `.` (passed via `-S`) for a `CMakeLists.txt` file.
- process it into a directory `build` (passed via `-B`).
- the flag `-DCMAKE_BUILD_TYPE=Release` tells CMake to configure a *Release* build (as opposed to, e.g., a *Debug* build).

After configuring with CMake, the project can be built by calling

```console
$ cmake --build build --config Release
```

This tries to build the project in the `build` directory (passed via `--build`).
Some operating systems and development environments explicitly require a configuration to be set, which is why the `--config` flag is also passed to the build command. The flag `--parallel <NUMBER_OF_THREADS>` may be added to trigger a parallel build.

### C++ Code Formatting and Linting

To ensure the quality of the code and certain formatting guidelines, we use

- [clang-tidy](https://clang.llvm.org/extra/clang-tidy/) – a static analysis tool that checks for common mistakes in C++ code, and
- [clang-format](https://clang.llvm.org/docs/ClangFormat.html) – a tool that automatically formats C++ code according to a given style guide.

Common IDEs like [CLion](https://www.jetbrains.com/clion/) or [Visual Studio Code](https://code.visualstudio.com/) have plugins that can automatically run clang-tidy on the code and automatically format it with clang-format.

- If you are using CLion, you can configure the project to use the `.clang-tidy` and `.clang-format` files in the project root directory.
- If you are using Visual Studio Code, you can install the [clangd extension](https://marketplace.visualstudio.com/items?itemName=llvm-vs-code-extensions.vscode-clangd).

They will automatically execute clang-tidy on your code and highlight any issues.
In many cases, they also provide quick-fixes for these issues.
Furthermore, they provide a command to automatically format your code according to the given style.

#### NOTE
After configuring CMake, you can also run clang-tidy explicitly on a file by calling

```console
$ clang-tidy <FILE> -- -I <PATH_TO_INCLUDE_DIRECTORY>
```

where `<FILE>` is the file you want to analyze and `<PATH_TO_INCLUDE_DIRECTORY>` is the path to the `include` directory of the project.

Our hook configuration also includes clang-format.
If you have installed prek, it will automatically run clang-format on your code before each commit.
If you do not have the hooks set up locally, the [pre-commit.ci](https://pre-commit.ci) bot will run clang-format on your code
and automatically format it according to the style guide.

#### TIP
Remember to pull the changes back into your local repository after the bot has formatted your code to avoid merge conflicts.

Our CI pipeline will also run clang-tidy over the changes in your pull request and report any issues it finds.
Due to technical limitations, the workflow can only post pull request comments if the changes are not coming from a fork.
If you are working on a fork, you can still see the clang-tidy results either in the GitHub Actions logs,
on the workflow summary page, or in the “Files changed” tab of the pull request.

To get that report before pushing, the `cpp-lint` nox session runs the very same check locally.

```console
$ nox -s cpp-lint
```

It configures a compilation database and analyzes the C++ files that differ from `origin/main` – the slice CI analyzes – printing every finding and failing if there is one.
Pass a revision to diff against that instead of `origin/main`, or `--all` to analyze every C++ file in the repository.
clang-tidy itself comes from a PyPI wheel pinned to the version the workflow uses, so the session needs no system-wide LLVM installation.

## Working on the Python project

We use [nanobind](https://nanobind.readthedocs.io/en/latest/) to expose large parts of the C++ library mockturtle to Python.
This allows to keep the performance-critical parts of the code in C++ while providing a convenient interface for Python users.
All source files related to C++-Python bindings are contained in the `src/aigverse` directory.

### uv  *(recommended)*

Getting the project up and running locally using `uv` is as simple as running:

```console
$ uv sync
```

This will

- download a suitable version of Python for you (if you don’t have it installed yet),
- create a virtual environment,
- install all the project’s dependencies into the virtual environment with known-good versions, and
- build and install the project itself into the virtual environment.

### pip

The whole process is a lot more tedious and manual if you use `pip` directly.
Once you have Python installed, you can first create a virtual environment with:

### macOS and Linux

```console
$ python3 -m venv .venv
$ source .venv/bin/activate
```

### Windows

```console
$ python3 -m venv .venv
$ .venv\Scripts\activate.bat
```

Then, you can install the project via:

```console
(.venv) $ pip install -e .
```

#### TIP
While the above commands install the project in editable mode, so that changes to the Python code are immediately reflected in the installed package,
any changes to the C++ code will require a rebuild of the Python package.

#### NOTE
When attempting to build the documentation, you must make sure to have `graphviz` installed on your system. Installing
the documentation or development dependencies will fail if `graphviz` cannot be detected.

### Ubuntu

```console
$ sudo apt-get install graphviz
```

### macOS

```console
$ brew install graphviz
```

### Windows

```console
$ winget install graphviz
```

or

```console
$ choco install graphviz
```

The way the Python package build process in the above commands works is that a wheel for the project is built
in an isolated environment and then installed into the virtual environment.
Due to the build isolation, the corresponding C++ build directory cannot be reused for subsequent builds.
This can make rapid iteration on the Python package cumbersome.
However, one can work around this by pre-installing the build dependencies in the virtual environment and then building the package without isolation.

Since the overall process can be quite involved, we recommend using [nox](https://nox.thea.codes/en/stable/) to automate the build process.
Nox is a Python automation tool that allows you to define tasks in a `noxfile.py` and then run them with a single command.

### via `uv`

The easiest way to install nox is via [uv](https://docs.astral.sh/uv/).

```console
$ uv tool install nox
```

### via `brew`

If you use macOS, then nox is in Homebrew, and you can use

```console
$ brew install nox
```

### via `pipx`

If you prefer to use [pipx](https://pypa.github.io/pipx/), you can install nox with

```console
$ pipx install nox
```

### via `pip`

If you prefer to use regular `pip` (preferably in a virtual environment), you can install nox with

```console
$ pip install nox
```

We define convenient nox sessions in the `noxfile.py`:

- `tests` to run the Python tests
- `minimums` to run the Python tests with the minimum dependencies
- `lint` to run the Python code formatting and linting
- `docs` to build the documentation

These are explained in more detail in the following sections.

### Running Python Tests

The Python part of the code base is tested via unit tests using the [pytest](https://docs.pytest.org/en/latest/) framework.
The corresponding test files can be found in the `test/` directory.
A `nox` session is provided to conveniently run the Python tests.

```console
$ nox -s tests
```

The above command will automatically build the project and run the tests on all supported Python versions.
For each Python version, it will create a virtual environment (in the `.nox` directory) and install the project into it.
We take extra care to install the project without build isolation so that rebuilds are typically very fast.

If you only want to run the tests on a specific Python version, you can pass the desired Python version to the `nox` command.

```console
$ nox -s tests-3.12
```

#### NOTE
If you don’t want to use `nox`, you can also run the tests directly using `pytest`.

```console
(.venv) $ pytest test/
```

This requires that you have the project installed in the virtual environment and the test dependency group installed.

We provide an additional nox session `minimums` that makes use of `uv`’s `--resolution=lowest-direct` flag to
install the lowest possible versions of the direct dependencies.
This ensures that the project can still be built and the tests pass with the minimum required versions of the dependencies.

```console
$ nox -s minimums
```

#### NOTE
By default, both `tests` and `minimums` skip what is expensive or fragile: the `torch` group is not installed, and the
tests marked `torch` and `network` are deselected.
That keeps a local run cheap and offline, at the cost of exercising less than CI does.

Pass `--full` to run everything, installing every optional dependency group and selecting every marker:

```console
$ nox -s tests -- --full
$ nox -s minimums -- --full
```

CI runs `--full -m "not network"` in the test matrix, keeping the circuit downloads in their own workflow so that a
hiccup at GitHub cannot redden an unrelated run.
A plain local run is therefore not enough to conclude that a change to the `torch` floors is sound.

### Test Fixtures and Markers

The test suite uses layered `pytest` fixtures to keep test setup reusable and localized:

- `test/conftest.py` for cross-suite fixtures and global test behavior.
- Domain-level fixture files like `test/networks/conftest.py`, `test/algorithms/conftest.py`,
  `test/adapters/conftest.py`, and `test/generators/conftest.py`.

When adding tests, prefer reusing an existing fixture over recreating the same network setup inline.
If a setup pattern is reused in multiple files, move it into the closest shared `conftest.py`.

Pytest markers are also configured to support targeted runs.
Useful marker filters include:

- `networks`
- `algorithms`
- `io`
- `generators`
- `adapters`
- `tts`
- `torch` (deselected by default; needs the `torch` group, see `--full` above)
- `network` (deselected by default; downloads benchmark circuits)

Run a marker subset on a specific Python version with:

```console
$ nox -s tests-3.12 -- -m algorithms
```

or run a file-level subset with:

```console
$ nox -s tests -- test/algorithms/test_simulation.py
```

### Python Code Formatting and Linting

The Python code is formatted and linted using a collection of hooks executed via [prek](https://prek.j178.dev/).
This collection includes:

- [ruff](https://docs.astral.sh/ruff/) – an extremely fast Python linter and formatter, written in Rust.
- [ty](https://docs.astral.sh/ty/) – a fast Python type checker.

There are two ways of using these hooks:

- You can install the hooks manually by running
  ```console
  $ prek install
  ```

  in the project root directory.
  This will install the hooks in the `.git/hooks` directory of the repository.
  The hooks will then be executed automatically when committing changes.
- You can use the `nox` session `lint` to run the hooks manually.
  ```console
  $ nox -s lint
  ```

  #### NOTE
  If you don’t want to use `nox`, you can also run the hooks directly using `prek`.
  ```console
  $ prek run --all-files
  ```

### Python Documentation

The Python part of the code base is documented using [Google-style docstrings](https://google.github.io/styleguide/pyguide.html#s3.8-comments-and-docstrings).
Every public function, class, and module should have a docstring that explains what it does and how to use it.
Ruff will check for missing docstrings and will explicitly warn you if you forget to add one.

We heavily rely on [type hints](https://docs.python.org/3/library/typing.html) to document the expected types of function arguments and return values.
For the compiled parts of the code base, we provide type hints in the form of `.pyi` stub files in the `python/aigverse` directory.
These stubs are generated from the nanobind-based extension modules.

You can regenerate the stubs using the dedicated `nox` session:

```console
$ nox -s stubs
```

The Python API documentation is integrated into the overall documentation that we host on ReadTheDocs using the
[sphinx-autoapi](https://sphinx-autoapi.readthedocs.io/en/latest/) extension for Sphinx.

## Working on the Documentation

The documentation is written in [MyST](https://myst-parser.readthedocs.io/en/latest/index.html) (a flavour of Markdown) and built using [Sphinx](https://www.sphinx-doc.org/en/master/).
The documentation source files can be found in the `docs/` directory.

On top of the API documentation, we provide a set of tutorials and examples that demonstrate how to use the library.
These are written in Markdown using [myst-nb](https://myst-nb.readthedocs.io/en/latest/), which allows to execute Python code blocks in the documentation.
The code blocks are executed during the documentation build process, and the output is included in the documentation.
This allows us to provide up-to-date examples and tutorials that are guaranteed to work with the latest version of the library.

#### NOTE
When attempting to build the documentation, you must make sure to have `graphviz` installed on your system. Installing
the documentation or development dependencies will fail if `graphviz` cannot be detected.

### Ubuntu

```console
$ sudo apt-get install graphviz
```

### macOS

```console
$ brew install graphviz
```

### Windows

```console
$ winget install graphviz
```

or

```console
$ choco install graphviz
```

You can build the documentation using the `nox` session `docs`.

```console
$ nox -s docs
```

This will install all dependencies for building the documentation in an isolated environment, build the Python package, and then build the documentation.
Finally, it will host the documentation on a local web server for you to view.

#### NOTE
If you don’t want to use `nox`, you can also build the documentation directly using `sphinx-build`.

```console
(.venv) $ sphinx-build -b html docs/ docs/_build
```

The docs can then be found in the `docs/_build` directory.

## Releasing

Releases are cut from `main` and published from the GitHub Releases UI.
Publishing a release is what ships the package: the `release: published` event triggers the `publish-to-pypi` job in `.github/workflows/cd.yml`, which uploads the sdist and the wheels to PyPI via trusted publishing, with build provenance attestation.
Pushing a tag on its own publishes nothing.

There is no version number to bump anywhere.
`pyproject.toml` declares `dynamic = ["version"]`, and `vcs-versioning` derives the version from the git tag at build time into the generated `python/aigverse/_version.py`.
The tag is the single source of truth.

1. **Land everything intended for the release on `main`, and label it.**
   Release Drafter (`.github/release-drafter.yml`) sorts pull requests into the release notes by label, so an unlabeled pull request ends up uncategorized.
   Build and CI work needs the `tooling` label to show up under `🔧 Tooling, Build & CI`; `build_system` and `testing` alone are not enough, because Renovate applies those to its pinned-revision bumps.
   Routine Renovate and pre-commit.ci bumps are collapsed into a single `⬆️ Dependencies` block at the bottom, so they need no special handling.
2. **Open a release-preparation pull request**, labeled `release-prep` so that it excludes itself from the notes.
   In `CHANGELOG.md`, rename the `## [Unreleased]` heading to `## [X.Y.Z] - <release date>`, add a fresh, empty `## [Unreleased]` heading above it, and update the link references at the bottom: point `[unreleased]` at `compare/vX.Y.Z...HEAD` and add `[X.Y.Z]` pointing at `releases/tag/vX.Y.Z`.
   Only touch `UPGRADING.md` if the release contains breaking changes.
3. **Merge it.**
   Release Drafter keeps a draft release up to date on every push to `main`, so the notes for the next release already exist.
4. **Publish the draft.**
   Open it under [Releases](https://github.com/marcelwa/aigverse/releases), check that the resolved version and the `vX.Y.Z` tag are correct — the version resolver increments the patch level unless a pull request in the release carries a `minor` or `major` label — and set the body to the `[X.Y.Z]` section of `CHANGELOG.md`.
   Make sure the release targets `main`, then publish it and watch the `🐍 • Packaging` workflow deploy to PyPI.
