Skip to content
Bill Ticehurst edited this page Apr 28, 2026 · 1 revision

Migrating from qsharp.estimate to the Quantum Resource Estimator

qsharp.estimate is deprecated. Use qdk.qre for all new resource estimation work. This document explains the key differences and shows how to achieve the same results with the new API.

Why a new resource estimator?

The original qsharp.estimate function treats resource estimation as a single-shot black box: you hand it a Q# program plus a fixed parameter dictionary, and it returns one result (or one result per batch item).

The new Quantum Resource Estimator (qdk.qre) rethinks estimation as a compositional, exploratory process. Instead of a single opaque call, you assemble an estimation pipeline from modular, swappable pieces (application models, hardware architectures, error-correction codes, and magic-state factories), and the estimator systematically explores the combinatorial design space, returning a Pareto-optimal frontier of results that trade off qubit count against runtime.

A central design goal is that the estimator itself makes barely any assumptions about the underlying hardware or protocols. In the old estimator, concepts like the QEC scheme, the distillation strategy, and the qubit model were built-in parameters with a fixed structure. In the new estimator, all of these have been moved into flexible, extensible models that are provided as input. Architecture descriptions can be composed from independent layers, for example stacking multiple rounds of distillation factories, choosing separate memory codes, or even estimating without error correction entirely. Because each layer is an independent, composable transform, you can model hardware stacks that the old estimator could not express at all.

At a glance

Aspect qsharp.estimate (old) qdk.qre (new) Learn more
Input Q# expression string + JSON parameter dict Separate Application, Architecture, ISA query, trace query Getting Started
Hardware model Predefined names ("qubit_gate_ns_e4") or flat parameter dict GateBased(...) / Majorana(...) objects with typed fields Building Your Own Models
QEC scheme "surface_code" / "floquet_code" or custom dict SurfaceCode.q(), TwoDimensionalYokedSurfaceCode.q(), etc., composed as transforms Building Your Own Models
Magic-state factories Implicit (part of the QEC scheme) Explicit and composable: RoundBasedFactory.q(), Litinski19Factory.q(), custom factories Building Your Own Models
Error budget Single float or {logical, tStates, rotations} partition max_error float; budget allocation is automatic Getting Started
Constraints maxDuration, maxPhysicalQubits, maxTFactories Pareto frontier replaces hard constraints; filter the results table instead Analysing Results
Result Single JSON blob or batch list EstimationTable of Pareto-optimal entries with built-in plotting Analysing Results
Exploration Manual batching (EstimatorParams(num_items=N)) Automatic: queries enumerate all combinations Getting Started
Supported inputs Q#, OpenQASM, logical counts Q#, QIR, Cirq, OpenQASM, logical counts, or custom Application subclass Importing Quantum Programs

Installation

pip install qdk[qre]

The qdk package re-exports everything through qdk.qre.


Migration recipes

1. Basic estimation of a Q# program

Old:

import qsharp

qsharp.init(project_root="./my_project")
result = qsharp.estimate("RunAlgorithm()")
print(result)

New:

import qdk
from qdk import qsharp
from qdk.qre import estimate
from qdk.qre.application import QSharpApplication
from qdk.qre.models import GateBased, SurfaceCode, RoundBasedFactory

# Load your Q# project
qsharp.init(project_root="./my_project")

# Wrap the Q# entry point as an Application
app = QSharpApplication(qdk.code.RunAlgorithm)

# Choose a hardware architecture (gate-based, 1e-4 error rate)
arch = GateBased(error_rate=1e-4, gate_time=100, measurement_time=500)

# Run the estimator: explores surface code distances × factory protocols
results = estimate(app, arch, isa_query=SurfaceCode.q() * RoundBasedFactory.q(), max_error=0.01)

# View the Pareto frontier
results.as_frame()

Why is this longer? The new API does not provide default architectures or QEC schemes, because resource estimates are only meaningful relative to explicit hardware assumptions. By requiring you to spell out the architecture, error-correction code, and factory protocol, the new API makes every assumption visible and reproducible. In return, it explores the full design space automatically and returns a table of Pareto-optimal results, not a single point. Each row is a configuration where no other configuration is simultaneously better in both qubits and runtime.

2. Choosing a qubit model

Old:

from qsharp.estimator import EstimatorParams, QubitParams

params = EstimatorParams()
params.qubit_params.name = QubitParams.GATE_NS_E4       # gate-based, ns timescale, 1e-4
result = qsharp.estimate("RunAlgorithm()", params)

New:

from qdk.qre.models import GateBased

# Equivalent to QubitParams.GATE_NS_E4
arch = GateBased(error_rate=1e-4, gate_time=50, measurement_time=100)

# Or for Majorana-based hardware:
from qdk.qre.models import Majorana
arch = Majorana(error_rate=1e-4, measurement_time=100)

Key difference: Instead of selecting a named preset string, you construct a typed architecture object with explicit physical parameters. This makes the hardware assumptions transparent and easy to sweep over.

3. Choosing a QEC scheme

Old:

params.qec_scheme.name = "surface_code"

New:

from qdk.qre.models import SurfaceCode, RoundBasedFactory

# SurfaceCode is an ISA transform; compose it with a factory
isa_query = SurfaceCode.q() * RoundBasedFactory.q()

