Figure 1: Our logo.
ScaLERQEC is a scalable framework for estimating logical error rates (LER) of quantum error-correcting (QEC) circuits at scale. It combines optimized C++ backends (QEPG) with high-level Python interfaces for QEC experimentation, benchmarking, symbolic analysis, and Monte-Carlo fault injection. ScaLER is compatible with STIM, but use completely different approach to test logical error rate.
We use Sphinx to automatically generate the documents:
py -m sphinx.cmd.build -b html docs/source docsYou may visit the current documentation through the following link:
📖 Documentation website:
https://yezhuoyang.github.io/ScaLERQEC/
🔧 Option 1 — Install via pip (recommended)
pip install scalerqecThis installs:
-
The Python package
scalerqec, -
The compiled C++ backend
scalerqec.qepg, and -
All Python modules for LER calculation, sampling, symbolic analysis, etc.
You can then immediately import all modules in Python:
import scalerqec
import scalerqec.qepg🔧 Option 2 — Install from source
- Clone the repository:
git clone https://github.com/yezhuoyang/ScaLERQEC.git
cd ScaLERQEC- Build and install:
pip install .This compiles the C++ backend using pybind11 and places the compiled extension under scalerqec/qepg.*.so or .pyd
After installation, the main package structure is:
scalerqec/
├── qepg # Compiled QEPG graph and samping method from a C++ backend (by pybind11)
├── Clifford/ # Clifford circuit
├── Monte/ # Monte Carlo sampling method
├── QEC/ # High-level description of quantum error correction circuit
├── Stratified/ # Stratified fault injection
├── Symbolic/ # Symbolic method
...A detailed tutorial on using ScaLER to estimate the logical error rate of the surface code is in the notebook Tutorial.ipynb. Below is a smaller example of how we construct a code using the stabilizer formalism on the [[ 3, 1, 3 ]]
from scalerqec.QEC.qeccircuit import StabCode
from scalerqec.QEC.noisemodel import NoiseModel
qeccirc= StabCode(n=3,k=1,d=3)
# Stabilizer generators
qeccirc.add_stab("ZZI")
qeccirc.add_stab("IZZ")
# Set the first (and only) logical Z
qeccirc.set_logical_Z(0, "ZZZ")
# Set the noise model with physical error rate
noise_model = NoiseModel(0.001)
# Set stabilizer parity measurement scheme
# We support the Standard, Shor, Knill, and Flag schemes
qeccirc.scheme="Standard"
# Rounds of stabilizer measurements
qeccirc.rounds=2
# Construct IR of the code's circuit
qeccirc.construct_circuit()The last line will convert our stabilizer code to an intermediate representation (IR) that is much easier to debug. One can see the IR by calling the following:
qeccirc.show_IR()Which outputs (for the above circuit):
c0 = Prop[r=0, s=0] ZZI
c1 = Prop[r=0, s=1] IZZ
c2 = Prop[r=1, s=0] ZZI
d0 = Parity c0 c2
c3 = Prop[r=1, s=1] IZZ
d1 = Parity c1 c3
c4 = Prop ZZZ
o0 = Parity c4cX = Prop[r, s] str: TheXth stabilizer check during syndrome extraction roundrcorresponding to stabilizers, with string representationstr.dX = Parity cY cZ: TheXth detector is the parity of checksYandZoX = Parity cY: TheXth detector is the exact same result as checkY.
We introduce LogiQ -- which supports users to define their own logical QEC block and implement logical Clifford+T operations.
# 1) Define a family of surface codes (sugar → CSSCode core)
code surface(d: Int) as CellComplex over Z2 {
cells {
faces F[x,y] in 0..(d-2), 0..(d-2);
edges_x Ex[x,y] in 0..(d-2), 0..(d-1);
edges_y Ey[x,y] in 0..(d-1), 0..(d-2);
vertices V[x,y] in 0..(d-1), 0..(d-1);
}
boundary {
d2(F[x,y]) =
Ex[x,y] +
Ey[x+1,y] +
Ex[x,y+1] +
Ey[x,y];
d1(Ex[x,y]) = V[x,y] + V[x+1,y];
d1(Ey[x,y]) = V[x,y] + V[x,y+1];
}
css {
hx = matrix(d2);
hz = transpose(matrix(d1));
}
}
code five_qubit as StabilizerCode {
# Number of physical qubits (optional if implied by generator length)
n = 5;
generators {
S0 = "XZZXI";
S1 = "IXZZX";
S2 = "XIXZZ";
S3 = "ZXIXZ";
}
logical_z {
LZ0 = "ZZZZZ";
}
}
surface q1 [n=40, k=1, d=5] # First surface-code block (distance-5)
surface q2 [n=40, k=1, d=5] # Second surface-code block (distance-5)
surface t0 [n=84, k=1, d=7] # Magic-T ancilla block (distance-7)
q1[0] = LogicH q1[0]
t0 = Distill15to1_T[d=25] # returns a magic_T handle (see MagicQ below)
InjectT q1[0], t0
q2[1] = LogicCNOT q1[0], q2[1]
c1 = LogicMeasure q1[0]
c2 = LogicMeasure q2[1]We introduce MagicQ -- which allows the user to construct a magic state factory. MagicQ also has the full power to express all code-switching protocols.
protocol Distill15to1_T(surface f, int d):
Repeat:
# ---- X-type stabilizer checks ----
c_x1 = LogicProp IIIIIIIXXXXXXXX
c_x2 = LogicProp IIIXXXXIIIIXXXX
c_x3 = LogicProp IXXIIXXIIXXIIXX
c_x4 = LogicProp XIXIXIXIXIXIXIX
# ---- Z-type stabilizer checks ----
c_z1 = LogicProp IIIIIIIIZZZZZZZZ
c_z2 = LogicProp IIIZZZZIIIIZZZZ
c_z3 = LogicProp IZZIIZZIIZZIIZZ
c_z4 = LogicProp ZIZIZIZIZIZIZIZ
c_z12 = LogicProp IIIIIIIIIIZZZZ
c_z13 = LogicProp IIIIIIIIZZIIIZZ
c_z14 = LogicProp IIIIIIIIZIZIZIZ
c_z23 = LogicProp IIIIIZZIIIIIIZZ
c_z24 = LogicProp IIIIZIZIIIIIZIZ
c_z34 = LogicProp IIZIIIZIIIZIIIZ
Success = c_x1 == 0 && c_x2 == 0 && c_x3 == 0 && c_x4 == 0 &&
c_z1 == 0 && c_z2 == 0 && c_z3 == 0 && c_z4 == 0 &&
c_z12 == 0 && c_z13 == 0 && c_z14 == 0 &&
c_z23 == 0 && c_z24 == 0 && c_z34 == 0
Until Success
returnMain method
ScaLERQEC estimates the LER by stratified fault-sampling and curve fitting:
Figure 2: Diagram for the main method in ScaLERQEC.
We propose a novel method which tests the logical error rate by stratified sampling and curve fitting. See the tutorial for a detailed explanation. With a fixed QEC circuit and the noise model, we provide a simple interface for this method.
from scalerqec.Stratified import StratifiedScurveLERcalc
calculator = StratifiedScurveLERcalc()
figname = "Repetition"
titlename = "Repetition"
stratifiedcalculator.calculate_LER_from_StabCode(qeccirc, noise_model, figname , titlename, repeat=3)which outputs:
![]() |
![]() |
|---|---|
| Figure 1: Subspace error rate in the log space | Figure 2: Same, but plot in original space |
Using the C++ QEPG Backend from Python
The QEPG is a model of how errors in certain locations can propagate to flip STIM detector outcomes.
Figure 2: Illustration of how we compile a QEPG graph in ScaLERQEC.
To do the above curve-fitting, ScaLERQEC compiles any STIM circuit to QEPG graph.
import scalerqec.qepg as qepg
graph = qepg.compile_QEPG(open("circuit.stim").read())
samples = qepg.return_samples_with_fixed_QEPG(graph, weight=3, shots=10_000)
print(samples)Running Monte-Carlo Fault-Injection
We support the standard Monte-Carlo testing through the following interface:
from scalerqec.Monte.monteLER import MonteLERcalc
montecalculator = MonteLERcalc()
symbcalculator.calculate_LER_from_StabCode(qeccirc, noise_model)Running Symbolic LER Analysis (Ground Truth)
ScaLERQEC has an additional novel method which calculates the exact symbolic polynomial representation of the logical error rate of a given QEC circuit under a uniform noise model.
from scalerqec.Symbolic.symbolicLER import SymbolicLERcalc
symbcalculator = SymbolicLERcalc()
symbcalculator.calculate_LER_from_StabCode(qeccirc, noise_model)- Support installation via
pip install - Higher-level, easier interface to generate QEC program
- Add cross-platform installation support (including macOS)
- Python interface to construct QEC circuit
- Write full documentation
- Support LDPC codes and LDPC code decoders
- Get rid of Boost package, use binary representation
- Add CUDA backend support and compare with STIM
- SIMD support and compare with STIM
- Constructing and testing magic state distillation/Cultivation
- Compatible with Qiskit
- Visualize results better and visualize QEPG graph
- HotSpot analysis (What is the reason for logical error?)
- Implement dynamic-circuit support (Compatible with IBM)
- Support testing code switching such as lattice surgery, LDPC code switching protocol
- Add more realistic noise models (Decoherence noise, Correlated noise)
- Support injecting quantum errors by type (Hook Error, Gate error, Propagated error, etc)
- Static analysis pass of circuit (Learn symmetric structure)
- Test Pauli measurement based fault-tolerant circuit
At the moment, ScaLERQEC is installed from source.
Common to all platforms:
- Python ≥ 3.9 (3.11+ recommended)
- A C++20-compatible compiler
pipand a virtual environment (venv,conda, etc.)
Additional dependencies:
- Boost (for
boost::dynamic_bitset) - pybind11 (handled automatically as a build dependency, but the C++ compiler must be able to see its headers)
- Install [Visual Studio Build Tools] or full Visual Studio with C++ toolchain.
- Install Boost (MSVC flavor), e.g. via Chocolatey:
choco install boost-msvc-14.3 -yThe boost header file will be stored under the path "C:\local\boost_1_87_0\boost". Add this path into VScode cpp include path in your development process.
We use pybind11 to convert the samples from C++ objects to python objects. To install using vcpkg, run the following command:
vcpkg install pybind11Install Xcode command-line tools:
xcode-select --installInstall Homebrew (if you don’t have it):
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"Install pybind11 via Homebrew:
brew install pybind11 boostThis provides headers in:
/opt/homebrew/include (ARM) /usr/local/include (Intel)
Roughly:
sudo apt install libboost-dev
pip install pybind11Boost headers go into /usr/include/boost.
Also need to add the path of the python header file
py -c "from sysconfig import get_paths as gp; print(gp()['include'])"Run the following command to build the QEPG package with pybinding:
py setup.py build_ext --inplaceRun the following command to clear the previously compiled output:
py setup.py clean --all We also need to convert C++ object to python object directly. So "Python.h" needs to be added to the search path. Typically, it is under:
C:\Users\username\miniconda3\includeTo compile QEPG python package by pybind11:
(Under QEPG folder)./compilepybind.ps1The python code is divided into different modules. For example, to run the test_by_stim.py file under test module, stay at the root folder and execute:
(Under Sampling folder)py -m test.test_by_stim 



