CUDA-Q Logical — Develop and evaluate fault-tolerant quantum applications

These pages cover the cudaq-logical preview package only. For everything else, see the main CUDA-Q documentation.

Preview release

cudaq-logical is in preview. Its APIs, behavior, and documentation may change substantially in upcoming versions.

CUDA-Q Logical (cudaq.logical) expands CUDA-Q with an open, extensible logical layer for fault-tolerant quantum computing. Use it to express fault-tolerant workloads, evaluate them across different QEC codes and system architectures, and understand the resources they need to run.

Fault-tolerant quantum computing is a co-design problem. Choices in the application, QEC code, logical architecture, physical hardware, decoding, control, and classical computing can all change the qubits and runtime required for the same computation. CUDA-Q Logical keeps the workload fixed while these system choices change, so you can compare results directly and inspect the assumptions behind each one.

In this preview, you can start from a CUDA-Q kernel or author a portable logical program directly; configure codes, gadgets, placement, and distillation protocols; generate resource estimates; and emit realized programs for simulation and further analysis in tools such as Stim.

Installation

CUDA-Q Logical comes pre-installed with cudaq, so one command is enough:

pip install cudaq

For more installation options or to build from source, see Quick start.

An end-to-end estimate

As a first step, define a CUDA-Q Logical target that describes the desired QEC encoding: for instance, a distance-3 surface code with room for one logical qubit. You can then lower any cudaq.kernel to a fault-tolerant program. cudaq.estimate returns the required resources for the encoded computation:

An ordinary CUDA-Q kernel lowered through a surface-code encoding (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")

When you call cudaq.estimate, CUDA-Q Logical takes the kernel’s logical intent through placement, QEC realization, physical lowering, and scheduling. This example compares physical-qubit, event-count, and makespan estimates across two surface-code layouts, then changes the operating assumptions while holding the layout fixed. The logical and static annotations still expose the protected logical qubits, encoded patches, and selected gadget calls. The examples page shows more of what CUDA-Q Logical can do.

CUDA-Q kernels are only one entry point. You can also author portable logical programs directly, define your own codes and gadgets, and place computations on a logical machine. The compiler refines a program through strict semantic stages, producing immutable evidence at each transition; see the architecture reference for the advanced staged model.

Choose your route

Start from the result you want:

If you want to…

Go to

Install and run a first estimate

Getting started

Build, place, and estimate a logical program

The logical programming stack

Do a specific job (define a code, place, distill, estimate, emit Stim)

Use cases

Browse runnable end-to-end examples

Examples

Inspect advanced internals or build against CUDA-Q

Reference