# Lesson 29 — Physical resource estimation and team contracts

## Turn the workload into a conditional engineering statement

We now have two things that must meet: the complete logical workload from Lesson 27, and the imperfect protected instructions from Lesson 28. Resource estimation asks how many physical qubits and how much modeled execution time that combination requires at a stated reliability target. It does not determine whether the chosen active space is chemically adequate, whether the initial state has useful overlap, or whether the answer creates practical value.

Our running scientific story remains the intended-singlet nitrogen calculation at 1.85 Å. The separate `qpe_stretched_n2.ipynb` resource-estimation example uses 1.270025 Å and estimates selected iteration circuits. We will use its API pattern without confusing its geometry or component scope with the full course experiment.

This chapter follows QDK Chemistry revision `97a3ff4f` and, where the implementation is external, public QDK revision `95c039f4b0447928aa041f5ae5ad52e7041d8937`. The examples below are unexecuted, version-bound teaching sketches, not claims about an installed environment. Chemistry declares `qdk[jupyter]>=1.31.0` and a separate `qre` extra requesting `qdk[qre,jupyter]>=1.31.0` in its [dependency manifest](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/pyproject.toml). A lower-bound dependency is not an exact reproducibility lock.

There are **two alternative interfaces**, not two required successive stages. `Circuit.estimate(...)` is a convenience route from a supported stored/live circuit to the older-style estimator parameter/result model. `Circuit.get_qre_application()` supplies an Application for the direct `qdk.qre` trace/architecture/query interface used by the notebooks. Choose the interface that matches the study and supported environment; do not feed one route’s dictionaries into the other as if their schemas were interchangeable.

## What the chemistry adapter actually selects

The [Circuit implementation](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/src/qdk_chemistry/data/circuit.py) checks available representations in a definite order:

| Available representation | `estimate` route | `get_qre_application` route |
|---|---|---|
| Live Q# factory data | Invoke the factory through its QDK context for logical counts; call those counts’ `estimate` | Construct `QSharpApplication` with the callable and positional arguments |
| QASM, without a factory | Call the external OpenQASM estimate function | Construct `OpenQASMApplication` |
| QIR only | Raise `RuntimeError` | Construct `QIRApplication` |
| Only a diagram-like Q# circuit object, or no supported input | Not a supported estimation representation by itself | Not a supported Application representation by itself |

If several representations exist, the earlier route wins. These methods do not automatically compare representations for equivalence. The factory’s parameter dictionary values are passed positionally in insertion order; merely having familiar parameter names does not repair an incorrectly constructed ordering.

The convenience method reports a discrepancy between `Circuit.num_qubits` and counted `numQubits` but does not change the declared width. It does not add qubits just because metadata declares them, nor subtract internally allocated scratch because a diagram hides it. The [Circuit tests](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/tests/test_circuit.py) explicitly exercise a declared width of five with a two-qubit QASM count and preserve the declaration. These are read source tests, not executions performed for this course.

`get_qre_application` imports its optional external classes lazily. An unavailable `qdk.qre` import becomes a `RuntimeError` identifying the missing optional dependency. A QIR-only representation therefore has a supported route in principle, but that does not imply arbitrary QIR profiles or adaptive control flows are handled faithfully by the external importer.

## Route A: a stored-circuit convenience estimate

Assume `selected_circuit` is an existing Circuit whose exact scope has already been recorded. The following sketch illustrates the parameter structure, not a completed nitrogen estimate:

```python
params = {
    "qubitParams": {"name": "qubit_gate_ns_e3"},
    "qecScheme": {"name": "surface_code"},
    "errorBudget": 0.01,
    "estimateType": "frontier",
}
result = selected_circuit.estimate(params)
raw = result.data()
```

The input `params` accepts `None`, a dictionary, a list of parameter dictionaries, or the external `EstimatorParams` helper. A batch estimates the supplied circuit under multiple parameter choices; it does not mean execute that circuit repeatedly as a statistical experiment. In particular, a list of hardware scenarios is not a list of IQPE shots.

The helper uses snake-case Python attributes such as `qubit_params`, `qec_scheme`, `error_budget`, and `estimate_type`, while dictionaries use camel-case keys such as `qubitParams` and `errorBudget`. Its `as_dict()` performs validation and conversion. At the inspected external revision, constructing `EstimatorParams` emits a deprecation warning recommending the newer direct `qdk.qre` route. That external evolution does not remove the convenience method from the pinned chemistry adapter.

