17Turning mathematical operations into circuits

A mathematical operation becomes a circuit through several explicit contracts.

~27 min reading14 sections3 knowledge checks

Progress is saved only in this browser. No account or tracking.

Compile the meaning of a Pauli rotation

Concept plate 17
BB†RzBasis change, parity, rotation, reverseSchematic two-mode Pauli exponential
A schematic Pauli exponential changes basis, computes a parity, rotates, and reverses those steps. Adding controls requires consistent phase handling; a global phase can become a relative phase.
  • 1Change to a useful basis
  • 2Compute parity and rotate
  • 3Uncompute and restore the basis
Original technical lesson · 1 contextual figure addedDownload original MarkdownJump to the interactive model

One workflow, several different translations

The nitrogen story now contains several objects that must not be confused. A chemical Hamiltonian encodes the selected orbital/electron model. A qubit operator represents that Hamiltonian in an encoding such as blocked Jordan–Wigner. A trial-state preparation circuit loads an approximation to a desired eigenstate. None of these, by itself, specifies the full quantum algorithm.

A further decision is needed: which mathematical operation should a quantum circuit implement? Time evolution, a block encoding, and a quantum walk are different operations derived from the same Hamiltonian. The repository represents their plans with UnitaryRepresentation, then uses a circuit mapper to produce a Circuit. An enclosing algorithm composes state preparation, controls, those operations, measurement, and possibly feedback. Compilation produces an interchange representation for execution or resource counting.

Thus the chain is chemical model → qubit operator → unitary plan → circuit subroutine → algorithm composition → compiled program → a chosen consumer. A fermion-to-qubit mapper does not synthesize all the gates, and a circuit mapper does not choose a chemically accurate active space. Maintaining these separate responsibilities makes numerical error, representation mistakes, and missing external capabilities easier to locate.

This chapter teaches the concrete circuit contracts at revision 97a3ff4f. Product-formula error analysis and block-encoding spectral derivations follow in Lessons 18–19; provider execution and physical resource estimation come later. We are reading source and tests, not compiling or executing any circuit here.

What a unitary plan contains

A unitary matrix UU satisfies U†U=IU^\dagger U=I, so its action preserves state norm and is reversible. An operator exponential is defined by the matrix power series, just as an ordinary exponential is defined for a scalar. For Hermitian HH,

U(t)=e−iHtU(t)=e^{-iHt}

is unitary. In atomic units, time tt and energy combine to give a dimensionless phase. A circuit generally implements an approximation to this exponential rather than calculating a dense exponential matrix first.

UnitaryRepresentation wraps a concrete container. Its supported serialization dispatch recognizes pauli_product_formula, lcu, lcu_walk, and sossa_block_encoding. Abstract UnitaryContainer, BlockEncodingContainer, and QuantumWalkContainer interfaces are not additional concrete executable methods.

PauliProductFormulaContainer stores an ordered step_terms list, positive integer step_reps, problem width num_qubits, and scale. Each ExponentiatedPauliTerm holds a dictionary from qubit index to Pauli letter and an angle θ\theta, meaning the factor e−iθPe^{-i\theta P}. It is not an instruction to apply Rz(θ)R_z(\theta). scale is evolution-time metadata for converting a measured phase back to an energy; the circuit mapper consumes the already computed term angles and does not multiply them by scale again.

The constructor rejects noninteger, boolean, zero, and negative repetition counts. Other payloads still need a valid producer: this is not a complete physical validation of every Pauli index, time, or coefficient. Reordering terms can change the approximate unitary when they do not commute. The current reorder_terms helper also reconstructs the container without forwarding scale, restoring its default 1.0; do not lose phase-to-energy metadata while experimenting with ordering. combine checks equal width and compatible scale, expands repetitions, and fuses adjacent identical Pauli factors, not arbitrary nonadjacent terms.

An LCUContainer stores precomputed PREPARE data and a Select description. LCU means linear combination of unitaries. PREPARE is an ancillary index-state preparation, not the chemical trial-state preparation. SELECT lists operations activated by integer index values, with associated ± signs. ControlledOperation.ctrl_state is that index value; its operation is a Pauli label with qubit 0 on the right. power describes repeated use of the block operation.

