Getting started

The fiction framework provides a stand-alone CLI tool as well as a C++20 header-only library and a Python module which can be used in external projects. Additionally, we provide an experimentation playground that can be used to quickly prototype new ideas or script evaluations.

We are continuously testing on Ubuntu, macOS, and Windows with multiple compilers and various Python versions. See the badges in the README file for more information.

CI builds every commit in Debug and Release with the following combinations. Any C++20 compiler should work; these are the ones we verify.

Platform

Compilers

Ubuntu 24.04 (x86-64)

GCC 13, GCC 14, Clang 18

Ubuntu 24.04 (ARM64)

Clang 20

Ubuntu 26.04 (x86-64)

GCC 15, Clang 20, Clang 22

Ubuntu 26.04 (ARM64)

Clang 22

macOS 15 (ARM64)

Apple Clang

Windows Server 2025 (x86-64)

MSVC v143, ClangCL

Quick Start

To help you getting started with fiction, pick the interface that best fits your use case:

Goal

Recommended Path

Section

Try the tool immediately

🐍 pip install

CLI (pip)

Run the CLI without installing Python

🐳 Docker CLI image

CLI (Docker)

Develop the C++ library

💻 Native build

Building from source

Integrate into a C++ project

📚 Header-only library

C++ Library

Script / notebooks / rapid prototyping

🐍 Python bindings (PyPI)

Python Bindings

For a full CLI command list or API reference, see the respective documentation sections.

CLI (pip)

The fiction command-line interface is part of the mnt.pyfiction Python package:

$ pip install mnt.pyfiction
$ fiction

Type help at the prompt for the list of commands, or run a flow without entering the shell:

$ fiction -c "read c17.v; ortho; cell; write c17.qca"

See Command Line Interface (CLI) for the full user guide.

CLI (Docker)

This is the fastest zero-install path. We release pre-built images of the latest CLI on Docker Hub. Make sure you have Docker installed on your local system.

Pull the latest image:

$ docker pull mawalter/fiction:latest

Run the interactive CLI session:

$ docker run --rm -it mawalter/fiction

Internally, the repository lives at /app/fiction.

Building from source

When you want to add your own algorithms or contribute to the project, you should build fiction from source.

Compilation requirements

Clone the repository:

$ git clone https://github.com/cda-tum/fiction.git
$ cd fiction

CMake fetches the third-party libraries during configuration. Only CMake and a C++20 compiler are required for the C++ part. If you want to work with the Python bindings, you need a Python 3.10+ installation.

On Ubuntu, the build dependencies can be installed via:

sudo apt-get install build-essential cmake python3 python3-dev

Building the tests

Configure and build with CMake:

$ cmake -S . -B build
$ cmake --build build --parallel
$ ctest --test-dir build

Several options can be toggled during the build. For a more interactive interface, please refer to ccmake for a full list of supported customizations.

CMake Presets

The repository ships a CMakePresets.json with a curated set of configurations for common tasks, so that you do not have to remember all relevant -D flags yourself. List them with:

$ cmake --list-presets

Noteworthy presets include dev (a quick Debug build with only the tests enabled), dev-full (the same, but with Z3 and ALGLIB also enabled), dev-asan (dev with sanitizers), tests-slim/tests-full (test-only builds, without/with all optional components, for the fastest edit-compile-test loop), pyfiction (mirrors the pyproject.toml configuration for iterating on the Python bindings directly with CMake), and release (an optimized, IPO-enabled build). The ci-* and coverage presets provide the shared baseline configuration used by the corresponding GitHub Actions workflows; each job layers a few compiler- and platform-specific -D overrides on top, so reproducing a specific failing job locally may require adding those too, e.g.:

$ cmake -S . --preset ci-debug
$ cmake --build --preset ci-debug
$ ctest --preset ci-debug

Any preset can still be combined with additional -D overrides on the command line. The fiction shell is not part of the CMake build; it comes with the Python package, see CLI (pip).

C++ Library

If you want to use fiction as a dependency in your project to utilize its header-only library for your own tool.

Add fiction as a sub-directory to your CMake project and link against libfiction (assuming your project is called fanfiction):

add_subdirectory(fiction)
target_link_libraries(fanfiction PRIVATE libfiction)

Note

The command target_link_libraries must be called after the respective add_executable statement that defines fanfiction.

An installed fiction package provides fiction::libfiction:

find_package(fiction CONFIG REQUIRED)
target_link_libraries(fanfiction PRIVATE fiction::libfiction)

fiction installs mockturtle into the same prefix and its package configuration finds it there, so no extra CMAKE_PREFIX_PATH entry is needed for it.

