Skip to content

Latest commit

 

History

History
700 lines (528 loc) · 23.1 KB

developer_documentation.md

File metadata and controls

700 lines (528 loc) · 23.1 KB

Stim Developer Documentation

This is documentation for programmers working with stim, e.g. how to build it. These notes generally assume you are on a Linux system.

Index

Compatibility guarantees across versions

A bug is bad behavior that wasn't intended. For example, the program crashing instead of returning empty results when sampling from an empty circuit would be a bug.

A trap is originally-intended behavior that leads to the user making mistakes. For example, allowing the user to take a number of shots that wasn't a multiple of 64 when using the data format ptb64 was a trap because the shots were padded up to a multiple of 64 using zeroes (and these fake shots could then easily be treated as real by later analysis steps).

A spandrel is an implementation detail that has observable effects, but which is not really required to behave in that specific way. For example, when stim derives a detector error model from a circuit, the exact probability of an error instruction depends on minor details such as floating point error (which is sensitive to compiler optimizations). The exact floating point error that occurs is a spandrel.

  • Stim Python API
    • The python API must maintain backwards compatibility from minor version to minor version (except for bug fixes, trap fixes, and spandrels). Violating this requires a new major version.
    • Trap fixes must be documented as breaking changes in the release notes.
    • The exact behavior of random seeding is a spandrel. The behavior must be consistent on a single machine for a single version, with the same seed always producing the same results, but it is not stable between minor versions. This is enforced by intentionally introducing changes for every single minor version.
  • Stim Command Line API
    • The command land API must maintain backwards compatibility from minor version to minor version (except for bug fixes, trap fixes, and spandrels). Violating this requires a new major version.
    • Trap fixes must be documented as breaking changes in the release notes.
    • It is explicitly not allowed for a command to stop working due to, for example, a cleanup effort to make the commands more consistent. Violating this requires a new major version.
    • The exact behavior of random seeding is a spandrel. The behavior must be consistent on a single machine for a single version, with the same seed always producing the same results, but it is not stable between minor versions. This is enforced by intentionally introducing changes for every single minor version.
  • Stim C++ API
    • The C++ API makes no compatibility guarantees. It may change arbitrarily and catastrophically from minor version to minor version.

Releasing a new version

  • Create an off-main-branch release commit
    • git checkout main -b SOMEBRANCHNAME
    • Global search replace __version__ = 'X.Y.dev0' with __version__ = 'X.Y.0'
    • git commit -a -m "Bump to vX.Y.0"
    • git tag vX.Y.0
    • Push tag to github
    • Check github Actions tab and confirm ci is running on the tag
    • Wait for ci to finish validating and producing artifacts for the tag
    • Get stim, stimcirq, and sinter wheels/sdists from cibuildwheels of this tag
  • Bump to next dev version on main branch
    • git checkout main -b SOMEBRANCHNAME
    • Global search replace __version__ = 'X.Y.dev0' with __version__ = 'X.(Y+1).dev0'
    • Increment INTENTIONAL_VERSION_SEED_INCOMPATIBILITY in src/stim/circuit/circuit.h
    • git commit -a -m "Start vX.(Y+1).dev"
    • Push to github as a branch and merge into main using a pull request
  • Write release notes on github
    • In title, use two-word theming of most important changes
    • Flagship changes section
    • Notable changes section
    • Include wheels/sdists as attachments
  • Do these irreversible and public viewable steps last!
    • Upload wheels/sdists to pypi using twine
    • Publish the github release notes
    • Add gates reference page to wiki for the new version
    • Add python api reference page to wiki for the new version
    • Update main wiki page to point to latest reference pages
    • Tweet about the release

Building stim command line tool

The stim command line tool is a binary program stim that accepts commands like stim sample -shots 100 -in circuit.stim (see the command line reference). It can be built with cmake, with bazel, or manually with gcc.

Building stim command line tool with cmake

# from the repository root:
cmake .
make stim

# output binary ends up at:
# ./out/stim

Stim can also be installed:

# from the repository root:
cmake .
make stim
make install

# output binary ends up at:
# -CMAKE_INSTALL_PREFIX/bin
# output library ends up at:
# -CMAKE_INSTALL_PREFIX/lib
# output headers end up at:
# -CMAKE_INSTALL_PREFIX/include

by default make install will install to the system default directory (e.g. /usr/local). The install path can be controlled by passing the flag -DCMAKE_INSTALL_PREFIX=/path/to/install to cmake.