For typed batching, `EstimatorParams(num_items=...)` supports one through 1000 items. Its block inheritance is not a general recursive dictionary merge: a nonempty item-level qubit or QEC block takes precedence over the corresponding default block. Inspect the emitted per-item dictionaries rather than assuming every top-level field is inherited. `LogicalCounts.estimate` sends the generated item list to the native numerical estimator.

The stored-circuit UI tool has the same narrow purpose. [`estimate_circuit`](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/src/qdk_chemistry/ui/tools.py) takes `project_name`, `circuit_filename`, and JSON-compatible `params`, loads the Circuit, and returns `circuit.estimate(params).data()` through its structured response wrapper. It does not build an entire application or save a new result artifact in that entry point. Its [MCP tests](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/tests/ui/test_mcp_circuit_estimate.py) verify parameter forwarding and raw batch-result preservation with mocks, not physical accuracy or every real-estimator failure path.

## Choose a coherent physical and QEC model

The convenience interface exposes six built-in physical model names: `qubit_gate_us_e3`, `qubit_gate_us_e4`, `qubit_gate_ns_e3`, `qubit_gate_ns_e4`, `qubit_maj_ns_e4`, and `qubit_maj_ns_e6`. These are scenario presets, not reservations of real devices. The accepted QEC names include `surface_code` and `floquet_code`, with compatibility restrictions discussed in Lesson 28.