Key difference: QEC codes and magic-state factories are separate, composable transforms. You can mix and match them freely and the estimator will explore all valid combinations automatically.

4. Sweeping over parameters (batching)

Old:

from qsharp.estimator import EstimatorParams, QubitParams

params = EstimatorParams(num_items=3)
params.items[0].qubit_params.name = QubitParams.GATE_NS_E3
params.items[1].qubit_params.name = QubitParams.GATE_NS_E4
params.items[2].qubit_params.name = QubitParams.GATE_US_E3
result = qsharp.estimate("RunAlgorithm()", params)

New:

from qdk.qre import estimate, plot_estimates
from qdk.qre.models import GateBased, SurfaceCode, RoundBasedFactory

results = []
for error_rate in [1e-3, 1e-4, 1e-5]:
    arch = GateBased(error_rate=error_rate, gate_time=100, measurement_time=500)
    r = estimate(app, arch, isa_query=SurfaceCode.q() * RoundBasedFactory.q(),
                 max_error=0.01, name=f"p = {error_rate:.0e}")
    results.append(r)

# Overlay multiple runs on a single Pareto plot
plot_estimates(results, runtime_unit="ms")

Key difference: Instead of index-based batch items, you simply loop over the parameters you want to sweep. Each estimate call already explores the full design space internally (code distances, factory protocols, etc.). Use plot_estimates to compare runs visually.

5. Error budget

Old:

params.error_budget = 0.01
# or fine-grained:
from qsharp.estimator import ErrorBudgetPartition
params.error_budget = ErrorBudgetPartition(logical=0.003, t_states=0.003, rotations=0.004)

New:

results = estimate(app, arch, isa_query=..., max_error=0.01)

Key difference: The new estimator takes a single max_error parameter. The budget allocation across logical errors, T-state distillation, and rotation synthesis is handled automatically as part of the optimization. Unlike the old estimator, which hard-codes exactly three error sources (logical, T-states, rotations), the new estimator derives error contributions from the actual instructions in the trace and ISA. This means it naturally accounts for additional sources of error (e.g., different types of magic states or custom operations) without requiring a manual budget partition.

6. Constraints (maxDuration, maxPhysicalQubits)

Old:

params.constraints.max_physical_qubits = 50_000
params.constraints.max_duration = "1s"

New:

The new estimator returns the full Pareto frontier, so instead of asking for a single point subject to hard constraints, you filter the results after estimation:

results = estimate(app, arch, isa_query=..., max_error=0.01)

# Filter to results that fit in 50k qubits
df = results.as_frame()
df[df["qubits"] <= 50_000]

This approach is more flexible: you see the full trade-off landscape and can apply any combination of filters after the fact.

7. Working with results

Old:

result = qsharp.estimate("RunAlgorithm()")
print(result.data())           # raw JSON
print(result.logical_counts)   # logical gate counts
result.summary                 # HTML summary
result.diagram                 # space-time diagram

New:

results = estimate(app, arch, isa_query=..., max_error=0.01)

# Pareto frontier as a pandas DataFrame
results.as_frame()

# Access individual results
entry = results[0]
print(entry.qubits, entry.runtime, entry.error)

# Add detail columns
results.add_qubit_partition_column()   # compute / factory / memory breakdown
results.add_factory_summary_column()   # e.g. "20×T"

# Plot the frontier
from qdk.qre import plot_estimates
plot_estimates(results, runtime_unit="ms")

8. Supported input formats (beyond Q#)

The new estimator supports additional input formats through specialized application classes:

from qdk.qre.application import (
    QSharpApplication,     # Q# programs
    QIRApplication,        # QIR bitcode files
    CirqApplication,       # Cirq circuits
    OpenQASMApplication,   # OpenQASM programs
)

You can also subclass Application to implement a fully custom application model that generates traces programmatically.


Concept mapping reference

Old concept New equivalent Notes
qsharp.estimate(expr, params) estimate(app, arch, isa_query, ...) Separate application from hardware
EstimatorQubitParams GateBased(...) / Majorana(...) Typed architecture objects
EstimatorQecScheme SurfaceCode, TwoDimensionalYokedSurfaceCode, etc. Composable ISA transforms
DistillationUnitSpecification RoundBasedFactory, Litinski19Factory, etc. Composable ISA transforms
ErrorBudgetPartition max_error parameter Automatic budget allocation
EstimatorConstraints Filter EstimationTable results Pareto frontier replaces hard constraints
EstimatorParams(num_items=N) Loop + plot_estimates([...]) Each call already explores internally
EstimatorResult (JSON dict) EstimationTable / EstimationTableEntry Structured, extensible results
result.diagram plot_estimates(results) Interactive Pareto frontier plot
result.summary results.as_frame() Tabular summary with custom columns
result.data() entry.qubits, entry.runtime, entry.error, entry.properties Typed access to result data

Further reading

  • Getting started notebook: samples/qre/0_getting_started.ipynb
  • Importing quantum programs: samples/qre/1_qre_input.ipynb
  • Analysing results: samples/qre/2_analysing_results.ipynb
  • Building custom models: samples/qre/3_building_your_own_models.ipynb

Clone this wiki locally