An LCUWalkContainer wraps an LCU block encoding and an ancilla reflection to define a walk step. Its scale is a normalization used for the walk’s nonlinear phase-to-energy relation, not the evolution time of a product formula. The container’s eigenvalue_from_phase methods therefore differ between families. Applying the product-formula decoder to a walk phase is not an interchangeable implementation choice.

SOSSABlockEncodingContainer carries outer and conditional inner PREPARE data, rotation tables, structural register layout, normalization, and scalar metadata for sum-of-squares spectral amplification, abbreviated SOSSA. It represents a specialized block encoding, not a precomposed generic controlled walk. The distinction determines which mapper and phase-estimation builder can consume it.

Deriving a Pauli exponential as gates

For a Pauli word PP, P2=IP^2=I, so its exponential simplifies to

e−iθP=cos⁡θ I−isin⁡θ P.e^{-i\theta P}=\cos\theta\,I-i\sin\theta\,P.

We can implement this without constructing that matrix. First rotate every nonidentity factor into the Z basis. For X use H. For Y apply S†S^\dagger followed by H. For Z do nothing. Let the combined basis change be BB, so BPB†BPB^\dagger is a product of Z factors.

Next accumulate the parity of the participating qubits into one of them using a CNOT ladder. For illustrative P=X0Y2Z3P=X_0Y_2Z_3, after the basis changes use CNOT(0,2), then CNOT(2,3). The last qubit holds the XOR of the participating bits. A rotation Rz(2θ)=e−iθZR_z(2\theta)=e^{-i\theta Z} on it applies the correct phase to each even/odd parity branch. Undo the CNOT ladder and then undo the basis changes.

If VV denotes that parity network, the whole expression is

U=B†V†Rz(2θ)VB.U=B^\dagger V^\dagger R_z(2\theta)V B.

Operators on the right act first. With ww nonidentity factors, the straightforward ladder uses 2(w−1)2(w-1) CNOTs plus local basis changes and one rotation. This is a logical construction with unrestricted connectivity, not a hardware layout or an exact gate count promised for every compiled Exp. Routing, cancellation across adjacent factors, rotation synthesis, and target conventions can change the realized count.

The repository’s native Q# mapper does not explicitly write that ladder in Python. PauliExp.qs calls the external primitive as Exp(paulis, -coeff, systems). The matrix test compares the result with e−iθPe^{-i\theta P}. The minus sign and factor-of-two distinction from conventional axis rotations are therefore source-backed. The standard-library decomposition itself remains an external QDK compiler/runtime boundary.

Terms execute in list order. If the list is X then Z, the state is acted on by X’s exponential first, so the resulting matrix is e−iθZZe−iθXXe^{-i\theta_Z Z}e^{-i\theta_X X}. The test explicitly constructs matrices in that order. Changing notation to a product symbol must not silently reverse it.

An empty Pauli map can represent identity. Its factor e−iθIe^{-i\theta I} is a global phase for an uncontrolled state evolution, but not necessarily disposable in the enclosing algorithm. The ordinary chemical qubit mapper excluded the core-energy scalar before this stage. Keep that scalar in the classical energy bookkeeping, or deliberately include it with the appropriate phase convention; do not both add it to the operator and add it again to the reported energy.

Direct control: why phase bookkeeping suddenly matters

A controlled operation is

C(U)=∣0⟩⟨0∣⊗I+∣1⟩⟨1∣⊗U.C(U)=|0\rangle\langle0|\otimes I+|1\rangle\langle1|\otimes U.

The control-zero branch is unchanged; the control-one branch receives UU. Replacing UU by eiϕUe^{i\phi}U changes the phase between those branches, even though it changed only a global phase in an isolated uncontrolled use. This is why controlled Hamiltonian operations need stricter equivalence checks than statevector comparisons that fit away any overall phase.

For an ordered product of factors, controlling every factor with the same control gives the controlled ordered product exactly. A useful construction can run basis changes and parity computation unconditionally, control only the central rotation, and then undo the basis/parity work. On the disabled branch those surrounding operations cancel. The repository uses Controlled Exp rather than manually asserting that every gate name should gain another control.

The algorithm names are circuit_mapper/pauli_sequence and controlled_circuit_mapper/pauli_sequence; both are defaults within their respective families. The ordinary mapper accepts only a Pauli product-formula container, creates the per-term Q# Pauli arrays, repeats the step, and declares the problem-register width. It has no additional synthesis-tolerance settings of its own.

