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:

  1. install CUDA-Q Logical;

  2. estimate an existing CUDA-Q kernel against a surface-code target;

  3. author a portable P0 logical program and read its logical estimate;

  4. define a QEC code and a verified gadget, and count their P2 operations;

  5. lower a surface-code workload to physical events and schedule it;

  6. 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.

examples/02_surface_code_resource_estimate.py
# ============================================================================ #
# 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 2 — Author a portable P0 program

You can also author logical programs directly through the cudaq.logical facade. A P0 program names no code, no device, and no carrier — and you can already estimate it.

examples/standalone/00_logical_program.py
# ============================================================================ #
# 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.     #
# ============================================================================ #
"""Author a portable logical program without a CUDA-Q kernel."""

# %%
# Import the standalone CUDA-Q Logical authoring and compiler API.
import cudaq.logical as cql


# %%
# Define a Bell program directly with linear logical-qubit values.
@cql.program
def bell() -> tuple[bool, bool]:
    qubits = cql.allocate(2, state=cql.types.zero)
    qubits[0] = cql.h(qubits[0])
    qubits[0], qubits[1] = cql.cx(qubits[0], qubits[1])
    return cql.measure_z(qubits[0]), cql.measure_z(qubits[1])


# %%
# Compile the program and request its logical resource counts directly.
build = cql.compile(bell)
resources = cql.estimate(build, tier=cql.estimate.Tier.LOGICAL)

assert build.stage == cql.stages.P0
assert resources.logical_qubits_peak == 2
assert resources.actions == {
    "qlx_standard_h": 1,
    "qlx_standard_cx": 1,
}

print(f"Portable Bell program: {resources.logical_qubits_peak} logical qubits")
python3 preview/logical/examples/standalone/00_logical_program.py
Portable Bell program: 2 logical qubits

Note the linear style: every operation consumes its operand and returns the successor, so a stale value is a typed error, not a silent bug. The Tier.LOGICAL estimate counts logical qubits and actions with any repetition still folded; P2 builds add Tier.STATIC, which reads the realized gadget counts directly.

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.

examples/standalone/02_code_and_gadget.py
# ============================================================================ #
# 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.

A device layered across P1, P2, and P3 (examples/standalone/04_physical_schedule.py).
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

To run the full conformance suite after building from source, see Building against CUDA-Q.