Then include what you need:

#include <fiction/layouts/clocking_scheme.hpp>
#include <fiction/technology/qca/layout.hpp>
#include <fiction/technology/qca/qca_one_library.hpp>
#include <fiction/technology/qca/io/write_qca_layout.hpp>
#include <fiction/...>

Symbols live in namespaces that mirror the directory tree, so a header under fiction/technology/sidb/simulation/engines/ declares into fiction::sidb::simulation::engines. fiction/technology/ itself adds no namespace level: its subdirectories map straight to fiction::fcn, fiction::qca, fiction::inml, and fiction::sidb.

Python Bindings

Ideal for notebooks, exploratory scripts, and integration with Python tooling.

Install the library from PyPI:

$ pip install mnt.pyfiction

Import what you need from the submodule that mirrors its C++ namespace:

from mnt.pyfiction.layouts import cartesian_layout

The Python synopsis is modeled after the C++ API to make it feel as familiar as possible. However, all available Python bindings are additionally documented together with the C++ code on this site to make it easier to get started. For each module, you can toggle between the two languages using the tabs.

Note

The fiction framework is primarily developed for C++ as a header-only library. The Python bindings are a thin wrapper around the C++ code. We try our best to keep the bindings in sync with the C++ code, and to expose most of fiction’s functionality in both C++ and Python. This is, unfortunately, not always possible. Should you encounter features that are not (yet) available in pyfiction, please open an issue on GitHub.

Algorithm Options

Python algorithms accept options as keyword-only arguments. Replace parameter-object construction and member assignments with keywords on the algorithm call; omitted options use the C++ defaults. Nested options use their leaf names. Circuit design distinguishes timeout from design_gate_timeout; time_to_solution distinguishes timeout and on_progress from quicksim_timeout and quicksim_on_progress.

from mnt.pyfiction.sidb.simulation.engines import quickexact
from mnt.pyfiction.synthesis import and_or_not, technology_mapping

result = quickexact(layout, base=2, mu_minus=-0.32, epsilon_r=5.6, lambda_tf=5.0)
mapped = technology_mapping(network, **and_or_not())

Gate-set factories return dictionaries typed as TechnologyMappingOptions, whose keys match technology_mapping.

SiDB computational inputs use the scalar keywords base, mu_minus, epsilon_r, and lambda_tf. base must be 2 or 3, mu_minus must be finite, and epsilon_r must be positive and finite. lambda_tf must be positive; positive infinity selects the unscreened model. sidb_simulation_parameters describes physical model data returned by result.simulation_parameters and potential_landscape.params(). Its keyword-only constructor uses base_number, mu_minus, relative_permittivity, and screening_distance; algorithms do not accept this object as an option.

Virtual Environment Setup

In order to set up a virtual environment on UNIX-like systems, you can use the following commands:

$ python3 -m venv venv
$ source venv/bin/activate

If you are using Windows, you can use the following commands instead:

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

Bindings Architecture

If you want to add or extend Python bindings, the C++ side lives under bindings/, the Python package under python/mnt/, and the Python tests under test/python/. The bindings use one translation unit per binding, which keeps compile time and memory usage manageable as the number of bindings grows:

bindings/
├── CMakeLists.txt                                 # one extension module per top-level namespace
├── include/pyfiction/                             # shared type aliases, docstrings, helpers
├── physical_design/
│   ├── register_physical_design.cpp               # NB_MODULE(physical_design, m): register_exact(m), ...
│   └── path_finding/
│       ├── a_star.cpp                             # defines register_a_star(nb::module_&)
│       └── register_path_finding.cpp              # calls register_a_star(m), register_distance(m), ...
├── sidb/
│   ├── register_sidb.cpp                          # NB_MODULE(sidb, m): register_lattice(m), ...
│   └── simulation/engines/
│       ├── quickexact.cpp                         # defines register_quickexact(nb::module_&)
│       └── register_sidb_simulation_engines.cpp   # calls register_quickexact(m), register_quicksim(m), ...
└── ...
python/mnt/pyfiction/
├── __init__.py                                    # loads the submodules lazily
├── physical_design/__init__.pyi, path_finding.pyi # generated stubs
└── ...

The Python module tree mirrors the C++ namespaces: fiction::sidb::simulation::engines::quickexact is mnt.pyfiction.sidb.simulation.engines.quickexact. Each top-level namespace (layouts, networks, synthesis, physical_design, verification, utils, qca, mol_qca, inml, sidb, fcn) is its own extension module. Each nested namespace is a submodule of it. For example, import the coordinate type with from mnt.pyfiction.layouts import coordinate. The directories under bindings/ follow the same tree, so a binding sits in the directory of the namespace it wraps: a_star.cpp is under physical_design/path_finding/.