Vectorization can be controlled by passing the flag -DSIMD_WIDTH to cmake:

  • cmake . -DSIMD_WIDTH=256 means "use 256 bit avx operations" (forces -mavx2)
  • cmake . -DSIMD_WIDTH=128 means "use 128 bit sse operations" (forces -msse2)
  • cmake . -DSIMD_WIDTH=64 means "don't use simd operations" (no machine arch flags)
  • cmake . means "use the best thing possible on this machine" (forces -march=native)

A compile_commands.json (used by clangd to provide IDE features over lsp) can be generated by passing the flag -DCMAKE_EXPORT_COMPILE_COMMANDS=1 to cmake.

Building stim command line tool with bazel

bazel build stim

or, to build and run:

bazel run stim

Building stim command line tool with gcc

# from the repository root:
find src \
    | grep "\\.cc$" \
    | grep -v "\\.\(test\|perf\|pybind\)\\.cc$" \
    | xargs g++ -I src -pthread -std=c++20 -O3 -march=native

# output binary ends up at:
# ./a.out

Linking to libstim shared library

!!!CAUTION!!! Stim's C++ API is not kept stable! Always pin to a specific version! I WILL break your downstream code when I update stim if you don't! The API is also not extensively documented; what you can find in the headers is what you get.

To use Stim functionality within your C++ program, you can build libstim and link to it to gain direct access to underlying Stim types and methods.

If you want a stim API that promises backwards compatibility, use the python API.

Linking to libstim shared library with cmake

!!!CAUTION!!! Stim's C++ API is not kept stable! Always pin to a specific version! I WILL break your downstream code when I update stim if you don't! The API is also not extensively documented; what you can find in the headers is what you get.

In your CMakeLists.txt file, use FetchContent to automatically fetch stim from github when running cmake .:

# in CMakeLists.txt file

include(FetchContent)
FetchContent_Declare(stim
        GIT_REPOSITORY https://github.com/quantumlib/stim.git
        GIT_TAG v1.4.0)  # [[[<<<<<<< customize the version you want!!]]]
FetchContent_GetProperties(stim)
if(NOT stim_POPULATED)
  FetchContent_Populate(stim)
  add_subdirectory(${stim_SOURCE_DIR})
endif()

(Replace v1.4.0 with another version tag as appropriate.)

For build targets that need to use stim functionality, add libstim to them using target_link_libraries:

# in CMakeLists.txt file

target_link_libraries(some_cmake_target PRIVATE libstim)

In your source code, use #include "stim.h" to access stim types and functions:

// in a source code file

#include "stim.h"

stim::Circuit make_bell_pair_circuit() {
    return stim::Circuit(R"CIRCUIT(
        H 0
        CNOT 0 1
        M 0 1
        DETECTOR rec[-1] rec[-2]
    )CIRCUIT");
}

Linking to libstim shared library with bazel

!!!CAUTION!!! Stim's C++ API is not kept stable! Always pin to a specific version! I WILL break your downstream code when I update stim if you don't! The API is also not extensively documented; what you can find in the headers is what you get.

In your WORKSPACE file, include stim's git repo using git_repository:

# in WORKSPACE file

load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")

git_repository(
    name = "stim",
    commit = "v1.4.0",
    remote = "https://github.com/quantumlib/stim.git",
)

(Replace v1.4.0 with another version tag or commit SHA as appropriate.)

In your BUILD file, add @stim//:stim_lib to the relevant target's deps:

# in BUILD file

cc_binary(
    ...
    deps = [
        ...
        "@stim//:stim_lib",
        ...
    ],
)

In your source code, use #include "stim.h" to access stim types and functions:

// in a source code file

#include "stim.h"

stim::Circuit make_bell_pair_circuit() {
    return stim::Circuit(R"CIRCUIT(
        H 0
        CNOT 0 1
        M 0 1
        DETECTOR rec[-1] rec[-2]
    )CIRCUIT");
}

Running unit tests

Stim's code base includes a variety of types of tests, spanning over a few packages and languages.

Running C++ unit tests with cmake

Unit testing with cmake requires the GTest library to be installed on your system in a place that cmake can find it. Follow the "Standalone CMake Project" instructions from the GTest README to get GTest installed correctly.

Run tests with address and memory sanitization, without compile time optimization:

# from the repository root:
cmake .
make stim_test
./out/stim_test

Run tests without sanitization, with compile time optimization:

# from the repository root:
cmake .
make stim_test_o3
./out/stim_test_o3

