# Lesson 21 — Reading energies through phase estimation

## The phase belongs to an eigenstate, not necessarily the ground state

Pauli sampling in Lesson 20 estimated the expectation of an observable on a prepared state. Quantum phase estimation, or **QPE**, instead estimates an eigenphase of a chosen unitary. If that unitary encodes a Hamiltonian spectrum, a phase can be converted into an energy. QPE does not inherently select the lowest energy, identify the correct electron sector, or make an approximate chemical model exact.

Our continuing problem is neutral singlet N₂ at 1.85 Å, reduced from the 28-spatial-orbital cc-pVDZ model to the selected active model. The ordinary Pauli route still separates the core scalar; the SOSSA route has already included it in its energy-shift metadata. A trial state supplies overlap with desired eigenstates, not a guarantee that every run returns the ground state.

This chapter separates three layers: the mathematical QPE experiment, the repository’s circuit builders, and its classical orchestration/decoding algorithms. That distinction is especially important for iterative QPE, whose current implementation repeatedly prepares the system rather than preserving one collapsed eigenstate throughout all bits. Nothing is executed here; source tests and tutorial settings are attributed evidence, not new results.

## Phase kickback in one control qubit

Suppose \(U|u\rangle=e^{2\pi i\varphi}|u\rangle\), where \(\varphi\) is a phase fraction measured in turns. Start a control in \(|+\rangle=(|0\rangle+|1\rangle)/\sqrt2\). A controlled U gives

\[
\frac{|0\rangle+e^{2\pi i\varphi}|1\rangle}{\sqrt2}\otimes|u\rangle.
\]

The system eigenstate factors out, but its eigenphase has become a relative phase of the control. This is **phase kickback**. A final H and Z measurement give probability \((1+\cos2\pi\varphi)/2\) for zero. Applying \(U^r\) changes the phase to \(r\varphi\) modulo one. Repeated powers expose different binary digits or finer phase structure, but their implementation cost is real.

Write an exactly representable phase as \(\varphi=0.b_1b_2\cdots b_m\), meaning \(\sum_{j=1}^{m}b_j2^{-j}\). The symbols MSB and LSB mean most- and least-significant bit. They describe numerical place values, not automatically physical qubit positions.

For time evolution \(U(t)=e^{-iHt}\), the fraction is

\[
\varphi=(-Et/2\pi)\bmod1.
\]

The minus sign is the convention used by the repository’s Pauli product formulas. Energies separated by \(2\pi/t\) have the same phase. These are **aliases**: physically different values indistinguishable from this one periodic signal. More phase bits refine the measured fraction but do not by themselves remove that energy ambiguity.

The product-formula container chooses a principal angle in \((-\pi,\pi]\) and returns \(-\mathrm{angle}/t\). It does not automatically search every physically possible energy branch. A known spectral bound, an energy shift, or a prior interval narrower than one alias period is needed to identify the intended branch. For example, a coefficient bound \(|E|\le\lambda\) permits \(|t|\lambda<\pi\) to keep the spectrum away from the wrap boundary. A very conservative bound can require many bits for fine energy precision. Zero evolution time cannot be used to decode energy.

## Standard QPE: Fourier analysis of controlled powers

A quantum Fourier transform, or **QFT**, changes between an integer basis and phase-pattern basis. In ideal standard QPE, an \(m\)-qubit phase register starts in a uniform superposition over \(M=2^m\) integers. Controlled powers produce

\[
\frac1{\sqrt M}\sum_{x=0}^{M-1}e^{2\pi i x\varphi}|x\rangle|u\rangle.
\]

The inverse QFT converts that phase progression into a distribution over integers \(y\). Its amplitude is

\[
a_y=\frac1M\sum_{x=0}^{M-1}e^{2\pi i x(\varphi-y/M)}.
\]

If \(M\varphi\) is an integer, the ideal distribution is concentrated there. Otherwise amplitude spreads over nearby bins with finite tails. This **spectral leakage** is finite-resolution behavior, not necessarily hardware noise. Bin spacing is \(2^{-m}\) in phase fraction and \(2\pi/(|t|2^m)\) in a local unaliased time-evolution energy scale. Bin width is not a confidence interval.

For a superposition \(\sum_k c_k|u_k\rangle\), the coherent experiment entangles the phase register with eigenstate-dependent phase distributions. Under ideal sufficient resolution, a complete shot selects an eigenstate branch with weight \(|c_k|^2\). Closely spaced phases can remain unresolved; finite-bin measurement need not project onto one unique eigenvector. The user-facing orchestration can also summarize many shots rather than retain each collapse result.