The controlled mapper supports one control. The common settings are control_indices=[0] and target_indices=[], where an empty target list auto-fills the lowest noncontrol indices to the required width. Explicit targets must have the right length, contain no duplicates, and not overlap controls. Duplicate controls are rejected. Supply sensible nonnegative indices; these checks are not a full proof that every possible standalone layout is supported.

There is a concrete layout limit: the direct controlled-Pauli standalone Q# factory allocates Length(systems)+1, not one plus the maximum requested index. Compact layouts such as control 0 and targets 1,2 work. A sparse layout such as control 2 and targets 3,4 can be accepted when constructing the Circuit yet exceed that factory’s allocation when compiled. The existing explicit-layout test checks construction, not compilation of that sparse case. The composable callable instead receives a control qubit and a target array directly. Do not treat those two interfaces as identical allocation promises.

Controlled mapper results currently do not declare num_qubits or encoding in their returned Circuit, unlike the ordinary Pauli mapper’s known width. The unitary plan and enclosing builder retain part of the layout knowledge. Source tests of matrix action and source tests that merely return a Circuit establish different things.

A small complete construction pattern

This unexecuted example uses a two-qubit toy operation rather than constructing or expanding the nitrogen molecule again:

from qdk_chemistry.algorithms import create
from qdk_chemistry.data.unitary_representation.base import UnitaryRepresentation
from qdk_chemistry.data.unitary_representation.containers.pauli_product_formula import (
    ExponentiatedPauliTerm,
    PauliProductFormulaContainer,
)

plan = UnitaryRepresentation(
    PauliProductFormulaContainer(
        step_terms=[ExponentiatedPauliTerm(pauli_term={0: "X", 1: "Z"}, angle=0.2)],
        step_reps=1,
        num_qubits=2,
        scale=1.0,
    )
)
circuit = create("circuit_mapper", "pauli_sequence").run(plan)
controlled = create(
    "controlled_circuit_mapper", "pauli_sequence", control_indices=[0]
).run(plan)

The first circuit implements e−0.2iX0Z1e^{-0.2iX_0Z_1}; the second embeds the same logical two-qubit operation on target positions 1 and 2 of a compact three-qubit standalone layout. It does not estimate an energy. scale=1.0 is explicit decoding metadata, not a second multiplier applied to the angle. Both calls require the installed QDK runtime to construct their Q# callables; neither has been run here.

An actual chemical time-evolution builder would choose the term list and step repetitions from the Hamiltonian and its approximation settings. The repository usage example shows that division of responsibility. Its separate hydrogen geometry is not our 1.85 Å nitrogen baseline.

Controlling by swapping with a vacuum register

controlled_circuit_mapper/cswap_pauli_sequence offers a different cost tradeoff. Rather than controlling every exponential, it adds a system-sized scratch register initialized to ∣0⋯0⟩|0\cdots0\rangle. Controlled SWAP gates exchange the system with this register when the control is one. The uncontrolled repeated evolution acts on the scratch register, and the swaps are undone. For nn target qubits this uses an additional nn-qubit register and two layers of nn controlled swaps. The repetition loop is inside the sandwich, so those two swap layers cover the whole repeated evolution.

This only works coherently when the all-zero register is an eigenstate of the actual ordered product:

U∣0⋯0⟩=eiϕ0∣0⋯0⟩.U|0\cdots0\rangle=e^{i\phi_0}|0\cdots0\rangle.

Without a phase correction, control zero acquires eiϕ0e^{i\phi_0} while control one receives UU. The Q# implementation applies R1(vacuumPhase, control), where R1(ϕ)=diag⁡(1,eiϕ)R_1(\phi)=\operatorname{diag}(1,e^{i\phi}), making the result eiϕ0C(U)e^{i\phi_0}C(U) on the clean-scratch subspace. It skips this correction only below its small 1e-12 phase threshold.

Particle-number conservation of the exact fermionic Hamiltonian is not enough. A split product formula can leak the computational vacuum if cancellation partners are separated by noncommuting factors. The Python validator groups contiguous mutually commuting strings, tracks off-diagonal amplitudes by the bit pattern they flip, and checks that those contributions cancel. It uses inYi^{n_Y} for the phase of a Pauli string acting on zero, preserving Y signs. Diagonal I/Z terms contribute the known phase −∑jθj-\sum_j\theta_j.