Stim supports 256 bit (AVX), 128 bit (SSE), and 64 bit (native) vectorization. The type to use is chosen at compile time. To force this choice (so that each case can be tested on one machine), add -DSIMD_WIDTH=256 or -DSIMD_WIDTH=128 or -DSIMD_WIDTH=64 to the cmake . command.

Running C++ unit tests with bazel

# from the repository root:
bazel test stim_test

Running performance benchmarks

Running performance benchmarks with cmake

cmake .
make stim_perf
./out/stim_perf

Running performance benchmarks with bazel

bazel run stim_perf

Interpreting output from stim_perf

When you run stim_perf you will see output like:

[....................*....................] 460 ns (vs 450 ns) ( 21 GBits/s) simd_bits_randomize_10K
[...................*|....................]  24 ns (vs  20 ns) (400 GBits/s) simd_bits_xor_10K
[....................|>>>>*...............] 3.6 ns (vs 4.0 ns) (270 GBits/s) simd_bits_not_zero_100K
[....................*....................] 5.8 ms (vs 6.0 ms) ( 17 GBits/s) simd_bit_table_inplace_square_transpose_diam10K
[...............*<<<<|....................] 8.1 ms (vs 5.0 ms) ( 12 GOpQubits/s) FrameSimulator_depolarize1_100Kqubits_1Ksamples_per1000
[....................*....................] 5.3 ms (vs 5.0 ms) ( 18 GOpQubits/s) FrameSimulator_depolarize2_100Kqubits_1Ksamples_per1000

The bars on the left show how fast each task is running compared to baseline expectations (on my dev machine). Each tick away from the center | is 1 decibel slower or faster (i.e. each < or > represents a factor of 1.26).

Basically, if you see [......*<<<<<<<<<<<<<|....................] then something is seriously wrong, because the code is running 25x slower than expected.

The stim_perf binary supports a --only=BENCHMARK_NAME filter flag. Multiple filters can be specified by separating them with commas --only=A,B. Ending a filter with a * turns it into a prefix filter --only=sim_*.

Profiling with gcc and perf

find src \
    | grep "\\.cc" \
    | grep -v "\\.\(test\|perf\|pybind\)\\.cc" \
    | xargs g++ -I src -pthread -std=c++20 -O3 -march=native -g -fno-omit-frame-pointer
sudo perf record -g ./a.out  # [ADD STIM FLAGS FOR THE CASE YOU WANT TO PROFILE]
sudo perf report

Creating a python dev environment

First, create a fresh python 3.6+ virtual environment using your favorite method: python -m venv, virtualenvwrapper, conda, or etc.

Second, build and install a stim wheel. Follow the python packaging stim instructions to create a wheel. I recommend packaging with bazel because it is BY FAR the fastest. Once you have the wheel, run pip install [that_wheel]. For example:

# from the repository root in a python virtual environment
bazel build :stim_dev_wheel
pip uninstall stim --yes
pip install bazel-bin/stim-0.0.dev0-py3-none-any.whl

Note that you need to repeat the above steps each time you make a change to stim.

Third, use pip install -e to install development references to the pure-python glue packages:

# install test dependencies
pip install pytest pymatching

# install stimcirq dev reference:
pip install -e glue/cirq

# install sinter dev reference:
pip install -e glue/sample

# install stimzx dev reference:
pip install -e glue/zx

Running python unit tests

See creating a python dev environment for instructions on creating a python virtual environment with your changes to stim installed.

Unit tests are run using pytest. Examples in docstrings are tested using the dev/doctest_proper script, which uses python's doctest module but ensures values added to a module at import time are also tested (instead of requiring them to be manually listed in a __test__ property).

To test everything:

# from the repository root in a virtualenv with development wheels installed:
pytest src glue
dev/doctest_proper.py --module stim
dev/doctest_proper.py --module stimcirq --import cirq sympy
dev/doctest_proper.py --module sinter
dev/doctest_proper.py --module stimzx

Test only stim:

# from the repository root in a virtualenv with development wheels installed:
pytest src
dev/doctest_proper.py --module stim

Test only stimcirq:

# from the repository root in a virtualenv with development wheels installed:
pytest glue/cirq
dev/doctest_proper.py --module stimcirq --import cirq sympy

Test only sinter:

# from the repository root in a virtualenv with development wheels installed:
pytest glue/sample
dev/doctest_proper.py --module sinter

Test only stimzx:

# from the repository root in a virtualenv with development wheels installed:
pytest glue/zx
dev/doctest_proper.py --module stimzx

Running sinter's python unit tests against a custom decoder