The registered orchestration method is `phase_estimation/qdk_standard`. It has `shots=3` by default. The corresponding native builder is separately registered as `qpe_circuit_builder/qdk_standard`. Its settings include `num_bits=-1`, requiring an explicit positive value, the nested unitary builder, the controlled mapper, and `measure_phase=True`.

A real configuration trap is visible in the [standard orchestration source](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/src/qdk_chemistry/algorithms/phase_estimation/standard_phase_estimation.py): its settings inherit the base `qdk_iterative` builder reference and do not replace it with the standard builder. Configure `qpe_circuit_builder=AlgorithmRef("qpe_circuit_builder","qdk_standard",...)` explicitly. Otherwise the orchestration’s builder-type check fails. Registry names at two different layers are not automatic mutual configuration.

The [native standard builder](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/src/qdk_chemistry/algorithms/phase_estimation/circuit_builder/standard_builder.py) constructs one controlled operation for each power \(2^{m-1},\ldots,1\), associates them with its phase wires, and returns a single-element circuit list. [`StandardPhaseEstimation.qs`](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/src/qdk_chemistry/utils/qsharp/src/StandardPhaseEstimation.qs) prepares the system once, Hadamards the phase register, applies the powered operations, and calls the external inverse QFT. The final factory measures the phase register and resets remaining qubits.

Do not infer final bit significance solely from a wire’s power before the inverse QFT. The Q# routine returns measurements in its wire order and the executor applies its result-string convention. The public result is interpreted as an MSB-first binary fraction. Existing [standard-QPE tests](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/tests/test_phase_estimation_standard.py) use nonpalindromic references such as phase 13/16 and expected string `1101`, preventing a casual double reversal from appearing correct only by accident. We have not reviewed the external QFT implementation or executed those tests.

The measurement-free `qsharp_op` is adjointable for coherent composition. Setting `measure_phase=False` changes the standalone factory’s phase-readout behavior, but that factory still resets its allocated register at the end. Use the composable operation, not a discarded standalone output, when a later algorithm needs coherent phase tags.

The orchestration executes the one circuit for the configured shots, selects the most frequent complete bitstring, converts it to a fraction, and returns one `QpeResult`. It does not return the whole phase histogram in that object. A count tie follows the existing dictionary order through Python `max`; this is not a physics-based branch decision. Empty surviving counts cause a failure at dominant-bin selection. A modal phase can correspond to whichever eigenstate has the largest observed weight, not necessarily the ground state.

## Iterative QPE: the actual feedback recurrence

The default orchestration method is `phase_estimation/qdk_iterative`, with `shots_per_bit=3`, default `qpe_circuit_builder/qdk_iterative`, and default sparse-state executor. The [iterative builder](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/src/qdk_chemistry/algorithms/phase_estimation/circuit_builder/iterative_builder.py) has `num_bits=-1`, `phase_correction=0.0`, and `num_iteration=-1`. A negative iteration selects all bit circuits; a nonnegative one selects that zero-based iteration. Positive bit count and an in-range iteration are required.

The first executed iteration uses \(U^{2^{m-1}}\) and resolves the **least-significant** bit of an exactly \(m\)-bit eigenphase. Later iterations proceed toward the MSB. The Q# circuit applies H to the control, applies an Rz feedback correction, applies the already-powered controlled unitary, applies H again, and measures/resets the control. The sign convention follows the actual [Q# routine](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/src/qdk_chemistry/utils/qsharp/src/IterativePhaseEstimation.qs), not an assumed generic IQPE formula.

Let \(F\) be the stored feedback angle, initially zero. The [orchestration](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/src/qdk_chemistry/algorithms/phase_estimation/iterative_phase_estimation.py) sends correction \(-F\), obtains one bit by majority vote, and updates

\[
F\leftarrow F/2+\pi b/2.
\]

After all bits, it returns phase fraction \(F/\pi\) and reverses the execution-order bit list into MSB-first order. Ties choose zero because zero wins when its count is greater than or equal to the count of one. If loss leaves both counts absent, this comparison also chooses zero; the algorithm does not separately diagnose that as a no-data bit. Noise/loss handling therefore needs care beyond reading a plausible-looking final bitstring.

