Quick start
This page takes you from a clean Python environment to a verified QEC resource
estimate and emitted Stim circuit text. Every command assumes cudaq-logical
is installed, and every program shown is one of the shipped, test-executed
examples under preview/logical/examples/.
You will:
install CUDA-Q Logical;
estimate an existing CUDA-Q kernel against a surface-code target;
author a portable P0 logical program and read its logical estimate;
define a QEC code and a verified gadget, and count their P2 operations;
lower a surface-code workload to physical events and schedule it;
emit standards-compatible Stim text from the command line.
Install
The simplest way to install CUDA-Q Logical is with the cudaq base package:
pip install cudaq
To install CUDA-Q Logical on its own instead, run:
pip install cudaq-logical[cu13]
Replace [cu13] with [cu12] if you have CUDA 12 installed.
Always include [cu13] or [cu12]
If you install cudaq-logical standalone, always suffix it with [cu13] or
[cu12], defaulting to [cu13] if your machine has no CUDA installation. The
bare package does not include the required dependencies.
To build from source against CUDA-Q, see Building against CUDA-Q.
Running the shipped examples
From a checkout of this repository, run any example with:
python3 preview/logical/examples/00_logical_resource_estimate.py
Every python3 command below is the same invocation with a different example
path.
Step 1 — Estimate an existing CUDA-Q kernel
The fastest route to a first number: take an ordinary CUDA-Q kernel, select a distance-3 rotated-surface-code target with room for one logical qubit, and estimate its resources through the CUDA-Q target integration.
# ============================================================================ #
# Copyright (c) 2026 NVIDIA Corporation & Affiliates. #
# All rights reserved. #
# #
# This source code and the accompanying materials are made available under #
# the terms of the Apache License 2.0 which accompanies this distribution. #
# ============================================================================ #
"""Explore physical surface-code estimates with a configurable target."""
# %%
# Import CUDA-Q and the CUDA-Q Logical target and result APIs.
import cudaq
import cudaq.logical as cql
# %%
# Author a logical-zero memory kernel whose qubit demand is a parameter.
@cudaq.kernel
def logical_zero_memory(logical_qubits: int):
qubits = cudaq.qvector(logical_qubits)
mz(qubits)
# %%
# Establish a one-qubit, distance-three reference configuration.
baseline_qubits = 1
baseline_target = cql.targets.surface_physical_target(
logical_capacity=baseline_qubits,
distance=3,
)
cudaq.set_target(baseline_target)
baseline_target.print_stack()
baseline_estimate = cudaq.estimate(logical_zero_memory, baseline_qubits)
baseline_analytical = baseline_estimate.annotations["ANALYTICAL"]
baseline_schedule = baseline_estimate.annotations["SCHEDULE"]
# %%
# Increase the workload capacity and code distance in a second physical study.
scaled_qubits = 3
scaled_target = cql.targets.surface_physical_target(
logical_capacity=scaled_qubits,
distance=5,
)
cudaq.set_target(scaled_target)
scaled_estimate = cudaq.estimate(logical_zero_memory, scaled_qubits)
scaled_static = cql.estimate.FabricCounts.from_annotations(
scaled_estimate.annotations)
scaled_analytical = scaled_estimate.annotations["ANALYTICAL"]
scaled_schedule = scaled_estimate.annotations["SCHEDULE"]
# %%
# Hold the layout fixed while changing the physical operating assumptions.
sensitivity_target = cql.targets.surface_physical_target(
logical_capacity=scaled_qubits,
distance=5,
p_phys=1.0e-4,
failure_budget=1.0e-6,
cycle_time=5.0e-9,
)
cudaq.set_target(sensitivity_target)
sensitivity_estimate = cudaq.estimate(logical_zero_memory, scaled_qubits)
sensitivity_analytical = sensitivity_estimate.annotations["ANALYTICAL"]
sensitivity_schedule = sensitivity_estimate.annotations["SCHEDULE"]
# %%
# Verify which metrics change with layout and operating-point parameters.
assert set(scaled_estimate.annotations) == {
"LOGICAL",
"STATIC",
"ANALYTICAL",
"SCHEDULE",
}
assert baseline_schedule["physical_qubits"] == cql.codes.Surface[3].block.size
assert scaled_static.logical_qubits_peak == scaled_qubits
assert scaled_schedule["physical_qubits"] > baseline_schedule["physical_qubits"]
assert scaled_schedule["event_count"] > baseline_schedule["event_count"]
assert sensitivity_schedule["physical_qubits"] == scaled_schedule[
"physical_qubits"]
assert sensitivity_schedule["event_count"] == scaled_schedule["event_count"]
assert sensitivity_schedule["makespan_ns"] > scaled_schedule["makespan_ns"]
assert sensitivity_analytical["logical_error"] < scaled_analytical[
"logical_error"]
assert scaled_analytical["budget_met"]
assert not sensitivity_analytical["budget_met"]
print("Surface-code layout scaling:")
print(" baseline (1 logical qubit, distance 3):")
print(f" physical qubits: {baseline_schedule['physical_qubits']}")
print(f" scheduled events: {baseline_schedule['event_count']}")
print(f" makespan: {baseline_schedule['makespan_ns']:.1f} ns")
print(" scaled memory (3 logical qubits, distance 5):")
print(f" physical qubits: {scaled_schedule['physical_qubits']}")
print(f" scheduled events: {scaled_schedule['event_count']}")
print(f" makespan: {scaled_schedule['makespan_ns']:.1f} ns")
print("Operating-point sensitivity at 3 logical qubits and distance 5:")
print(" target defaults:")
print(f" cycle time: {scaled_analytical['cycle_time'] * 1.0e9:g} ns")
print(f" physical error rate: {scaled_analytical['p_phys']:.1e}")
print(f" failure budget: "
f"{scaled_analytical['failure_budget']['total']:.1e}")
print(f" logical error: {scaled_analytical['logical_error']:.3e}")
print(f" budget met: {scaled_analytical['budget_met']}")
print(f" scheduled makespan: {scaled_schedule['makespan_ns']:.1f} ns")
print(" changed physical assumptions:")
print(f" cycle time: "
f"{sensitivity_analytical['cycle_time'] * 1.0e9:g} ns")
print(f" physical error rate: {sensitivity_analytical['p_phys']:.1e}")
print(f" failure budget: "
f"{sensitivity_analytical['failure_budget']['total']:.1e}")
print(f" logical error: {sensitivity_analytical['logical_error']:.3e}")
print(f" budget met: {sensitivity_analytical['budget_met']}")
print(f" scheduled makespan: "
f"{sensitivity_schedule['makespan_ns']:.1f} ns")
python3 preview/logical/examples/02_surface_code_resource_estimate.py
The example prints the selected backend stack, then compares physical-qubit, event-count, and makespan estimates for distance-3 and distance-5 layouts. It also holds the layout fixed while changing the physical error rate, failure budget, and cycle time, making the assumptions behind the estimate explicit.
Step 3 — Choose a code and a gadget (P2)
P2 is where codes and gadgets enter. This example defines the [[7,1,3]] Steane
code as a CSS block, declares a terminal-memory objective, and authors a gadget
that implements it — one syndrome-extraction pass followed by data-qubit
readout.
# ============================================================================ #
# Copyright (c) 2026 NVIDIA Corporation & Affiliates. #
# All rights reserved. #
# #
# This source code and the accompanying materials are made available under #
# the terms of the Apache License 2.0 which accompanies this distribution. #
# ============================================================================ #
"""Define a Steane code and a concrete syndrome-extraction gadget."""
# %%
# Import the standalone CUDA-Q Logical code and gadget APIs.
import cudaq.logical as cql
# %%
# Define the self-dual Steane CSS code from its check supports.
@cql.code
class Steane:
block = cql.codes.CSSBlock(data=7, sx=3, sz=3)
d = 3
hx = ((0, 1, 2, 3), (0, 1, 4, 5), (0, 2, 4, 6))
hz = hx
lx = (tuple(range(7)),)
lz = lx
# %%
# Declare the ideal logical operation implemented by the gadget.
@cql.objective
def terminal_memory(qubit: cql.types.logical_qubit) -> None:
cql.discard(qubit)
# %%
# Implement that operation using encoded syndrome extraction and measurement.
@cql.gadget(implements=terminal_memory)
def steane_memory(block: cql.patch[Steane]) -> None:
block, _ = cql.extract_syndrome(block)
block, _ = cql.mz(block.data)
cql.discard(block)
# %%
# Materialize both definitions and inspect the gadget's static operations.
code = cql.materialize(Steane)
gadget = cql.compile(steane_memory)
counts = cql.analysis.count(gadget)
assert "fabric.code @Steane" in code.to_mlir()
assert "fabric.gadget @steane_memory" in gadget.to_mlir()
assert (Steane.n, Steane.k, Steane.d.value) == (7, 1, 3)
print("Steane [[7,1,3]] terminal-memory gadget:")
print(f" authored operations: {dict(counts.operation_counts)}")
python3 preview/logical/examples/standalone/02_code_and_gadget.py
Steane [[7,1,3]] terminal-memory gadget:
authored operations: {'reset': 2, 'h': 2, 'cx': 2, 'read_syndrome_ancillas': 1, 'mz': 1, 'dealloc': 1}
cudaq.logical.materialize and cudaq.logical.compile lower both artifacts
into the fabric dialect, where cudaq.logical.analysis.count reports the
gadget’s authored operations. These verified, named gadgets are the atoms of
every P2 static estimate — the surface-code counts in Step 1 are sums over
exactly such calls.
Step 4 — Lower to physical events and schedule them (P3)
Everything so far stopped at the QEC realization. P3 adds the physical layer: carriers, the binding from encoded regions onto them, and an operating point that turns dimensionless cycles into time.
surface_architecture = recipes.surface_architecture(3)
surface_code = surface_architecture.code
builder = cql.devices.DeviceBuilder("StandaloneSurfaceCodeDevice")
compute = builder.logical.add_compute(capacity=1)
encoded_region = builder.qec.bind(compute, architecture=surface_architecture)
carriers = builder.physical.add_qubits(
surface_code.block.size,
native_actions=cql.architecture.physical_actions.clifford_set(),
native_instruments=(
cql.architecture.physical_instruments.MZ,
cql.architecture.physical_instruments.MPP,
),
)
builder.physical.bind(encoded_region, to=carriers)
builder.physical.set_operating_point(timing={"cycle_ns": 1.0})
device = builder.build()
# %%
With those facts present, the compiler lowers the placed program to a physical event graph, schedules it, and costs the schedule:
schedule = cql.compiler.schedule(physical)
resources = cql.estimate(
schedule,
tier=cql.estimate.Tier.SCHEDULE,
p_phys=1.0e-3,
failure_budget=0.1,
cycle_time=1.0e-9,
)
python3 preview/logical/examples/standalone/04_physical_schedule.py
Standalone physical schedule:
physical qubits: 17
scheduled events: 50
makespan: 44.0 ns
A distance-3 rotated surface code needs 17 carriers; the three requested syndrome rounds become 50 scheduled events with a 44 ns makespan under a 1 ns cycle. Change the operating point and the makespan moves; change the code distance and the carrier count moves. The logical program is untouched by either.
Step 5 — Emit Stim text from the command line
A verified P2 entry gadget can be projected to standards-compatible Stim
circuit text — CUDA-Q Logical’s secondary interchange path. qlx-translate
ships as a console script with the wheel you installed above. Point it at one
round of Steane syndrome extraction:
qlx-translate preview/logical/examples/mlir/stim_steane_memory.mlir \
--fabric-to-stim
R 7 8 9
H 7 8 9
CX 7 0 7 1 7 2 7 3 8 0 8 1 8 4 8 5 9 0 9 2 9 4 9 6
H 7 8 9
R 10 11 12
CX 0 10 1 10 2 10 3 10 0 11 1 11 4 11 5 11 0 12 2 12 4 12 6 12
M 7 8 9
M 10 11 12
M 0 1 2 3 4 5 6
Data carriers are 0–6, ancillas 7–12, and every stabilizer coupling is written
out. The text loads directly with the reference stim Python package. See
Stim emission for what the projection refuses
to do.
Where to go next
Continue with Build, place, and estimate a logical program for a step-by-step guide.
Use the task-oriented code, placement, and estimation guides.
Browse the remaining runnable studies in Examples.
Inspect compiler internals in the architecture reference.
To run the full conformance suite after building from source, see Building against CUDA-Q.