The setting vacuum_preservation_tolerance=1e-9 is a total budget over flipped supports and all repetitions; the per-step allowance divides by step_reps. It is not an instruction to throw away each small intermediate coefficient separately. This is a sufficient structured test, not an arbitrary dense-matrix proof accepting every unitary that happens to fix zero.

A contiguous equal-weight XX/YY pair cancels its action on the vacuum, whereas interleaving a noncommuting Z term can invalidate that argument. The vacuum_annihilating term grouper is the source-backed way to arrange suitable Hamiltonian terms before a compatible product-formula builder. The mapper validates rather than blindly trusting the grouping name. A tapered encoding’s all-zero state need not mean fermionic vacuum, so tapering can invalidate the shortcut even for an originally number-conserving problem.

The CSWAP tests check clean-scratch matrix blocks, phase correction, repetitions, valid cancellations, and rejected leakage. Its standalone factory sizes from the maximum requested index, unlike the direct controlled-Pauli factory. Its callable advertises Adj, not a general further-controlled capability. This method can reduce controlled-rotation overhead at the expense of width and swaps; neither resource is universally cheaper.

PREPARE–SELECT–PREPARE-adjoint

The next native family is prepare_select_prepare, available under both circuit_mapper and controlled_circuit_mapper. Its accepted inputs are LCU containers or LCU-walk containers, not Pauli product formulas and not arbitrary subclasses with a promising name.

Let AA denote PREPARE and SS denote SELECT. The block circuit is

B=A†SA.B=A^\dagger S A.

Chronologically it prepares the coefficient index, applies the indexed operation, then unprepares. It acts on the system and all the ancillary qubits needed by the coefficient loader. The Hamiltonian appears in a designated matrix block, rather than the complete circuit simply being HH, which is generally not unitary. Lesson 19 will explain why the normalization and selected block reproduce the desired operator.

The PSP mapper constructs SELECT directly from the container’s control states, Pauli strings, and sign phases. Its prepare setting defaults to AlgorithmRef("state_prep","dense_pure_state"). It invokes the child on the container’s coefficient wavefunction, requires a Q# callable and a positive declared width, and ensures that width covers the SELECT index. A loader such as alias sampling can need more ancillary qubits than its index width, so the mapper uses the child’s actual declared width rather than just a logarithm of term count.

This path rejects a PREPARE circuit requesting a caller-owned phase-gradient suffix: the PSP mapper does not supply that sharing contract. Use a self-contained compatible loader. The special no-PREPARE branch uses a no-op with zero index width for an appropriate singleton operation. It is not free loading of a nontrivial distribution. Qiskit-only or otherwise noncomposable PREPARE circuits cannot be embedded merely because they belong to the same family.

Select.qs applies a Pauli word conditioned on an integer index and explicitly implements the sign phase of a negative coefficient. In the zero-index-qubit case it uses an identity rotation to retain a minus sign as global phase, so the sign survives a later outer control. This is a concrete example of why identity phases cannot always be optimized away.

The PrepSelPrep.qs controlled specialization runs PREPARE and its adjoint unconditionally and controls only SELECT. On control zero, A†A=IA^\dagger A=I; on control one, the desired block circuit remains. This is valid even though a naive “control every gate” construction would cost more. It relies on the stated inverse and workspace contracts.

For an LCU walk, the mapper then applies the reflection R=2∣0⟩⟨0∣−IR=2|0\rangle\langle0|-I on the whole block-ancilla register and produces W=RBW=RB. The reflection includes coefficient-loader workspace, not just SELECT’s index bits. Its Q# implementation has explicit global/sign corrections, and a controlled walk also controls the reflection. The controlled PSP tests examine both the normalized Hamiltonian block and the expected walk phases.

The current controlled PSP wrapper requires exactly one control at index 0. Its layout is fixed to control followed by system and block ancillas; it does not honor arbitrary inherited target_indices as a remapping facility. It repeats according to the top-level container power. Do not independently nest arbitrary powers in the wrapped block and assume they all multiply: these mappers rebuild their step from PREPARE/SELECT data. Use builder-produced consistent plans.