For the illustrative eigenphase \(5/8=0.101\), three iterations use powers 4,2,1. The first phase is \(\pi\) modulo \(2\pi\), yielding bit 1 and \(F=\pi/2\). The next phase is \(\pi/2\), canceled by \(-F\), yielding bit 0 and \(F=\pi/4\). The final relative phase is \(5\pi/4-\pi/4=\pi\), yielding bit 1 and final \(F=5\pi/8\). The numerical bitstring is `101`. This is hand arithmetic in the ideal eigenstate case, not a simulator output.

Building all iteration circuits with one fixed `phase_correction` is useful for inspection or component costing. It does not know the feedback produced by a future run. The orchestration updates correction and iteration settings between measurements and calls the builder’s implementation for the selected iteration; it does not execute a prebuilt all-zero-feedback list as if it were adaptive QPE.

## Fresh preparation changes the success story

Textbook iterative schemes can recycle a phase ancilla while retaining the same physical system state across bits. The current QDK Chemistry bit circuit does something more specific: it allocates a fresh register, runs the supplied state preparation, measures the phase control, and resets the system/ancillas. The executor repeats that circuit for each shot. The Python algorithm then builds and runs the next bit’s fresh circuit.

For a true eigenstate this is an efficient-width way to gather bit evidence. For a trial superposition, different shots and bits can encounter different spectral components. A concatenated result need not have the same distribution as a complete coherent standard-QPE shot.

Consider a toy trial with equal weights on exact eigenphases 0 and 3/4, using two bits and one shot per bit. Coherent standard QPE would ideally return `00` or `11`. In the fresh-state implementation, the first, least-significant bit can be zero from the phase-zero component. With zero feedback, a fresh phase-3/4 component at the next iteration gives a random control bit because its relative phase is \(3\pi/2\). A one outcome then creates `10`, corresponding to 1/2—neither original phase. This hybrid outcome is possible without noise or Hamiltonian approximation. It is a consequence of repreparation plus feedback built from potentially different components.

More shots per bit can reduce ordinary bit-sampling error under suitable single-eigenstate conditions, but do not turn that mixture protocol into the coherent-collapse experiment. For an odd three-shot majority and independent wrong-bit probability \(p\), the wrong majority probability is \(3p^2-2p^3\). In a real adaptive sequence, the bit distribution depends on prior feedback and the state mixture; treating every bit as having the same independent error probability is another assumption.

Thus neither “ground fidelity F” nor “use about 1/F complete trials” is a blanket success formula for this implementation. Repeated complete IQPE runs, examining their full distribution, and validating the protocol on an appropriate model are meaningful; interpreting every bitstring as an eigenvalue sampled with ground overlap weight is not generally justified. The [iterative tests](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/tests/test_phase_estimation_iterative.py) include carefully prepared eigenstate benchmarks and specific noncommuting cases, not a theorem for every trial mixture.

## Shared nested settings and the unitary being estimated

The [common circuit-builder base](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/src/qdk_chemistry/algorithms/phase_estimation/circuit_builder/base.py) defaults to `hamiltonian_unitary_builder/trotter` and `controlled_circuit_mapper/pauli_sequence`. It explicitly sets the requested power on each newly resolved unitary builder and forces the mapper’s control index to zero. Those are parent overrides. Changing a nested reference’s power/control setting does not necessarily override the enclosing QPE schedule.

The standard and iterative builders must estimate powers of a consistent unitary. With deterministic `power_strategy="repeat"`, the powers repeat the same base approximation when the builder inputs agree. With `rescale`, the builder recompiles at a longer simulated time; a finite product formula need not equal a power of the previously built approximation. The enclosing phase-estimation interpretation then needs an error analysis, not just a matching nominal time.

Random builders add another trap. Fixed-seed qDRIFT with repeat may reproducibly construct the same sampled base product and repeat it, but its averaged-channel bound still does not certify the coherent eigenphases of that one product. With the default nondeterministic seed, separately rebuilt bit/power circuits can instead use different sampled base products. They are not automatically powers of one common U at all. Partial randomization has related finite splitting and consistency obligations.

The common native builder derives extra unitary ancillas from the **unitary container’s** structural width, not by inspecting every generated controlled circuit. A coefficient loader that adds alias-sampling workspace beyond that structural LCU width is not automatically accommodated by standard/iterative composition. The unary builder has a different, explicit mapper-width contract. Likewise the state-preparation width check only rejects a known width greater than the passed register; it does not prove exact equality, encoding identity, spin sector, or orbital alignment.

