This is documentation for programmers working with stim, e.g. how to build it. These notes generally assume you are on a Linux system.
- compatibility guarantees across versions
- releasing a new version
- building
stim
command line tool - linking to
libstim
shared library - running C++ tests
- running performance benchmarks
- creating a python dev environment
- running python unit tests
- python packaging
stim
- python packaging
stimcirq
- python packaging
sinter
- javascript packaging
stimjs
- autoformating code
- adding new c++ files
- adding new CLI commands
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.
- 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
, andsinter
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
insrc/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
- Upload wheels/sdists to pypi using
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.
# 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
.
bazel build stim
or, to build and run:
bazel run stim
# 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
!!!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.
!!!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");
}
!!!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");
}
Stim's code base includes a variety of types of tests, spanning over a few packages and languages.
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.
# from the repository root:
bazel test stim_test
cmake .
make stim_perf
./out/stim_perf
bazel run 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_*
.
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
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
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
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
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.
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/*
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 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/*
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
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__)"
# from repo root
cd glue/cirq
python setup.py sdist
cd -
# output in glue/cirq/dist/*
# from repo root
pip install -e glue/cirq
# stimcirq is now installed in current virtualenv as dev reference
# from repo root
cd glue/sample
python setup.py sdist
cd -
# output in glue/sample/dist/*
# from repo root
pip install -e glue/sample
# sinter is now installed in current virtualenv as dev reference
# from repo root
cd glue/zx
python setup.py sdist
cd -
# output in glue/zx/dist/*
# from repo root
pip install -e glue/zx
# stimzx is now installed in current virtualenv as dev reference
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
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
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
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.