Binding files declare namespace nb = nanobind; after their includes. Each leaf .cpp file defines public binding helpers with a register_ prefix (e.g. void register_a_star(nb::module_& m)) that binds a class, function, or related group. Internal template helpers use the same prefix. Each directory that holds binding sources has exactly one register_<path>.cpp, named after the directory, that forward-declares and calls the binding helpers beside it. Module registries use the same prefix. In a top-level directory, that file holds the NB_MODULE block. The block imports the modules whose types it names in signatures or default arguments, calls the binding functions of its directory, and creates each nested submodule with pyfiction::def_submodule before calling the submodule’s registry.

New source files do not need to be added anywhere manually: bindings/CMakeLists.txt collects each module’s sources with file(GLOB_RECURSE ...). Wire the new function into the directory’s register_<path>.cpp, then run nox -s stubs to regenerate the .pyi files under python/mnt/pyfiction/ and commit them.

Note

The bindings are built with nanobind, which (unlike the previous pybind11-based setup) is resolved as an installed Python package rather than fetched by CMake. When configuring the pyfiction preset directly (e.g. for IDE-based iteration, outside of pip install), make sure the Python interpreter CMake picks up has nanobind installed — the project’s uv-managed virtual environment already does, so pass -DPython_EXECUTABLE=<path_to_repo>/.venv/bin/python3 (or the equivalent .venv\Scripts\python.exe on Windows) if CMake would otherwise pick up a different interpreter.

Note

nanobind is used in split mode: the extension contains no nanobind library code and resolves it at import time from the separate nanobind-backend package, which is therefore a runtime dependency of mnt.pyfiction. That is what lets a single abi3 wheel per platform serve every supported interpreter. Free-threaded interpreters are not supported until Python 3.15 gives them a stable ABI (PEP 803); building on an earlier one stops the CMake configure with a message naming that version.

—

Advanced Configuration

GIL handling

pyfiction releases Python’s global interpreter lock (GIL) during substantial native computation, collection traversal, and file I/O. Other Python threads can run while these calls execute. Small accessors and mutations retain the GIL because a release would add overhead. Python object construction, imports, and printing also require the GIL; wrappers release it only around their native work.

Python callbacks passed through nanobind’s std::function conversion acquire the GIL for each invocation. Native calls that wait for callback workers release the GIL so those workers can enter Python.

GIL release does not synchronize native objects. Do not access an object from another thread or callback while a released call uses it. Even read-only operations can update native traversal state. This restriction includes copies that share native storage and statistics objects supplied to a running algorithm. Use a Python lock around all such access or pass independent objects. A callback may read its supplied arguments; it must not access other native objects still in use by the algorithm.

Retaining the GIL in a small accessor or mutation does not protect that object from calls that release the GIL.

On GIL-enabled Python builds, BDL iterator cursor updates retain the GIL so concurrent next() calls consume distinct patterns. Do not peek with it + n, it - n, or it[n] while another thread advances it.

Binding contributors should use nanobind::call_guard<nanobind::gil_scoped_release>() for entirely native call bodies. Mixed wrappers need an inner release scope after Python validation and before Python result construction. Keep Python-owned objects and callback wrappers alive until the GIL is reacquired. Explain retained-GIL decisions once per binding group and locally at Python-sensitive boundaries.

Enabling dependent functions

Some functionalities require the presence of third-party dependencies. In the following, it is discussed how to enable them.

SMT-based exact P&R

The exact placement and routing algorithm utilizes the SMT solver Z3. Follow the installation instructions and call sudo make install to install headers, scripts, and the binary.

Note

Be sure to compile Z3 in Release mode to avoid performance issues when running fiction’s dependent functions! This can be achieved by passing -DCMAKE_BUILD_TYPE=Release to Z3’s cmake call.

Finally, before building fiction, pass -DFICTION_Z3=ON to the cmake call. It should be able to find Z3’s include path and link against the binary automatically if installed correctly. Otherwise, you can use -DZ3_ROOT=<path_to_z3_root> to set Z3’s root directory that is to be searched for the installed solver.

ALGLIB-dependent ClusterComplete exact SiDB simulation

The ClusterComplete exact SiDB simulation algorithm relies on functionality offered by ALGLIB by the ALGLIB Project. When enabled, it will be downloaded automatically and linked against fiction.