The controlled-Pauli standalone high-index allocation limit and CSWAP vacuum-preserving ordered-block requirement from Lesson 17 still apply. Native QPE uses compact control/system layouts. A tapered operator needs a correspondingly transformed trial state. Common state-preparation or mapper class ancestry is not a universal compatibility certificate.

## Unary QPE: a signed query schedule, not a unary-sized phase register

The registered orchestration and builder names are both `qdk_unary` in their respective families. The orchestration defaults to `shots=3`, `resolve_positive_branch=False`, and its matching unary builder. The [builder](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/src/qdk_chemistry/algorithms/phase_estimation/circuit_builder/unary_phase_estimation_builder.py) defaults to an LCU unitary builder with `quantum_walk=True`, an uncontrolled `prepare_select_prepare` circuit mapper, `num_queries=-1`, and `compute_capacity=-1`. An explicit positive query count is required.

Unary here refers to sequential one-hot **address iteration**, not allocating one permanent phase qubit for every query. With \(p\) block queries, there are \(p+1\) reflection slots, addressed by

\[
m=\lceil\log_2(p+1)\rceil
\]

phase qubits. The implementation computes this as the positive query count’s bit length. For example, seven queries need three phase bits, while eight queries need four. Queries need not be a power of two. A conflicting positive `num_bits` setting is warned about and ignored; `num_queries` determines the register.

The phase register is prepared in a zero-padded **cosine-window** state, implemented with amplitudes proportional to

\[
\sin\left(\frac{\pi(j+1)}{p+2}\right),\qquad j=0,\ldots,p.
\]

This smooth weighting suppresses spectral tails compared with a rectangular uniform window. Padding outside the valid slot range must remain zero because invalid address values can route to the wrong slot through the unary-iteration helper. It is not harmless unused register space.