For example, the inspected [physical-model source](https://github.com/microsoft/qdk/blob/95c039f4b0447928aa041f5ae5ad52e7041d8937/source/resource_estimator/src/system/modeling/physical_qubit.rs) assigns the `qubit_gate_ns_e3` preset 50 ns one- and two-qubit gates, 100 ns measurement, and the specified modeled error rates. Its microsecond-family presets have different timing and T-state-error assumptions. The older Majorana presets use 100 ns measurement assumptions; these are **not the same defaults** as the direct `Majorana` class whose default time is 1000 ns. Matching a broad hardware name across interfaces is insufficient for a fair comparison.

Custom `qubitParams` can specify `instructionSet`, operation times, operation error rates, idle error, and, for measurement-based models, separate measurement process/readout errors. A process error changes the quantum evolution; a readout error misreports a classical outcome. Accepted time strings include suffixes `s`, `ms`, `us`, `µs`, `μs`, and `ns`; the numerical result runtime uses nanoseconds. A bare timing number and an energy in Hartree are not interchangeable with such durations.

The native normalizer fills documented omissions from a selected preset or related fields. A genuinely custom gate-based model requires measurement time/error, one-qubit gate time/error, and T error in the inspected backend; missing two-qubit/T times may be derived from one-qubit gate time. The Python helper’s checks are not the entire backend contract. Supply justified values and inspect the normalized parameters rather than assuming an omitted field means zero cost or zero error.

A custom `qecScheme` can provide `errorCorrectionThreshold`, `crossingPrefactor`, `distanceCoefficientPower`, `logicalCycleTime`, `physicalQubitsPerLogicalQubit`, and `maxCodeDistance`. The [QEC model evaluator](https://github.com/microsoft/qdk/blob/95c039f4b0447928aa041f5ae5ad52e7041d8937/source/resource_estimator/src/system/modeling/fault_tolerance.rs) evaluates formula strings with actual names such as `codeDistance`, `oneQubitGateTime`, and `twoQubitGateTime`. Some nearby explanatory comments use older snake-case names; use the implemented formula context, not a guessed spelling.

In the [threshold-distance model](https://github.com/microsoft/qdk/blob/95c039f4b0447928aa041f5ae5ad52e7041d8937/source/resource_estimator/src/estimates/error_correction/code_with_threshold_and_distance.rs), `distanceCoefficientPower` is a nonnegative integer b multiplying the error fit by \(d^b\): for odd distance d, the fit is \(p_L=A d^b(p/p_{\mathrm{th}})^{(d+1)/2}\). A is the crossing prefactor, p the modeled physical error, and \(p_{\mathrm{th}}\) the threshold. With b=0 this reduces to the simpler form in Lesson 28. `maxCodeDistance` limits the available distance search; it does not force every logical patch to use that distance. These are parameters of a chosen error fit, not knobs that improve hardware merely by being changed.

Formulas are supplied as a coupled model: patch geometry, timing, error suppression, and distance all refer to the same code/implementation assumptions. Syntax validation, positive evaluated resources, and threshold checks do not establish that arbitrarily assembled formulas describe a realizable machine. Do not independently choose the smallest area, shortest cycle, and best threshold from incompatible codes.

## Error budgets, synthesis, and constraints in Route A

The inspected [job-parameter backend](https://github.com/microsoft/qdk/blob/95c039f4b0447928aa041f5ae5ad52e7041d8937/source/resource_estimator/src/system/data/job_params.rs) defaults to total error budget 0.001 and `singlePoint` estimation. With no physical/QEC override, its defaults are `qubit_gate_ns_e3` and `surface_code`. These defaults differ from the notebook’s explicit direct-model scenario and 0.01 allowance.

A scalar `errorBudget` must lie strictly between zero and one. If the logical counts contain rotations, the backend initially divides it among logical, T-state, and rotation contributions. With T demand but no rotations it uses two portions; without either it allocates the budget to logical errors. These are modeling partitions, not chemical error bars.

A manually partitioned budget allows greater control. In this inspected external revision, the **input** JSON keys are `logical`, `tStates`, and `rotations`; the Python helper is `ErrorBudgetPartition(logical=..., t_states=..., rotations=...)`. All relevant portions must be positive, irrelevant T/rotation portions may be zero, and the total must remain below one. There is a source-version trap: the chemistry skill describes `tstates`, and a single-point **output** uses `tstates`, but the inspected backend input and Python conversion use `tStates`. Do not round-trip the output blindly as input. The scalar example above avoids that spelling difference; a pinned study must verify its actual schema.

`constraints.logicalDepthFactor` is a modeled multiplier on logical depth, not an automatic geometry-aware router or decoder benchmark. `maxTFactories` caps parallel factories. `maxDuration` constrains modeled duration and `maxPhysicalQubits` constrains footprint, but those last two are mutually exclusive in this convenience interface. The typed helper rejects both together and requires positive-size constraints, with the depth factor at least one. Backend constraints also include a distillation-round limit; not every backend field is exposed by every typed helper revision.

`estimateType` is `singlePoint` or `frontier`. A frontier requests alternative feasible space/time tradeoffs, not one universally best design. The inspected [native driver](https://github.com/microsoft/qdk/blob/95c039f4b0447928aa041f5ae5ad52e7041d8937/source/resource_estimator/src/system.rs) rejects frontier requests that also supply `maxDuration`, `maxPhysicalQubits`, or `maxTFactories`; use constrained `singlePoint` searches or generate an unconstrained frontier and filter it explicitly. If a deadline or qubit cap leaves no feasible candidate, that is a result about the chosen search/model constraints, not proof that the chemical task is impossible under every future architecture.

Distillation can use the default units or `distillationUnitSpecifications`. Inspected predefined unit names include `15-to-1 RM prep` and `15-to-1 space efficient`. A custom specification supplies a display name, input/output state counts, `failureProbabilityFormula`, `outputErrorRateFormula`, and physical/logical unit resources. At least one physical or logical specification is required by the backend. A first-round logical override needs a logical specification to override. Unit resources include `numUnitQubits` and `durationInQubitCycleTime`.

The [distillation evaluator](https://github.com/microsoft/qdk/blob/95c039f4b0447928aa041f5ae5ad52e7041d8937/source/resource_estimator/src/system/modeling/tfactory.rs) makes `inputErrorRate`, `cliffordErrorRate`, and `readoutErrorRate` available to the formulas. A custom failure polynomial is not independently validated as a real protocol just because it parses. A predefined name and a custom typed specification are mutually exclusive; select the documented contract deliberately.

## What is calculated from logical counts

Before interpreting a physical answer, understand the layout abstraction. The external [`LogicalCounts`](https://github.com/microsoft/qdk/blob/95c039f4b0447928aa041f5ae5ad52e7041d8937/source/qdk_package/qdk/estimator/_estimator.py) carries `numQubits`, `tCount`, `rotationCount`, `rotationDepth`, `cczCount`, `ccixCount`, and `measurementCount`, with optional compute/memory fields. `rotationDepth` is the dependency depth of rotation layers, not total gate count or total circuit depth. Missing values in a manually constructed object can default to zero; missing information must not be mistaken for measured absence.

The [convenience layout implementation](https://github.com/microsoft/qdk/blob/95c039f4b0447928aa041f5ae5ad52e7041d8937/source/resource_estimator/src/system/data/logical_counts.rs) uses Parallel Synthesis Sequential Pauli Computation, abbreviated PSSPC. It models rotations prepared/synthesized in parallel where allowed and sequential Pauli-based logical computation. It is a resource-layout model, not a complete physical control schedule extracted from every circuit wire.

Let n be counted algorithm qubits, \(N_R\) the rotations, \(D_R\) their depth, \(N_T\) the explicit T count, \(N_C\) the sum of CCZ/CCiX counts, and \(N_M\) the measurement count. Ignoring separate memory-transfer extensions, its modeled overhead has the form

\[
Q_{\mathrm{layout}}=2n+\left\lceil\sqrt{8n}\right\rceil+1,
\qquad
N_{T,\mathrm{demand}}=N_T+4N_C+\nu N_R,
\]

\[
D_{\mathrm{layout}}=N_M+N_R+N_T+3N_C+\nu D_R.
\]

Here \(\nu\) is modeled T demand per arbitrary rotation. For \(N_R>0\) and allocated synthesis allowance \(\epsilon_{\mathrm{rot}}\), the inspected constants give

\[
\nu=\left\lceil0.53\log_2\!\left(\frac{N_R}{\epsilon_{\mathrm{rot}}}\right)+4.86\right\rceil.
\]

The constants belong to a particular synthesis/modeling construction cited by the estimator; they are not a universal exact gate-optimal decomposition for every angle. The Clifford workload is represented through this layout abstraction rather than by charging every original Clifford gate separately. That does not make protected Clifford operations free in the physical model.

This also explains why output `algorithmicLogicalQubits` can exceed input `logicalCounts.numQubits`. First there may be internal circuit scratch; then there is layout overhead; then each protected logical unit has a physical code cost. A resource count is meaningful only if its level is named.

## Read Route A results without hiding failure

`EstimatorResult.data()` returns raw dictionaries or a batch list. At the inspected revision, a one-element list is unwrapped to a single dictionary. A successful item has `status == "success"`; failed data use error code/message information and must not be interpreted as resources equal to zero.

A successful single-point item contains `logicalCounts`, `jobParams`, `physicalCounts`, `logicalQubit`, `reportData`, and relevant factory/error-budget fields. `physicalCounts.physicalQubits` is total modeled physical footprint; `physicalCounts.runtime` is duration in nanoseconds. Its breakdown includes algorithmic logical qubits/depth, final logical depth, T-state demand, number of factories/runs, and algorithm/factory physical-qubit contributions. `logicalQubit.codeDistance` is the selected compute-code parameter, not necessarily every factory’s code distance.

For a frontier, these candidate-specific fields live inside `frontierEntries`. The inspected [result producer](https://github.com/microsoft/qdk/blob/95c039f4b0447928aa041f5ae5ad52e7041d8937/source/resource_estimator/src/system/data/result.rs) sorts those entries by runtime. Some convenience indexing falls back to the first entry. Reading `result["physicalCounts"]` alone can therefore hide the rest of the frontier; inspect all entries and their assumptions.

The failure contract needs equal care. A failed singleton may raise `EstimatorError` rather than return a result. At this external revision that class derives from `BaseException`, so an `except Exception` clause is not a guaranteed catch for it. Batch items can preserve individual failures, but the chemistry adapter immediately accesses `result.logical_counts`, whose batch accessor uses the first item. Static inspection therefore identifies a possible `KeyError` if the first failed item lacks `logicalCounts`. Do not promise that every partial batch failure reaches the UI unchanged; preserve the actual error and rerun a permitted, isolated diagnostic only when execution is authorized.

## Route B: an Application, a trace query, and an ISA query

An **Application** describes a problem instance and how to generate resource traces. In the direct abstraction, application parameters can fix the problem, while trace parameters choose implementations of that same problem. A stored Circuit adapter generally supplies one fixed circuit trace, not an automatic family of active spaces, Trotter accuracies, or trial-state strategies.

A **trace** records operations, operands, repeated blocks, and resource-state demands in a form useful for counting/scheduling models. A **trace query** enumerates selected transformations or implementation parameters for that program. An **ISA query** enumerates available protected instructions derived from a physical architecture and QEC/factory models. These objects answer different questions: what work is requested versus how the machine can implement it.

The [`TraceQuery` implementation](https://github.com/microsoft/qdk/blob/95c039f4b0447928aa041f5ae5ad52e7041d8937/source/qdk_package/qdk/qre/_trace.py) uses `*` to chain transforms in order. The [`ISAQuery` implementation](https://github.com/microsoft/qdk/blob/95c039f4b0447928aa041f5ae5ad52e7041d8937/source/qdk_package/qdk/qre/_isa_enumeration.py) uses `*` for combinations of instruction sets, with later matching instruction IDs overriding earlier ones; `+` enumerates alternatives. These operators compose search descriptions. They do not multiply physical-qubit counts or repeat the application.

A `.q(...)` call creates such a query. Lists specify explored parameter domains. No-argument queries may enumerate defaults from model metadata rather than one fixed constructor default. For example, the inspected PSSPC constructor defaults to 20 T states per rotation, whereas its query domain spans 5 through 20 unless overridden. Record the actual domains in a reproducible study.

The architecture and transforms carry instruction requirements. A ThreeAux query requires measurement-based primitives; a round-based T factory needs a physical or already encoded T source plus compatible operations for its protocols. The factory’s default code query is gate-based SurfaceCode, so the Majorana notebook explicitly supplies ThreeAux for factory-code exploration too. A mathematically attractive but incompatible combination does not describe a valid ISA.

## Do not assume every Application contains the original detailed trace

The actual default matters. At the inspected external revision, [`QSharpApplication`](https://github.com/microsoft/qdk/blob/95c039f4b0447928aa041f5ae5ad52e7041d8937/source/qdk_package/qdk/qre/application/_qsharp.py) defaults `use_trace_backend=False`. Its [interoperability helper](https://github.com/microsoft/qdk/blob/95c039f4b0447928aa041f5ae5ad52e7041d8937/source/qdk_package/qdk/qre/interop/_qsharp.py) obtains logical counts, distributes rotation counts into synthetic layers matching their count/depth, and creates repeated blocks for T-like, CCX-like, and measurement work. That trace preserves selected aggregate resource facts, not every original dependency and branch.

The external class offers `use_trace_backend=True` for direct trace generation from Q# execution, but the chemistry adapter does not pass that flag. A separately constructed external Application is a different, version-sensitive choice that needs supported-program validation. Supplying precomputed LogicalCounts cannot recover the missing detailed order; the direct-trace option rejects that combination. Also, a manually created LogicalCounts with rotations but default zero rotationDepth is not an adequate trace input: the inspected count-to-trace bridge only constructs its rotation blocks when both are nonzero.

`OpenQASMApplication` imports the supplied QASM into the QDK context and uses the count-derived trace helper in this implementation. `QIRApplication` accepts base-profile QIR text/bitcode according to its external contract. Its [QIR trace importer](https://github.com/microsoft/qdk/blob/95c039f4b0447928aa041f5ae5ad52e7041d8937/source/qdk_package/qdk/qre/interop/_qir.py) includes a gate/measurement mapping but documents that conditional branches follow the false path, assuming zero measurement results. It skips bare RESET and several classical/runtime operations; correlated-noise instructions are unsupported.

Consequently, “QIR accepted” does not mean exact expected-cost or worst-case analysis of arbitrary adaptive algorithms. An all-zero path is not automatically the longest path. Supply an explicitly justified branch model, separate branch traces, or a supported complete workload representation when classical feedback changes cost. Noise instructions and QEC syndrome simulations are not required to count a logical trace.

## Follow the notebook’s direct-query pattern

The [nitrogen notebook](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/examples/qpe_stretched_n2.ipynb) constructs its Application from `iqpe_iter_circuits[0]`, the largest iteration circuit. Here is an annotated, version-bound sketch using that existing object; it is intentionally still **component scope**:

```python
from qdk.qre import estimate, PSSPC, LatticeSurgery
from qdk.qre.models import Majorana, RoundBasedFactory, ThreeAux

app = iqpe_iter_circuits[0].get_qre_application()
architecture = Majorana(error_rate=1e-5)
trace_query = (
    app.q()
    * PSSPC.q(num_ts_per_rotation=[16, 17, 18, 19])
    * LatticeSurgery.q(slow_down_factor=[1.0, 2.0, 4.0])
)
isa_query = (
    ThreeAux.q(distance=[11, 13, 15, 17, 19])
    * RoundBasedFactory.q(
        code_query=ThreeAux.q(distance=[5, 7, 11, 13, 15, 17, 19]),
        use_cache=False,
    )
)
results = estimate(
    app, architecture, isa_query, trace_query,
    max_error=0.01, use_graph=False, post_process=False,
    name="Largest IQPE iteration only",
)
```

The notebook itself uses simpler default queries and `use_cache=True`. The expanded lists illustrate the sweep style actually used in the [Ising estimation notebook](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/examples/estimation_ising_2d.ipynb), which explores rotation values 16–19 and slowdown factors 1–34, along with separate compute and factory distance domains. The shorter slowdown list above is an illustrative study choice, not a recommended optimum or a report of computed outcomes.

The direct [`estimate` function](https://github.com/microsoft/qdk/blob/95c039f4b0447928aa041f5ae5ad52e7041d8937/source/qdk_package/qdk/qre/_estimation.py) defaults `max_error` to 1.0, so specify a meaningful budget explicitly. When no trace query is supplied, it uses `PSSPC.q() * LatticeSurgery.q()`. Its default error composition is `ErrorComposition.UnionBound`; `ErrorComposition.Product` instead combines contributions as \(1-\prod_i(1-p_i)\). Neither choice automatically validates independence or the physical noise assumptions behind the input contributions.

## What the direct transforms do, not merely their names

The direct [PSSPC implementation](https://github.com/microsoft/qdk/blob/95c039f4b0447928aa041f5ae5ad52e7041d8937/source/qre/src/trace/transforms/psspc.rs) counts Pauli measurements, rotations, T-like gates, and CCX-like gates, tracking rotation dependency depth across shared operands and Clifford interactions. It constructs the same style of layout overhead taught above, adds resource-state demand, and attaches an approximation contribution to the trace. Unsupported non-Clifford instructions raise rather than silently receiving a valid arbitrary cost.

Here the chosen integer \(\nu=\texttt{num_ts_per_rotation}\) determines a modeled per-rotation synthesis error

\[
\epsilon_{\mathrm{one\ rotation}}=2^{(4.86-\nu)/0.53}.
\]

The trace receives \(N_R\epsilon_{\mathrm{one\ rotation}}\) as a base approximation contribution. This is the cited mixed-fallback synthesis model, not an actual synthesized gate string or a chemical-energy error. Increasing \(\nu\) trades greater T demand and synthesis depth for lower modeled approximation error. It is a meaningful precision/resource knob only within that model’s assumptions.

By default `ccx_magic_states=False`, so CCX-like work contributes four T states per counted operation. Setting it true requests CCX resource states instead. A T-only RoundBasedFactory does not thereby acquire a CCX-state supply: the ISA must provide every resource type the transformed trace requires.

The direct [LatticeSurgery transform](https://github.com/microsoft/qdk/blob/95c039f4b0447928aa041f5ae5ad52e7041d8937/source/qre/src/trace/transforms/lattice_surgery.rs) replaces the trace with repeated logical lattice-surgery work of depth \(\lceil sD\rceil\), where D is the transformed depth and s is `slow_down_factor`. Its operation spans the transformed compute operands. It is not drawing a routed patch layout or modeling every wire movement. Slower logical progress can allow fewer simultaneous factories, but it increases protected computation time and may increase error exposure.

Compute distance and factory distances need not coincide. The optimal balance can use a large distance for a long-lived compute area and different distances in distillation rounds. The query explores these compatible options; it does not turn one hand-picked distance into an unquestionable output fact. Preserve the instruction provenance and factory details that explain each retained candidate.

## Search completeness, caching, and actual execution boundaries

`use_graph=True` is the direct estimator default. It prunes instruction choices using local space/time/error comparisons. The public source explicitly warns that this can miss globally Pareto-optimal application results: factory footprint is the number of required copies times the space per factory, and the copy count depends on runtime and discrete production opportunities. A locally attractive instruction need not produce the best whole-machine tradeoff.

`use_graph=False` enumerates the ISAs yielded by the supplied queries without that outer graph-pruning shortcut. It does not search all imaginable architectures, all code distances, or all factory protocols. Individual model/query implementations can already restrict or filter their candidates. State the generated search space and the completeness boundary rather than calling the result the globally optimal quantum computer.

The `post_process` option also needs care. At the inspected revision, its implementation samples application post-processing effects, infers per-trace resource multipliers, filters approximate candidates, and then reevaluates retained candidates. The basic sketch leaves it false. A custom post-processing function with arbitrary architecture-dependent nonlinear effects requires its own correctness review; the option is not a general proof that every adjusted frontier remains complete.

Caching is a real side effect, not part of quantum cost. The external [`RoundBasedFactory`](https://github.com/microsoft/qdk/blob/95c039f4b0447928aa041f5ae5ad52e7041d8937/source/qdk_package/qdk/qre/models/factories/_round_based.py) defaults to disk caching. At this inspected revision its cache-path helper creates a directory before the `use_cache` condition, so **`use_cache=False` is not a guarantee of zero filesystem writes**. The Q# Application has separate optional compiled-trace caching. No calls in these sketches were executed for this read-only course preparation.

The reviewed numerical paths use local QDK compiler/interpreter and Python/Rust estimation routines, not remote quantum-job submission. They may consume substantial classical CPU/memory as traces and candidate sets grow. Telemetry hooks are present and their complete network behavior was not audited here; convenience notebook diagrams also reference an external rendering script. Local numerical estimation is therefore not the same claim as universally network-free package behavior. Neither physical execution nor a noisy QEC simulation is required merely to obtain a modeled estimate.

## Read direct results as a table of candidates

The direct return type is an [`EstimationTable`](https://github.com/microsoft/qdk/blob/95c039f4b0447928aa041f5ae5ad52e7041d8937/source/qdk_package/qdk/qre/_results.py), a list-like collection of entries, not a convenience `EstimatorResult` dictionary. Each entry exposes `qubits`, `runtime`, `error`, `source`, `factories`, and additional `properties`. Runtime is an integer number of nanoseconds; `as_frame()` formats its default runtime column as a pandas duration.

`source` records how the selected ISA instructions were produced. The Ising example extracts compute distance through `entry.source[LATTICE_SURGERY].instruction[DISTANCE]` and synthesis precision through `entry.properties[NUM_TS_PER_ROTATION]`. Those are actual exported property/instruction keys, not universal string columns automatically present in every model result.

For interpretation, `add_factory_summary_column()` displays the number and type of resource-state factory copies, and `add_qubit_partition_column()` exposes modeled compute/factory/memory contributions where supplied. A display helper that substitutes zero for a missing property does not prove the architecture physically needs no such resource. Consult provenance and scope when a field is absent.

The table’s `stats` includes `num_traces`, `num_isas`, `total_jobs`, `successful_estimates`, and `pareto_results`. A search yielding no retained entries produces diagnostics about missing compatible instructions, empty query outputs, errors, or exceeding `max_error`. An empty table is **not an estimate of zero qubits and zero time**. Increasing the allowed error may expose a candidate, but that changes the scientific engineering requirement rather than fixing a numerical typo.

## Pareto frontiers make the tradeoff visible

A candidate is dominated if another feasible candidate uses no more qubits and no more time, with at least one strict improvement. The **Pareto frontier** consists of candidates not dominated in that sense. Direct estimation retains space/time-optimal entries subject to its allowed-error condition; error need not be a third optimization axis merely because it is displayed.

The following table is entirely synthetic. It is not generated by either estimator and has no connection to a measured nitrogen or Ising workload. Assume all rows describe the same complete workload and satisfy the same reliability requirement.

| Hypothetical candidate | Physical qubits | Duration | Interpretation |
|---|---:|---:|---|
| A | 200,000 | 4 hours | Smallest footprint among these choices |
| B | 400,000 | 2 hours | Uses more space to finish earlier |
| C | 300,000 | 5 hours | Dominated by A |
| D | 800,000 | 1 hour | Fastest among these choices |

A, B, and D are nondominated in this small set. A 2.5-hour deadline admits B and D, not A. A 300,000-qubit cap favors A; applying both requirements finds no feasible row here. That does not prove no configuration outside the explored set could satisfy them. In the convenience API, study such restrictions separately or inspect/filter a generated frontier because its two named hard constraints cannot be supplied together.

Unit conversion must remain explicit. A hypothetical runtime of \(7.2\times10^{12}\) ns is 7200 seconds, or two hours—not 7200 nanoseconds or two hours of laboratory wall-clock occupancy. Queueing, compilation, initialization not represented in the trace, calibration, classical preprocessing, and operational downtime can remain outside modeled quantum runtime.

Parallel shots can reduce elapsed campaign time only if their machines, factories, classical control, and initializations are supplied concurrently. Doubling throughput by duplicating the whole machine can approximately double space; sharing limited factory capacity changes that reasoning. A Pareto point for one circuit does not specify an unrestricted multi-experiment schedule.

## Compare chemistry algorithms only after matching the task

The nitrogen notebook estimates the largest Trotter IQPE iteration and the largest qubitization iteration separately under one chosen architecture/error budget. That is a useful component comparison. It is not automatically a comparison of complete algorithms that produce an energy with the same tolerance and confidence.

For fixed-base-U QPE, Lesson 27 showed why all powers sum to \(2^m-1\), while the largest power is \(2^{m-1}\). IQPE also prepares fresh states per bit and shot. An all-bit campaign includes those preparations, readouts, trial repeats, and classical aggregation. Multiplying a largest-circuit runtime by an unexplained constant is not a substitute for this ledger, especially when power mappers, reusable ancillas, or synthesis choices change component costs.

For Trotter versus LCU/qubitization/SOSSA, match molecule, geometry, orbital basis, electron/symmetry sector, target state/observable, active/core energy convention, approximation tolerance, and overall success criterion. Equal phase-bit counts do not necessarily imply equal energy resolution: evolution-time phases and block-encoding normalization/shift decoders have different energy sensitivity. The SOS shifted signal and ordinary Hamiltonian energy must be decoded and offset according to their own contracts.

Neither interface infers useful eigenstate overlap or a valid whole-run IQPE success probability. State preparation, postselection losses, and amplification oracles are work that must actually be included. Logical reliability cannot rescue a hybrid-bit IQPE outcome or an off-grid amplification construction that fails its ideal algorithmic assumptions.

The Ising notebook similarly estimates a chosen evolution circuit and explores term grouping/implementation parameters. A full time series, observable campaign, or parameter sweep repeats additional work. An entire chemical potential-energy curve includes multiple geometries, often new classical preprocessing and changed overlaps—not just the best point’s logical trace.

## Make the study record sufficient to reproduce and challenge

A transparent estimate should travel with its input record, not as a screenshot containing one impressive number. The following ledger identifies both the required evidence and the functional handoff; these roles do not assert an organization’s internal ownership structure.

| Record section | Required information and responsible contribution |
|---|---|
| Scientific contract | Chemistry/application contributors state geometry, units, basis, charge/spin/sector, active/inactive spaces, energy reference/offsets, target tolerance, classical validation, and why the model answers the question |
| Algorithm and workload | Algorithm contributors specify trial state, success evidence, evolution/block normalization, power construction, phase precision, all shots/trials, postselection/amplification, state-preparation scope, and allowed parallelism |
| Representation and compilation | Compiler contributors identify source revisions, Circuit representation selected, scratch lifetimes, count versus detailed-trace path, branch/reset limitations, rotation synthesis, and phase/control correctness |
| Physical and QEC assumptions | Hardware/QEC contributors provide compatible primitive errors/times, noise limitations, connectivity, code formulas/distance ranges, extraction/decoder/control assumptions, factories, routing, and buffers |
| Budgets and search | The study records total/partitioned reliability allowance, separate chemical/approximation/statistical targets, query domains, hard constraints, graph/post-processing choices, and cache/provenance conditions |
| Outputs and interpretation | Preserve status/errors, raw counts, units, selected and rejected feasible candidates, complete frontier, code/factory provenance, runtime/space partitions, omitted overhead, and sensitivity conclusions |

Retain exact chemistry and external QDK versions separately. Keep the normalized parameter dictionaries or explicitly enumerated query configuration alongside results. Record whether a plot represents a single largest iteration, one complete run, or a multi-geometry campaign. Distinguish a worst-case runtime bound from an expected runtime derived from an explicit probabilistic schedule.

Sensitivity analysis changes justified assumptions and reports their effect. Examples include a physically consistent range of measurement times/errors, stricter total failure allowance, finer rotation synthesis, alternative factory distances, or a larger active space with documented scientific benefit. Reoptimizing each scenario is usually more informative than assuming one factory count and code distance remain best everywhere. Such sensitivity scenarios are not statistical confidence intervals unless justified input distributions and an uncertainty-propagation method have been supplied. The result’s displayed error is a modeled algorithmic-error contribution, not an uncertainty bar on the assumed hardware performance.

If a configuration fails, preserve why: no compatible ISA, insufficient synthesis accuracy, inadequate distance range, too few factory possibilities, unsupported input, or resource constraints. Do not replace failure with a blank cell that readers interpret as free computation. A result must be qualified by the source and validity of its physical model; favorable independent parameter values from incompatible platforms should never be combined into a supposedly validated machine.

## Use resource estimates to improve the scientific design

The output closes a loop rather than ending the project. An expensive state-preparation method can justify exploring a cheaper state only if increased repetition is counted. A smaller active space can reduce resources while changing chemical predictions; its saved qubits do not erase the scientific approximation. Factorization can reduce representation/circuit cost while introducing approximation or normalization tradeoffs. Tighter phase resolution can sharply increase controlled evolution work, so precision should follow the target result rather than an arbitrary attractive bit count.

The official [resource-estimation introduction](https://learn.microsoft.com/en-us/azure/quantum/intro-to-resource-estimation) and [local setup overview](https://learn.microsoft.com/en-us/azure/quantum/install-run-resource-estimator) describe this layered external modeling workflow. The chemistry repository supplies application construction and adapters; external QDK supplies the estimator machinery, code/factory models, and physical instruction abstractions inspected here. Neither source can supply missing scientific success evidence or device validation by naming a model.

The final statement should therefore read like: under this explicit scientific model, complete algorithm, reliability/accuracy contract, and compatible hardware/QEC assumptions, these are the modeled feasible space/time tradeoffs within this search. It should not claim actual billing, commercial revenue, a hardware availability date, practical quantum advantage, or successful chemical prediction without the additional evidence those claims require.