Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
The Microsoft Quantum resource estimator takes a quantum error correction (QEC) code transform and magic state factory transform model as one of its core inputs. The QEC and factory transforms determine how the physical instruction set (ISA) from the hardware architecture model converts to a logical instruction set for error correction.
From the QEC code model and magic state factory model, you build an ISA query that you pass to the resource estimator. The ISA query defines the combinations of QEC codes and magic state factories that the resource estimator explores. Each combination of QEC code and magic state factory determines how the resource estimator converts the physical ISA into a logical ISA. In this article, you learn how to build ISA queries from the default QEC code models and default magic state factory models, and how to build your own custom models.
Warning
The resource estimator in the QDK extension for VS Code will be deprecated soon. Use the qdk.qre Python module to perform resource estimation.
Build ISA queries from the default QEC code and magic state factory models
The quantum resource estimator includes four default QEC code models and three default magic state factory models.
Default QEC code models
The quantum resource estimator includes the following default QEC models:
| QEC model | Description |
|---|---|
| SurfaceCode | Gate-based rotated surface code |
| SurfaceCodeLowMove | Gate-based rotated surface code for neutral atom hardware that has mobile ancilla qubits |
| OneDimensionalYokedSurfaceCode | 1D Yoked surface code that provides generic memory instructions |
| TwoDimensionalYokedSurfaceCode | 2D Yoked surface code that provides generic memory instructions |
| ThreeAux | Pairwise measurement-based surface code with three auxiliary qubits per stabilizer measurement |
Default magic state factory models
The quantum resource estimator includes the following default magic state factory models:
| Magic state factory model | Description |
|---|---|
| RoundBasedFactory | Magic state factory that uses round-based distillation pipelines to produce T gate instructions |
| Litinski19Factory | T and CCZ factories based on the paper arXiv:1905.06903 |
| GSJ24CCXFactory | 8T-to-CCZ factories based on the paper arXiv:2409.17595 |
| GSJ24Factory | Magic state cultivation based on the paper arXiv:2409.17595 |
| MagicUpToClifford | ISA transform that adds Clifford equivalent representations of magic states |
Build an ISA query from default models
To build an ISA query, combine a QEC model with a magic state factory model using either the + or * operator. For example, build an ISA query from the gate-based surface code QEC model and the round-based magic state factory model.
from qdk.qre.models import SurfaceCode, RoundBasedFactory
isa_query = SurfaceCode.q() * RoundBasedFactory.q()
Build ISA queries from custom QEC code and magic state factory models
The quantum resource estimator lets you build custom QEC and magic state factory models. These custom models use the ISATransform subclass to convert physical qubit instructions into logical qubit instructions.
To build these custom models, use the ISATransform subclass and define two methods:
required_isa: A static method that defines what operations to use from the architecture model's ISA. This method can't reference fields, such aserror_correction_threshold. Instead, useConstraintBoundwith comparison operators to set limits or ranges on values.provided_isa: Computes logical instruction properties from physical instructions to produce one or more logical ISAs. This method references the custom fields in your model.
Build a custom QEC model
A QEC transform encodes qubits into an error correction code based on these main parameters:
| QEC code parameter | Description | Depends on |
|---|---|---|
| Space | The number of physical qubits per logical qubits | Code distance |
| Time | The time it takes to complete one logical operation | Syndrome extraction cycles |
| Error | The probability that an error occurs from a logical operation | Physical error rate, maximum error rate, and code distance |
Custom QEC transforms need to define values for these parameters. For example, the following code builds a custom QEC model called GenericQEC that converts physical H gate, CNOT gate, and measurement instructions into a logical lattice surgery instruction set.
from dataclasses import KW_ONLY, dataclass, field
from typing import Generator
from qdk.qre import (
ISAContext,
ConstraintBound,
ISA,
ISARequirements,
ISATransform,
LOGICAL,
constraint,
linear_function
)
from qdk.qre.instruction_ids import H, CNOT, MEAS_Z, LATTICE_SURGERY
@dataclass
class GenericQEC(ISATransform):
"""
A configurable QEC code model with tunable threshold, overhead,
and error suppression.
This transform consumes physical H, CNOT, and MEAS_Z instructions and
produces a logical LATTICE_SURGERY instruction. The resource formulas
are parameterized so you can model different QEC code families.
The logical error rate follows:
error = crossing_prefactor × (p_physical / threshold) ^ ⌊(d+1)/2⌋
Space per data qubit is:
qubits_per_data_qubit × d²
"""
crossing_prefactor: float = 0.03
error_correction_threshold: float = 0.01
qubits_per_data_qubit: int = 2
syndrome_extraction_depth: int = 4
_: KW_ONLY
distance: int = field(default=3, metadata={"domain": range(3, 22, 2)})
@staticmethod
def required_isa() -> ISARequirements:
# required_isa is static, so we cannot reference instance fields here.
# Instead, we use ConstraintBound to express fixed constraints on the
# implementation ISA. ConstraintBound supports .lt(), .le(), .eq(),
# .gt(), and .ge() comparisons.
return ISARequirements(
constraint(H, error_rate=ConstraintBound.lt(0.01)),
constraint(CNOT, arity=2, error_rate=ConstraintBound.lt(0.01)),
constraint(MEAS_Z, error_rate=ConstraintBound.lt(0.01)),
)
def provided_isa(
self, impl_isa: ISA, ctx: ISAContext
) -> Generator[ISA, None, None]:
cnot = impl_isa[CNOT]
h = impl_isa[H]
meas_z = impl_isa[MEAS_Z]
# Physical error rate is the worst case across all required gates
physical_error_rate = max(
cnot.expect_error_rate(),
h.expect_error_rate(),
meas_z.expect_error_rate(),
)
d = self.distance
# Space: physical qubits per data qubit, scaled by distance².
#
# Because LATTICE_SURGERY has variable arity (it can operate on any
# number of logical qubits), space and error_rate must be provided as
# functions of arity rather than as fixed numbers. The estimator
# provides several helpers for this:
#
# linear_function(slope) → f(n) = slope × n
# constant_function(value) → f(n) = value
# block_linear_function(k, s, o) → f(n) = s × ⌈n/k⌉ + o
# generic_function(callable) → f(n) = callable(n)
#
# For QEC codes, resources scale linearly with the number of logical
# qubits, so linear_function is the natural choice.
space = linear_function(self.qubits_per_data_qubit * d**2)
# Time: syndrome extraction cycle × code distance
code_cycle_time = (
h.expect_time()
+ self.syndrome_extraction_depth * cnot.expect_time()
+ meas_z.expect_time()
)
time = code_cycle_time * d
# Error: exponential suppression below threshold
logical_error = self.crossing_prefactor * (
(physical_error_rate / self.error_correction_threshold)
** ((d + 1) // 2)
)
error = linear_function(logical_error)
yield ctx.make_isa(
ctx.add_instruction(
LATTICE_SURGERY,
encoding=LOGICAL,
arity=None,
space=space,
time=time,
error_rate=error,
# transform=self records that this instruction was produced by
# this QEC transform instance, and source=[...] records which
# physical instructions it was derived from. Together, they
# form the provenance chain that you can inspect in the
# estimation results (see notebook 2).
transform=self,
source=[cnot, h, meas_z],
# Extra keyword arguments (like distance) are stored as
# properties on the instruction. This lets you retrieve the
# code distance used for each Pareto-optimal result when
# analyzing the estimation output. Further, the property can be
# required and read by a parent ISATransform to create new
# logical instructions based on this one.
distance=d,
),
)
You can call GenericQEC with the default parameter values, or you can set other values when you call the model. For example, create a QEC model that has a high error threshold and low qubit overhead.
custom_qec = GenericQEC.q(
crossing_prefactor=0.01,
error_correction_threshold=0.03,
qubits_per_data_qubit=1,
)
Build a custom magic state factory model
A magic state factory model transform produces high-fidelity magic states from low-level physical instructions.
You can build a custom magic state factory model, or use a custom model to explore how established models behave with changes to specific parameters. For example, build a custom factory model called ScaledLitinskiFactory that's based on the default Litinski19Factory model. The custom model lets you modify the space factor for logical qubits and the number of cycles that the factory requires.
from dataclasses import dataclass
from typing import Generator
from math import ceil
from qdk.qre import (
ISAContext,
ConstraintBound,
ISA,
ISARequirements,
ISATransform,
LOGICAL,
constraint
)
from qdk.qre.instruction_ids import T, H, CNOT, MEAS_Z
# Reference data from Litinski (arXiv:1905.06903), Table 1.
# Assumes Clifford and T error rates at most 1e-4.
# Each tuple: (output_error_rate, space_in_physical_qubits, cycles)
_LITINSKI_TABLE = [
(4.4e-8, 810, 18.1),
(1.5e-9, 762, 36.2),
(9.3e-10, 1150, 18.1),
(1.9e-11, 2070, 30.0),
(2.4e-15, 16400, 90.3),
(6.3e-25, 18600, 67.8),
]
@dataclass
class ScaledLitinskiFactory(ISATransform):
"""
A factory transform based on the Litinski (2019) distillation protocols,
with configurable scaling factors for space and cycle costs.
The base data is taken directly from Table 1 in arXiv:1905.06903.
Each entry specifies a distillation protocol's output error rate, space
footprint (in physical qubits), and time (in syndrome extraction cycles).
The space already includes the QEC overhead within the factory itself.
Scaling factors let you explore hypothetical improvements:
- ``space_factor=0.5`` models a factory that uses half the qubits.
- ``cycle_factor=0.5`` models a factory that runs twice as fast.
Note:
The error rates are kept fixed when scaling. In practice, changing
space or cycles would affect error rates too, but modelling that
relationship requires protocol-specific analysis.
"""
space_factor: float = 1.0
cycle_factor: float = 1.0
@staticmethod
def required_isa() -> ISARequirements:
return ISARequirements(
constraint(T, error_rate=ConstraintBound.le(1e-4)),
constraint(H, error_rate=ConstraintBound.le(1e-4)),
constraint(CNOT, arity=2, error_rate=ConstraintBound.le(1e-4)),
constraint(MEAS_Z, error_rate=ConstraintBound.le(1e-4)),
)
def provided_isa(
self, impl_isa: ISA, ctx: ISAContext
) -> Generator[ISA, None, None]:
cnot = impl_isa[CNOT]
h = impl_isa[H]
meas_z = impl_isa[MEAS_Z]
t = impl_isa[T]
# Syndrome extraction time from the physical ISA
syndrome_extraction_time = (
4 * cnot.expect_time() + h.expect_time() + meas_z.expect_time()
)
for error_rate, base_space, base_cycles in _LITINSKI_TABLE:
# Apply scaling factors to space and time
space = ceil(base_space * self.space_factor)
time = ceil(syndrome_extraction_time * base_cycles * self.cycle_factor)
yield ctx.make_isa(
ctx.add_instruction(
T,
encoding=LOGICAL,
arity=1,
space=space,
time=time,
error_rate=error_rate,
transform=self,
source=[cnot, h, meas_z, t],
),
)
With the custom ScaledLitinskiFactory model, you can lower space_factor to explore factories that use fewer physical qubits or lower cycle_factory to explore factories that need fewer cycles for distillation. For example, create a model that uses half the physical qubits as LitinskiFactory, and another model that needs half as many cycles.
compact_factory = ScaledLitinskiFactory.q(space_factor=0.5)
fast_factory = ScaledLitinskiFactory.q(cycle_factor=0.5)
Build an ISA query from custom models
To build an ISA query from custom QEC and distillation models, set the custom parameter values and combine the models using either the + or * operator. For example, build an ISA query from the custom QEC model that you already built. Set the GenericQEC model to have a higher error threshold and set the ScaledLitinskiFactory model to require fewer cycles.
custom_qec = GenericQEC.q(error_correction_threshold=0.02)
custom_factory = ScaledLitinskiFactory.q(cycle_factor=0.75)
custom_query = custom_qec * custom_factory
Run estimates for custom QEC and distillation models
To run estimates with your custom models, pass custom_query to the estimate function as the ISA query parameter along with an application model and a hardware architecture model.
Import the necessary modules and objects.
from qdk import qsharp from qdk.qre.application import QSharpApplication from qdk.qre.models import GateBased from qdk.qre import estimate, plot_estimatesBuild an application model. For example, write an 8-bit adder program in Q# with single-qubit rotations.
%%qsharp import Std.Arithmetic.*; import Std.Math.*; import Std.Convert.*; /// An 8-bit adder with single-qubit rotations. /// The rotations introduce T gates via synthesis, which exercises /// the magic state factory during resource estimation. operation AdderWithRotations() : Unit { use a = Qubit[8]; use b = Qubit[8]; for i in 0..7 { Ry(PI() / IntAsDouble(i + 2), a[i]); } RippleCarryCGIncByLE(a, b); }Convert the Q# program into an application model.
from qdk import code app = QSharpApplication(code.AdderWithRotations)Build a hardware architecture model. For example, use the default gate-based model.
arch = GateBased(error_rate=1e-4, gate_time=100, measurement_time=500)To run an estimate, pass the application model, architecture model, and custom ISA query to the
estimatefunction. Set a 1% maximum error rate.result = estimate(app, arch, custom_query, max_error=0.01) result.plot(figsize=(8, 5))To compare estimates for different sets of QEC and magic state factory models, build a list of estimates and plot the results. For example, build a query from the default parameters of the custom models and compare that with models that have different parameters.
baseline = GenericQEC.q() * ScaledLitinskiFactory.q() high_cross = GenericQEC.q(crossing_prefactor=0.05) * ScaledLitinskiFactory.q() compact_factory = GenericQEC.q() * ScaledLitinskiFactory.q(space_factor=0.5) high_compact = GenericQEC.q(crossing_prefactor=0.05) * ScaledLitinskiFactory.q(space_factor=0.5) results = [ estimate(app, arch, baseline, max_error=0.02, name="Baseline"), estimate(app, arch, high_cross, max_error=0.02, name="High crossing prefactor"), estimate(app, arch, compact_factory, max_error=0.02, name="Compact factory"), estimate(app, arch, high_compact, max_error=0.02, name="High crossing prefactor & Compact factory"), ] plot_estimates(results, figsize=(8, 5), runtime_unit="ms")