The schedule uses a self-inverse block encoding B and an outer reflection R. It applies p blocks and omits exactly the reflection selected by the phase address. On slot j, the resulting action is the signed walk power \(W^{p-2j}\), with \(W=RB\). The [Q# schedule](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/src/qdk_chemistry/utils/qsharp/src/UnaryPhaseEstimation.qs) controls the reflection/slot behavior rather than requiring a generic controlled version of B. That is why an Adj-only SOSSA block can fit this protocol while it cannot be passed to the ordinary controlled-QPE mapper.

The address-dependent phase changes by twice the walk eigenphase between slots. After inverse QFT, the measured fraction is related to the walk phase by \(y=\pm2\varphi\bmod1\). This doubled-phase alias is central to the decoder; it is not a minor bit-order correction.

## Unary decoding needs prior branch information

The [unary orchestration](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/src/qdk_chemistry/algorithms/phase_estimation/unary_phase_estimation.py) folds conjugate bins and combines their counts. For a measured y it computes \(f=\min(y,(-y)\bmod1)/2\). `resolve_positive_branch=True` chooses canonical walk fraction f; false chooses \(1/2-f\). It then selects the most populated canonical bin, breaking ties toward the smaller canonical fraction, and reports both mirror energy candidates.

For ordinary LCU, \(E=\lambda\cos(2\pi\varphi)\). The two candidates are E and −E. Default false chooses the nonpositive **signal** branch; it cannot infer from the data alone that the desired state actually belongs there. The implementation’s default is not a universal ground-state oracle.

For SOSSA, \(E=\Lambda(1+\cos2\pi\varphi)+E_s\). The ambiguity is instead between energies reflected around \(E_s+\Lambda\): \(E\) and \(2(E_s+\Lambda)-E\). The flag chooses the lower or upper half of that shifted signal band, not necessarily negative or positive total molecular energy. Use physically justified reference/bound information to choose a branch, and account for uncertainty near its dividing point.

Because conjugate bins are folded, `phase_fraction` and `bitstring_msb_first` in the unary result describe the chosen representative bin; they need not be the literal most frequent original shot string. `canonical_phase_fraction` is the decoded walk phase, and `raw_energy` is computed from that canonical phase. The [unary tests](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/tests/test_phase_estimation_unary.py) explicitly check merged conjugate counts, deterministic ties, and the doubled-phase relationship.

Increasing p sharpens the finite window but cannot recover the discarded sign/half-band information without extra knowledge. A suggested query-scaling formula in the settings description, involving normalization and desired energy error, is planning guidance; the builder does not automatically solve a complete chemical/phase/statistical budget from an epsilon setting.

## Unary representation, ancillas, and execution limits

The builder accepts an LCU **walk** container or a SOSSA block-encoding container; a bare LCU container without the walk interpretation is rejected. For LCU it extracts/rebuilds a unit-power block and warns when supplied powers are ignored. SOSSA must already have unit power. The mapper must return a Q# callable and declared width, with a nonempty ancillary region for the outer reflection.

Shared phase-gradient metadata is handled explicitly here. State preparation and the block may each request a trailing gradient; if both do, their widths must agree. The builder initializes it once around the schedule, appends it only to consumers expecting it, and excludes it from zero-ancilla reflection. Inner scratch allocated inside B is still additional to the declared argument register. Physical QEC overhead is not represented by these widths.

`compute_capacity=-1` keeps logical qubits in compute. A positive value requests the QDK resource-estimation memory/compute placement model with least-recently-used scheduling, and the Python builder warns that placement counts may be inaccurate. Zero or values below −1 raise. This setting is not proof that a physical quantum memory device or a particular hardware deployment exists.

There is also an explicit current backend limit: unary orchestration permits only the executor named `qdk_sparse_state_simulator`. Recursive Q# unary iteration threads a callable through the recursion and is not lowered to QIR by the current path. The full-state QIR executor and Qiskit executors are therefore rejected by name, even if other parts of the same workflow can use them. Construction and supported logical-count interpretation are different capabilities from exporting/deploying a general QIR program. The source tests exercise particular interpreter and counting paths; no such execution was performed here.

## Optional Qiskit circuit builders

The optional plugin supplies `qpe_circuit_builder/qiskit_standard` and `qiskit_iterative`, while the orchestration remains the corresponding `phase_estimation/qdk_standard` or `qdk_iterative` algorithm. Their [implementation](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/src/qdk_chemistry/plugins/qiskit/phase_estimation_circuit_builder.py) builds Qiskit circuits and exports QASM rather than native composable Q# operations.

Qiskit standard QPE probes the compiled controlled circuit for workspace width, prepares the phase register with H gates, applies increasing powers on its phase wires, and calls external `synth_qft_full(..., inverse=True)`. Its `qft_do_swaps` setting defaults true. Turning swaps off changes output ordering and is not compensated by a new generic decoder in this orchestration; preserve the tested setting unless you explicitly adapt and validate the convention.

The Qiskit standard path checks that state-preparation width equals the system width. The iterative plugin composes prepared and controlled circuits as gates, which requires appropriate gate-convertible, effectively unitary subcircuits; arbitrary adaptive measurement-bearing preparations cannot simply become a gate. Extra decomposition ancillas and QIR-to-Qiskit control-flow restrictions still apply. These are optional external synthesis/conversion contributions, not alternative physical hardware guarantees.

## What QpeResult does and does not store

[`QpeResult`](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/python/src/qdk_chemistry/data/qpe_result.py) stores `method`, phase fraction and angle, canonical phase fraction and angle, `raw_energy`, candidate `branching`, optional `resolved_energy`, optional MSB-first bits/string, and optional metadata. The factory normalizes phase fractions modulo one and calls the supplied container decoder on the canonical fraction.

For standard and iterative orchestration, canonical phase equals the measured/assembled fraction, branching defaults to the single decoded energy, and resolved energy remains unset. Those methods do not enumerate all time-evolution aliases using a chemical energy interval. For unary, both mirror candidates are supplied and the branch flag sets resolved energy. The word “raw” does not mean untouched data in every method: it is still a decoder output from the canonical phase.

The current orchestrators do not populate complete shot histograms, per-bit counts, all generated circuits, state-overlap diagnostics, or confidence intervals into this result. Optional metadata is merely available storage. If those details are needed for auditing, retain execution data or a surrounding workflow record explicitly. The main tutorial does exactly that for repeated complete IQPE runs.

JSON/HDF5 preserve these result fields, with metadata serialized as JSON where appropriate. They do not serialize the decoder callable or prove which Hamiltonian, orbital model, approximation settings, core shift, and trial state produced a number. Keep that provenance with the result. Unknown/malformed metadata is not a recovered physical interpretation.

For ordinary Pauli time evolution or ordinary LCU, add the separately omitted molecular core scalar once to the decoded active energy. For SOSSA, the decoder already adds the shift containing that scalar. Adding it again would double count it. Statistical spread across runs, finite-bit resolution, approximation errors, and chemical model errors must be reported separately rather than bundled into an unexplained plus/minus number.

## The nitrogen tutorial is deliberately reference-guided

The main [IQPE tutorial](https://github.com/microsoft/qdk-chemistry/blob/97a3ff4f/docs/source/_static/examples/python/tutorial_run_iqpe.py) reuses the 1.85 Å mapping/trial-state workflow. Its defaults are six phase bits, three shots per bit, twenty complete runs, and seeds starting at 42. It uses first-order Trotter with one base division and repeated powers, and the native iterative orchestrator with a full-state simulator.

Its time-selection routine first considers \(t_{\mathrm{bound}}=\pi/\lambda\), then uses a **known selected-space reference energy** to choose a finite phase-grid point and adjust time so that grid energy sits one milliHartree above the reference. This is explicitly circular validation input. It demonstrates implementation and finite-grid behavior; it is not a production strategy for discovering an unknown ground energy or proof that a generic six-bit calculation is accurate to one milliHartree.

The surrounding tutorial repeats complete runs with consecutive seeds, stores each bitstring/energy/error/runtime record, and requires a unique modal complete bitstring when summarizing. That outer aggregation is not a hidden `num_trials` setting of the base IQPE algorithm. The separate notebook at 1.270025 Å remains a different molecular baseline; its resource/circuit results must not be substituted into this story.

For the tutorial’s declared m=6 and three-shot bit votes, each complete run performs 18 state preparations/bit shots and \(3(2^6-1)=189\) base-unitary applications when counting repeated powers. Twenty complete runs therefore imply 360 bit shots and 3780 base-unitary applications before expanding each base step into Pauli gates. These are arithmetic workload counts from source settings, not observations from an executed session. The largest one-bit circuit alone is not that complete workload.

## A bounded configuration pattern

This source-backed pattern is unexecuted. Assume `qubit_hamiltonian` is a validated nonzero Hermitian Pauli operator and `trial_circuit` is a compatible clean preparation in the same untapered active encoding:

```python
from math import pi
from qdk_chemistry.algorithms import create
from qdk_chemistry.data import AlgorithmRef

coefficient_bound = qubit_hamiltonian.schatten_norm
base_time = pi / (2 * coefficient_bound)
qpe = create(
    "phase_estimation", "qdk_iterative",
    shots_per_bit=3,
    qpe_circuit_builder=AlgorithmRef(
        "qpe_circuit_builder", "qdk_iterative",
        num_bits=6,
        unitary_builder=AlgorithmRef(
            "hamiltonian_unitary_builder", "trotter",
            time=base_time, order=2, num_divisions=4,
            power_strategy="repeat",
        ),
        controlled_circuit_mapper=AlgorithmRef("controlled_circuit_mapper", "pauli_sequence"),
    ),
    circuit_executor=AlgorithmRef("circuit_executor", "qdk_sparse_state_simulator", seed=42),
)
# In an authorized execution environment: result = qpe.run(trial_circuit, qubit_hamiltonian)
```

The bound-based time avoids relying on the known nitrogen energy to select a phase-grid target. It does not certify a six-bit energy tolerance, four-division Trotter accuracy, or adequate trial-state overlap. Those choices are explicit illustrative limits that must be refined for a real accuracy objective.

## Comparing variants without hiding the work

Standard QPE uses m phase qubits and one state preparation per complete shot, but all controlled powers must be coherent within that shot. Iterative QPE uses one phase control per bit circuit, yet repeats preparation and uses classical feedback; the longest controlled power remains \(2^{m-1}\). Fewer phase qubits does not mean shallow evolution or near-term noise tolerance. Unary uses a logarithmic phase-address register plus an arbitrary-length block/reflection schedule, and a specialized doubled-phase decoder with prior branch selection.

All variants also need system qubits, representation-specific ancillas, internal scratch, and later physical error correction. Count every powered operation, every PREPARE/SELECT/reflection or Pauli step, rotation precision, state preparation, measurement/reset, shot, complete trial, and classical build/post-processing stage. Randomization consistency and shared-ancilla ownership belong in that accounting.

QPE is therefore a carefully defined spectral experiment, not an energy-reading instruction that ignores state quality and implementation details. The next lesson examines two related tools: overlap-sensitive Hadamard tests and coherent success amplification, including why a finite-resolution phase filter is not a free exact-ground-state oracle.