SOSSA wiring is a separate contract

circuit_mapper/sossa accepts only SOSSABlockEncodingContainer with power==1. It produces the uncontrolled block B=U†RefBUB=U^\dagger\mathrm{Ref}_B U, not a complete controlled quantum walk. No registered controlled mapper in this batch accepts that container. The current intended outer consumer is the unary phase-estimation builder, which supplies its own reflection schedule. Calling the ordinary pauli_sequence default on SOSSA data is an error, not an automatic fallback.

The mapper settings are outer_prepare_algorithm=AlgorithmRef("state_prep","alias_sampling"), inner_prepare_algorithm="controlled_alias_sampling", select_algorithm="qrom_phase_gradient", rotation_bit_precision=10, and coefficient_bit_precision=10. The inner alternative is direct; the SELECT alternative is direct. Precision settings expose 1–30 bits. These choices trade table loading, arithmetic, rotations, and scratch, not the active electron count.

The outer child is configured by available settings: the parent sets its alias precision and disables internal gradient allocation when the child offers those controls. This is an example of explicit parent override of nested settings, not a guarantee that a supplied child setting always wins. The child must return a Q# callable and declared width.

The mapper handles outer index/workspace, inner index/comparison pieces, sign and generator information, two spin-selection ancillas, and an optional trailing shared gradient. “Free-rider” data here means classical generator metadata loaded alongside the index, not costless chemistry. A reserved free-rider region without its table is rejected. If outer PREPARE and SELECT both use the shared gradient, their widths must agree.

The Q# block implementation allocates some inner lookup/free-rider scratch internally and uncomputes it. The public declared width includes the system, retained outer/inner reflection registers, spin ancillas, and gradient suffix, but not all such scratch. The suffix is excluded from zero-register reflections because it holds the nonzero phase-gradient state. The callable advertises Adj; the standalone factory initializes its own gradient and resets registers at the end. That standalone exported program is not simply a pure unitary matrix on the callable’s argument register.

Existing SOSSA tests cover algorithm combinations, signed/general-angle blocks, multiple ranks/copies, and declared/shared register sizes. Their full-block fixtures test more than matching a state up to global phase. None of those tests was executed here, and they are not physical device benchmarks.

Circuit is a bundle of capabilities, not a universal conversion promise

Circuit can hold OpenQASM text, QIR, a native QDK Q# circuit object, or Q# factory data, plus an optional composable qsharp_op. The constructor needs at least one of the first four representations. A callable alone is not sufficient. The qsharp parameter denotes a circuit representation object; it is not an arbitrary source-code string to be parsed automatically.

QsharpFactoryData(program, parameter) stores a callable entry point and its classical arguments. The implementation invokes program with *parameter.values(): dictionary insertion order is positional argument order. Key names are descriptive and do not provide keyword binding at that call boundary. Factory dictionaries must therefore be built in the exact signature order. The Pauli mapper’s descriptive dictionary names differing from the Q# parameter names work because their order agrees.

A composable operation acts on qubits supplied by its caller. A factory usually allocates a standalone register and applies the operation. For controlled factories that fresh control begins at zero; a separate experiment must prepare it appropriately to observe the enabled branch. A circuit diagram is yet another view. It does not replace the original callable or turn a diagnostic rendering into a composable state-preparation routine.

num_qubits is declared argument-register width when known. It is optional, and internal scratch is excluded. CircuitMetadata.num_phase_gradient_ancillas defaults to zero, must be nonnegative, requires a known width if nonzero, and cannot exceed it. The field states a sharing expectation; it neither initializes the gradient nor expresses a physical-qubit requirement. Constructor validation does not parse all supplied representations and prove their widths or semantics agree.

The object resists normal attribute reassignment, but lazy conversions cache internal artifacts. A cached Qiskit circuit is the same mutable object returned on subsequent calls; make a copy before modifying it for a separate experiment. “Immutable container” does not guarantee deep immutability of every returned third-party object.

The exact conversion paths

get_qir() first returns existing QIR. Otherwise it compiles a Q# factory in its owning context and caches that result. With only QASM, it invokes the external OpenQASM compiler using OutputSemantics.OpenQasm and TargetProfile.Base; this fallback is not a request to use whatever adaptive profile happens to be active elsewhere. That QASM-only compilation result is not cached in the same branch.