To enable it, before building fiction, pass -DFICTION_ALGLIB=ON to the cmake call.

Building experiments

The experiments folder provides a playground for quickly scripting some ideas by plugging algorithms together. A fictionlib_demo.cpp demonstrates the usage. Any *.cpp file that is placed in on of its sub-folders is automatically linked against libfiction and compiled as a stand-alone binary. Simply add a main function and include the desired header files to get started:

#include <fiction/layouts/clocking_scheme.hpp>
#include <fiction/technology/qca/layout.hpp>
#include <fiction/technology/qca/qca_one_library.hpp>
#include <fiction/technology/qca/io/write_qca_layout.hpp>
#include <fiction/...>

int main(int argc, char* argv[])
{
  // your code goes here
}

Each file can be built individually via CMake:

$ cmake -S . -B build -DFICTION_EXPERIMENTS=ON
$ cmake --build build --parallel

Building tests

Unit tests can be built with CMake via a respective flag on the command line and executed via ctest:

$ cmake -S . -B build -DFICTION_TEST=ON
$ cmake --build build --parallel
$ ctest

Building code benchmarks

Using Catch2’s micro-benchmarking feature, you can compile and run code tests that evaluate the performance of certain code constructs. The test/benchmark folder provides a selection of benchmarks we were running to evaluate the performance of our code during development. Any *.cpp file that is placed in that folder is automatically linked against fiction and compiled as a stand-alone binary using the following commands:

$ cmake -S . -B build -DFICTION_BENCHMARK=ON
$ cmake --build build --parallel

Noteworthy CMake options

The following CMake options are available which have a potential positive impact on the build process, debugging attempts, or performance of the resulting binaries:

  • -DFICTION_ENABLE_IPO=ON: Enable IPO/LTO to improve performance of resulting binaries on some systems.

  • -DFICTION_ENABLE_PCH=ON: Enable precompiled headers (PCH) for the test suite to speed up compilation. The dev and tests-slim presets turn this on. On Windows, add sloppiness = pch_defines,time_macros to your ccache configuration, or ccache will stop caching the compilations that use the PCH.

  • -DFICTION_LIGHTWEIGHT_DEBUG_BUILDS=ON: Cut debug information down to -g1 and disable inlining. This is by far the largest single lever on Debug build cost; the CI Debug preset and the dev/tests-slim presets enable it.

  • -DFICTION_ENABLE_SANITIZER_ADDRESS=ON: Enable the address sanitizer to detect memory issues.

  • -DFICTION_ENABLE_SANITIZER_LEAK=ON: Enable the leak sanitizer to detect memory leaks.

  • -DFICTION_ENABLE_SANITIZER_UNDEFINED=ON: Enable the undefined behavior sanitizer to detect undefined behavior.

  • -DFICTION_ENABLE_SANITIZER_THREAD=ON: Enable the thread sanitizer to detect multithreading-related problems.

  • -DFICTION_ENABLE_SANITIZER_MEMORY=ON: Enable the memory sanitizer to detect uninitialized reads.

  • -DFICTION_ENABLE_JEMALLOC=ON: Enable the usage of jemalloc by Jason Evans to speed up malloc in parallelized processes.

  • -DFICTION_ENABLE_TIME_TRACE=ON: Emit Clang -ftime-trace compilation profiles to find out where build time goes.

Profiling compilation time

fiction is header-only and template-heavy, so a translation unit’s build time is dominated by the headers it pulls in and the templates it instantiates. -DFICTION_ENABLE_TIME_TRACE=ON makes Clang write a .json profile next to every object file. ClangBuildAnalyzer aggregates those into a ranking of the most expensive headers, template instantiations, and functions:

$ cmake -S . --preset tests-slim -DCMAKE_CXX_COMPILER=clang++ -DFICTION_ENABLE_TIME_TRACE=ON
$ cmake --build --preset tests-slim
$ ClangBuildAnalyzer --all build-tests-slim trace.bin
$ ClangBuildAnalyzer --analyze trace.bin

The option is Clang-only; it warns and does nothing on GCC and MSVC. Combine it with -DFICTION_ENABLE_CACHE=OFF, as a ccache hit produces no profile.

Usage of jemalloc

While enabling jemalloc through the above CMake is not beneficial to every application (and may add runtime due to overhead), it can bring significant runtime improvements to some applications. In particular, it is recommended to use jemalloc for parallelized applications in which allocations are predominantly non-ephemeral.

Note

Windows users need to install jemalloc manually. It can be done by following these steps.

Uninstall

Since all tools were built locally, simply delete the git folder cloned initially to uninstall this project.