Some of sinter's python unit tests verify that a decoder is behaving correctly. It can be useful, when creating a custom decoder, to run these tests against the decoder. This can be done by setting the environment SINTER_PYTEST_CUSTOM_DECODERS variable to custom_package:custom_method where custom_package is a python package to import and custom_method is a method name implemented by that package that returns a Dict[str, sinter.Decoder]. This is the same form of argument that's given to sinter collect --custom_decoders.

Example:

SINTER_PYTEST_CUSTOM_DECODERS="my_custom_package:my_custom_sinter_decoder_dict_method" pytest glue/sample

python packaging stim

Because stim is a C++ extension, it is non-trivial to create working python packages for it. To make cross-platform release wheels, we rely heavily on cibuildwheels. To make development wheels, various other options are possible.

python packaging stim with cibuildwheels

When a commit is merged into the main branch of stim's GitHub repository, there are GitHub actions that use cibuildwheels to build wheels for all supported platforms.

cibuildwheels can also be invoked locally, assuming you have Docker installed, using a command like:

CIBW_BUILD=cp39-manylinux_x86_64 cibuildwheel --platform linux
# output goes into wheelhouse/

When these wheels are finished building, they are automatically uploaded to pypi as a dev version of stim. For release versions, the artifacts created by the github action must be manually downloaded and uploaded using twine:

twine upload --username="${PROD_TWINE_USERNAME}" --password="${PROD_TWINE_PASSWORD}" artifacts_directory/*

python packaging stim with bazel

Bazel can be used to create dev versions of the stim python wheel:

# from the repository root:
bazel build stim_dev_wheel
# output is at bazel-bin/stim-0.0.dev0-py3-none-any.whl

python packaging stim with python setup.py

Python can be used to create dev versions of the stim python wheel (very slow):

Binary distribution:

# from the repository root in a python venv with pybind11 installed:
python setup.py bdist
# output is at dist/*

Source distribution:

# from the repository root in a python venv with pybind11 installed:
python setup.py sdist
# output is at dist/*

python packaging stim with pip install -e

You can directly install stim as a development python wheel by using pip (very slow):

# from the repository root
pip install -e .
# stim is now installed in current virtualenv as dev reference

python packaging stim with cmake

A python module can be built using cmake. The output can be imported as import stim, but is not packaged, so if you want to pip install, prefer building the bindings with Bazel.

# from the repository root
cmake stim_python_bindings
# output is at out/stim.cpython-${PYTHON_VERSION}-${ARCH}.so
PYTHONPATH=out python -c "import stim; print(stim.__version__)"

Python packaging stimcirq

Python packaging stimcirq with python setup.py

# from repo root
cd glue/cirq
python setup.py sdist
cd -
# output in glue/cirq/dist/*

Python packaging stimcirq with pip install -e

# from repo root
pip install -e glue/cirq
# stimcirq is now installed in current virtualenv as dev reference

Python packaging sinter

Python packaging sinter with python setup.py

# from repo root
cd glue/sample
python setup.py sdist
cd -
# output in glue/sample/dist/*

Python packaging sinter with pip install -e

# from repo root
pip install -e glue/sample
# sinter is now installed in current virtualenv as dev reference

Python packaging stimzx

Python packaging stimzx with python setup.py

# from repo root
cd glue/zx
python setup.py sdist
cd -
# output in glue/zx/dist/*

Python packaging stimzx with pip install -e

# from repo root
pip install -e glue/zx
# stimzx is now installed in current virtualenv as dev reference

Javascript packaging stimjs

Javascript packaging stimjs with emscripten

Install and activate enscriptem (emcc must be in your PATH). Example:

# [outside of repo]
git clone [email protected]:emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source emsdk_env.sh

Run the bash build script:

# [from repo root]
glue/javascript/build_wasm.sh

Outputs are the binary out/stim.js and the test runner out/all_stim_tests.html. Run tests by opening in a browser and checking for an All tests passed. message in the browser console:

firefox out/all_stim_tests.html

Autoformating code

Autoformating code with clang-format

Run the following command from the repo root to auto-format all C++ code:

find src | grep "\.\(cc\|h\)$" | grep -Pv "crumble_data.cc|gate_data_3d_texture_data.cc" | xargs clang-format -i

Adding new C++ files

For the cmake build system, we maintain C++ file lists in the folder file_lists. When you add a new C++ file, they can be updated using dev/regen_file_lists.sh

Adding new CLI commands

A reference is maintained to document the CLI usage. This is generated using dev/regen_docs.sh which assumes the script is run from a virtualenv with a dev wheel of stim installed.