get_qasm() returns stored QASM first. If none exists but QIR or a factory is available, it goes through QIR-to-Qiskit conversion and Qiskit’s OpenQASM 3 exporter. It requires the optional conversion dependencies and does not store the returned text as a new canonical source. A Q# circuit object by itself is not sufficient for that route.

get_qsharp_circuit(prune_classical_qubits=False) returns a stored native circuit object, otherwise asks the factory’s owning context for a circuit view, otherwise uses the external OpenQASM circuit converter. It has no direct QIR-only branch. Pruning applies to the factory-derived view; it is not a promise to delete those qubits from a semantically equivalent physical computation.

A representation wrapper is not an all-to-all converter#

Supplementary · Conceptual diagram

Request QIR
  1. Return stored QIR, if present.
  2. Otherwise compile the owning Q# factory.
  3. Otherwise compile QASM with the stated Base profile.
Request QASM
  1. Return stored QASM, if present.
  2. Otherwise supported QIR/factory → Qiskit conversion → OpenQASM 3 export.

Optional conversion dependencies are required.

Request circuit view
Stored native view, owning factory, or supported QASM converter.

No direct QIR-only branch.

Model and source. Summarizes the pinned implementation paths described in this section, including optional-dependency and target-profile boundaries. Arrows are supported routes, not universal equivalences.

How to read this. Check which representation is actually present and which consumer is supported before requesting a conversion.

Return to this explanation

get_qiskit_circuit() returns its cached result if present, otherwise prefers QIR/factory over QASM. The QIR route uses the repository’s PyQIR-based converter; QASM-only uses Qiskit’s parser. It warns when classical bits appear under a non-Base active context, but that warning does not repair unsupported control flow. Supplying conflicting QASM and QIR can therefore make different getters return different programs. Consistency is a producer obligation, not a constructor theorem.

QIR, quantum intermediate representation, is an LLVM-based compiler interchange format: a low-level language for instructions, registers/results, and classical control flow. It is not a target device’s pulse schedule and does not choose an error-correcting code. A device/provider/compiler stack must implement the remaining lowering. Likewise OpenQASM is a program interchange language, not a guarantee that every importer understands every language feature.

Serialization currently stores QASM when present, QIR when available or factory-compilable, encoding, width, and shared-gradient metadata. It does not serialize the Python callable/factory as a reusable live closure. A reloaded QIR-only circuit may still be convertible to Qiskit or consumable by an appropriate QIR executor but no longer supplies the Q# operation needed by a native composition builder. A Q#-object-only circuit has no equivalent serialized executable path in these methods. Even content hashing of a factory can trigger compilation. “Inspect this object” is not always equivalent to a pure source read, which is why none of these operations was invoked during this authoring session.

Contexts, profiles, and reversible composition

The repository’s Q# context utilities lazily create a shared context, defaulting to Adaptive_RIF. create_qsharp_context creates a fresh one; set_qsharp_context changes the process-wide choice, and use_qsharp_context temporarily overrides it on the current thread. Factory methods use the owning context attached to their program when present. Mixing callable objects from unrelated contexts is not a safe substitute for loading everything into one composition context.

A target profile states which program features the compiler may use. Base supports a more restricted circuit model; the repository loads a selected subset of its Q# utility files for it. That subset includes ordinary state preparation, Pauli/PSP circuits, and basic phase-estimation components, but excludes QROM/phase-gradient/SOSSA utility files. Choosing Base is therefore not a universal workaround for an adaptive conversion warning. The Base helper also stages a reduced source project internally; no context creation or staging was performed here.

An adjoint reverses a unitary action. Q# Adj and Ctl are capabilities in the operation type, not properties automatically attached to every Circuit. The ordinary PauliExp wrapper returns a plain Qubit[] => Unit operation in this source, even though its mathematical factors are unitary; use the dedicated controlled mapper for its supported controlled form. The generic sequential helper similarly returns a plain operation, whereas repeated/controlled composition helpers require Adj + Ctl. Binary and SOSSA routines advertise their narrower adjoint capability explicitly.

CircuitComposition.qs offers sequential application, repetition, a controlled-operation adapter, conversion from a control-array interface to a single-control interface, and shared-ancilla initialization wrappers. Its repeated helper contains estimator caching markers. Those markers reuse analysis work; they do not remove repeated physical operations from the requested algorithm.

The within { A } apply { B } form means compute AA, apply BB, then uncompute AA. This is why loading a lookup word and later clearing it can preserve coherence without measurement of the chemical data. Where an implementation intentionally uses measurement-based uncomputation, classical feedback and a compatible profile become part of the circuit contract. Resetting arbitrary entangled workspace is not a substitute for uncomputation.

Qiskit interoperability: useful, but not arbitrary QIR

The QIR converter reads entry-point attributes for required qubit/result counts, resolves indexed QIR qubit/result handles, and forwards supported gates into a Qiskit QuantumCircuit. Handlers include Pauli/H/S/T gates and adjoints, axis rotations, controlled Pauli gates, SWAP, two-qubit XX/YY/ZZ rotations, Toffoli, measurement, and reset. Angles are extracted as numerical constants and passed in the corresponding rotation convention, not arbitrary symbolic runtime expressions.

Its control-flow walker is important. It can preserve direct measurement-result-conditioned branches with supported immediate joins as Qiskit if/if_else blocks. It rejects loops, unsupported branch structure, branch conditions not directly reading a measurement result, and entry points without a body. Equivalent source programs can lower to different classical-expression forms, only some of which it recognizes. The interop tests verify a guarded X remains conditional and explicitly test an unsupported condition. Do not infer universal adaptive-program support from one successful measured-AND conversion, or a blanket unsupported-instruction guarantee from a broad module docstring alone.

The transpiler helpers merge Z-axis regions, replace special-angle rotations with Clifford/T gates or Pauli gates, and remove diagonal operations on qubits known to start at zero. Their settings and assumptions matter. For example, SubstituteCliffordRz recognizes numerical multiples of π/4\pi/4 with a default machine-epsilon comparison tolerance and skips symbolic parameter expressions. Its name includes T, which is not itself Clifford. Replacing Rz(π/2)R_z(\pi/2) by S is only equal up to a global phase unless that phase is accounted for; the direct replacement does not record a compensating phase. That can be harmless for standalone state preparation or a matched prepare/unprepare pair but is not a license to control the replacement as if its full unitary phase were identical. The zero-state-removal pass likewise is not valid for arbitrary unknown input.

The circuit-analysis utilities compute depth, gate counts, and heuristic idle/classical/quantum role labels. Their drawing helper can omit wires, measurements, and gates incident on filtered wires. That produces a simplified visualization, not a proof of equivalent evolution or a reduction in QEC resources. A register classified as classical for one initialization may be quantum in a different coherent use.

Noise conversion is separate again. The noise adapter builds Qiskit Aer depolarizing errors from a supplied error profile and gate arity; it explicitly rejects qubit-loss support in that adapter and unsupported gate/error types. A noiseless representation conversion neither supplies realistic calibration data nor validates a device model. We will treat execution/noise in the dedicated later lesson.

How the enclosing algorithm uses the subroutines

The native iterative phase-estimation builder expects usable Q# operations for both trial-state preparation and controlled unitary. If they are absent, it raises rather than automatically using arbitrary QIR or a rendered circuit; a separate Qiskit builder serves that plugin route. Its compact control/system layout and width checks are part of the contract. A standalone subroutine factory is not already a complete phase-estimation experiment.

The unary builder instead takes an LCU walk or SOSSA block, obtains a unit-power block circuit, and owns a reflection/query schedule. It requires the mapper to declare width, removes shared-gradient bits from the reflected zero-ancilla count, checks agreement if the state and block both request gradients, and requires a nonempty reflection register. It initializes shared ancillas through a supplied operation rather than pretending metadata itself is a quantum action. Detailed query schedules and success probabilities remain for later chapters.

These builders demonstrate why chemical, encoding, callable, and width compatibility must all hold simultaneously. The unitary representations do not carry a complete automatic proof that an arbitrary input trial state has matching orbital identities or the same tapering sector. Most circuit mappers also do not propagate an encoding label into their returned Circuit. Retain the provenance explicitly; a missing label is not permission to pair unrelated states and operators.

Two different resource-estimation consumers

Circuit.estimate() uses factory-derived logical counts and the external estimator, or an OpenQASM estimation path. It raises for a QIR-only or native-circuit-object-only instance. If a result reports a different logical width from the declared argument width, the method logs that difference; it does not rewrite the declaration to a physical-qubit number.

Circuit.get_qre_application() is an alternative adapter for the optional qdk.qre application interface. It chooses a Q# application for factory data, otherwise an OpenQASM application, otherwise a QIR application. That permits a QIR-only route the older .estimate() method does not support. It is not a mandatory second step after .estimate(). The Circuit tests encode those different capabilities.

The external resource-estimation and quantum-error-correction teams supply later models of logical error budgets, gate synthesis, codes, factories, scheduling, and physical devices. At this layer, a circuit contains logical operations and allocation structure. A shorter logical circuit is promising input to that analysis, not an already established reduction in physical cost.

For our nitrogen workflow, the output of this lesson is therefore a carefully specified subroutine contract: which mathematical operation, on which register, with which angles, phases, workspace, representation, and supported consumers. We have not made the active-space approximation more accurate, and we have not run the algorithm. We have made the path from its mathematics to its circuits explicit enough that the next lessons can compare actual time-evolution and block-encoding methods without hiding the engineering between them.

Make the idea your own

Supplementary learning

The original lesson above is preserved in full, with labeled supplemental figures placed beside the relevant explanations. The model and questions below are also learning aids, not newly executed chemistry results.

Make a phase visible

Interactive model

Observe why a relative phase matters before composing a controlled operation.

Illustrative model, not a scientific calculation. One ideal qubit initialized in zero, then H, diag(1, exp(i phase)), H, and computational-basis measurement. The output is an exact probability, not sampled data. No noise or hardware is simulated.

Enable JavaScript to explore this model. The assumptions and original lesson remain readable without it.

Check your understanding

Supplementary learning

Reason through these 3 questions. Your answers do not mark the lesson as read, and nothing is locked.

1. Why can global-phase bookkeeping matter when controlling an operation?
2. Why reverse a parity-computation network after a rotation?
3. What should be checked before requesting a framework conversion?

Enable JavaScript to check and save answers locally.

Source notes & references (36)

No compilation/imports/tests/execution/files. Native Pauli angle convention and chronological ordering traced to Q# calls/tests; actual Exp decomposition external. Direct controlled-Pauli standalone allocation supports compact layouts but can fail accepted sparse high indices; controlled results omit declared width. CSWAP requires vacuum-preserving ordered blocks and phase correction. PSP controls SELECT only, rejects external gradients, controlled version fixes control0 and ignores inherited target remapping. SOSSA unit-power uncontrolled Adj-only block; unary consumer owns reflection, no generic controlled mapper. Circuit getters have differing priorities/dependencies/caches; factory dict argument order is positional; serialization loses live callable. QASM-to-QIR fallback fixedBase, current default contextAdaptive_RIF with limitedBase sources. QIR importer supports particular direct-result branch forms, not all adaptive/QIR programs. Some transpiler replacements are only equivalent up to global phase. get_qre_application and estimate are alternative consumers with different QIR-only capability. Pauli reorder_terms currently resets scale metadata.

Public source: microsoft/qdk-chemistry, pinned to 97a3ff4fb86e. This course describes that revision, not every later release.

Look up a term

On this page

  1. One workflow, several different translations
  2. What a unitary plan contains
  3. Deriving a Pauli exponential as gates
  4. Direct control: why phase bookkeeping suddenly matters
  5. A small complete construction pattern
  6. Controlling by swapping with a vacuum register
  7. PREPARE–SELECT–PREPARE-adjoint
  8. SOSSA wiring is a separate contract
  9. Circuit is a bundle of capabilities, not a universal conversion promise
  10. The exact conversion paths
  11. Contexts, profiles, and reversible composition
  12. Qiskit interoperability: useful, but not arbitrary QIR
  13. How the enclosing algorithm uses the subroutines
  14. Two different resource-estimation consumers
  15. Interactive model
  16. Knowledge check
  17. Source notes

The complete journey

Course homeGlossarySavedAbout

Search the course

Search titles and the full lesson text.

    Tab to a result, Enter to open. Escape closes search.