MethodLayer 2
Block-encoding from a Pauli decomposition
Write over Pauli strings; PREPARE loads amplitudes proportional to into an ancilla register, SELECT applies the controlled Pauli strings, and PREPARE unprepares, leaving in the block flagged by the all-zeros ancilla. This is the input model chemistry and lattice Hamiltonians supply for free.
An access model for — sparse-access oracles, a Pauli or LCU decomposition, a purification, or an explicit arithmetic description — plus a target precision .
A unitary on qubits, its subnormalization , and its ancilla/flag count . Because , Gilyén, Su, Low and Wiebe's Definition 43 forces .
Same contract as the slot it fills.
This one, drawn
From Matrix you can query to Block-encoding
A circle is an object you are holding. This method is drawn heavier, opened into its own steps; the other lines between the same two ends are the alternatives recorded for the same slot. Circles are named on hover, and each one is a link.
Nothing drawn here has a recorded way through it that this figure leaves shut. See it on the map
What it fills
- Block-encode a matrix
Wrap an operator inside a larger unitary so that sits in 's top-left block, giving every routine above it one uniform way to touch the matrix. The subnormalization and the ancilla count are outputs of this layer, not free parameters.
When it applies
Efficient only when the number of Pauli terms is and stays small. It does not rescue a general dense matrix: an arbitrary matrix has Pauli coefficients, so this route is a win only where the physics hands you a short decomposition. Note also that a decomposition with many terms of comparable magnitude is expensive even when itself is well-conditioned, because the term count enters the cost through .
Requires
These do not move the route along. The method needs each of them alongside its own work, and the cost of getting them is part of what the method costs.
- Prepare an input state
Map to a state whose amplitudes are proportional to a specified vector , to within . The cost is set by which description of you hold, not by the algorithm that consumes it.
The slot receives one classical vector and one normalisation: the coefficients of , which Babbush et al. take "real and non-negative, with any phases included in the ", and — their one-norm, never the two-norm. The returned amplitudes normalise against that , which the finished block-encoding inherits as its subnormalisation and which the same authors warn "turns out to have significant ramifications for the overall algorithm complexity". Back must come a circuit on "an empty ancilla register of qubits" carrying amplitude on index — and its inverse too: the preparation is spent once forward, once as its adjoint, per block-encoding. approximation: the ancilla need not come back holding only the index. The same paper's Section III D generalises the requirement to , with "an unspecified junk register entangled with " — the loosening that permits subsampling the coefficient oracle.
approximation
Example
given a decomposition A = Sum_j c_j P_j over Pauli strings
# the input model chemistry and lattice Hamiltonians supply for free
# -- efficient only when the number of Pauli terms is poly(n) and ||c||_1
# stays small. It does not rescue a general dense matrix: an arbitrary
# 2^n x 2^n matrix has 4^n Pauli coefficients, so this route is a win only
# where the physics hands you a short decomposition
PREPARE: load amplitudes proportional to sqrt(|c_j|) into an ancilla
register
# loading the coefficient vector is state preparation: hand it to the
# layer below
SELECT: apply the controlled Pauli strings
PREPARE^dagger: unprepare
# A/||c||_1 is left in the block flagged by the all-zeros ancilla
# Gilyen, Su, Low and Wiebe Lemma 52: with (P_L, P_R) a
# (beta, b, epsilon_1)-state-preparation-pair for the coefficient vector and
# each U_j an (alpha, a, epsilon_2)-block-encoding, the result is an
# (alpha*beta, a+b, alpha*epsilon_1 + alpha*beta*epsilon_2)-block-encoding,
# using a single use each of SELECT, P_R and P_L^dagger
# their Definition 51 requires beta >= ||y||_1; for a Pauli decomposition
# that is the lambda = ||c||_1 of the chemistry literature. So a decomposition
# with many terms of comparable magnitude is expensive even when A itself is
# well-conditioned -- the term count enters the cost through ||c||_1
# b = ceil(log2 L) for L terms is implied by Definition 51's requirement
# 2^b >= L; Lemma 52 does not state it, and this listing does not claim it as
# the lemma's
# Babbush et al. give a SELECT/PREPARE compilation with T-gate complexity
# O(N + log(1/epsilon)) for N orbitals, enabling qubitized phase estimation
# with optimal query complexity O(lambda/epsilon)Cost, as the source states it
Gilyén, Su, Low and Wiebe (arXiv:1806.01838) Lemma 52: with a -state-preparation-pair for the coefficient vector and each an -block-encoding, the result is an -block-encoding using a single use each of SELECT, and , with required by their Definition 51 — for a Pauli decomposition this is the of the chemistry literature. Babbush et al. give a SELECT/PREPARE compilation with T-gate complexity for orbitals, enabling qubitized phase estimation with optimal query complexity . The flag-qubit count for terms is implied by Definition 51's requirement ; Lemma 52 does not state it.
Implementations
Qualtran's `LCUBlockEncoding` and `SelectBlockEncoding` bloqs
Qualtran is Google Quantum AI's library for writing fault-tolerant algorithms down and counting what they cost: its abstract presents it as "an open-source library for representing and analyzing quantum algorithms" and says that "Using appropriate abstractions and data structures, we can simulate and test algorithms, automatically generate information-rich diagrams, and tabulate resource requirements". Block encoding is Section III E, and that section's definition is the record's: "the (s + a)-qubit unitary B[A] is a (α, a, ϵ)-block encoding of A if" , "where α is a normalization constant chosen such that ∥A∥/α ≤ 1, and ϵ ≥ 0 is the precision of the encoding" — the norm is the paper's, and without it the left-hand side would be an operator compared to a scalar. The LCU route is the second of three primitives the section builds — "First, it provides bloqs to construct block encodings from primitive elements such as unitaries, LCU SELECT/PREPARE oracles, and sparse access matrices" — and it is named outright: "any linear combination of unitaries as specified by a pair of SELECT and PREPARE oracles defines a block encoding of A, which can be created by Qualtran's LCUBlockEncoding bloq". What the paper offers for those primitives is a parameter table rather than a run: Table III's caption says of α, a and ϵ that "Rather than manually reasoning about these performance parameters, users can compute them automatically by invoking the bloq implementations".
The module holds two classes, and they differ in exactly the place this record's `summary` names: which ancilla state flags the block. `LCUBlockEncoding` is the sandwich — its docstring builds "the standard block encoding from an LCU as" and extracts the Hamiltonian by , the all-zeros flag. Its `build_composite_bloq` is that product literally: it adds `self.prepare`, then `self.select`, then `self.prepare.adjoint()`. `SelectBlockEncoding` is the variant that does not unprepare — alone, extracted by against . The contrast is stated in `LCUBlockEncoding`'s docstring, from its own side: "This differs from the `SelectBlockEncoding` which uses Prepare for the signal state, while here it is the identity operator." In code that is `signal_state`, which returns `self.prepare` on `SelectBlockEncoding` and `PrepareIdentity(self.selection_registers)` on `LCUBlockEncoding`. Both classes take the subnormalisation from the preparation rather than computing it: `alpha` returns `self.prepare.l1_norm_of_coeffs`, whose base-class docstring reads "Sum of the absolute values of coefficients . For LCU Hamiltonians, this is usually referred to as in texts" — the record's . Both docstrings state "The ancilla register is at least of size ". The precision slot is not filled: `epsilon` returns `0.0` on both, under a comment naming Qualtran issue 1247 as the open work, so that zero is a placeholder and not a claim that the encoding is exact. On the ancilla convention the file is explicit: "In our implementations we typically split the ancilla registers into selection registers (i.e. the registers above) and junk registers which are extra qubits needed by state preparation but not controlled upon during SELECT." A caller pinning an older Qualtran meets a trap here, because the two names were swapped: at tag v0.4.1 the class called `LCUBlockEncoding` is the SELECT-only one and the sandwich is called `LCUBlockEncodingZeroState`, while from v0.5.0 onward the sandwich is `LCUBlockEncoding` and the SELECT-only one is `SelectBlockEncoding`. The swap landed on 2024-08-08 in pull request 1268, "Rename LCU block encodings", and `LCUBlockEncodingZeroState` no longer exists anywhere in the repository. The same name denotes different objects on either side of that commit — though not on either side of the paper, which was submitted on 6 Sep 2024, four weeks after the rename.
No molecule and no matrix: the inputs are a SELECT bloq and a PREPARE bloq, and the worked instances are lattice models. All four `bloq_example` functions in the file build the same one, commented "# 3x3 hubbard model U/t = 4" with `dim = 3`, `U = 4`, `t = 1`, from `SelectHubbard(x_dim=dim, y_dim=dim)` and `PrepareHubbard(x_dim=dim, y_dim=dim, t=t, u=U)` in `qualtran.bloqs.chemistry.hubbard_model.qubitization`; two of them wrap those in `BlackBoxSelect` and `BlackBoxPrepare` first. The paper's Fig. 10 runs a larger one, `n, t_hubbard, u_hubbard = 4, 1, 4`, introduced as "an example snippet for simulating the 2D Hubbard Hamiltonian [9, Sec. 5] using HamiltonianSimulationByGQSP". Its reference [9] is "R. Babbush, C. Gidney, D. W. Berry, N. Wiebe, J. McClean, A. Paler, A. Fowler, and H. Neven, Encoding electronic spectra in quantum circuits with linear t complexity, Phys. Rev. X 8, 041015 (2018)" — the second of this record's own two citations.
`qualtran/bloqs/block_encoding/lcu_block_encoding.py` in https://github.com/quantumlib/Qualtran — Python, Apache License 2.0, header "Copyright 2023 Google LLC"; 376 lines on `main` as read on 2026-08-26, latest tag v0.7.0. The two block-encoding classes are `SelectBlockEncoding` and `LCUBlockEncoding`, both `attrs.frozen` subclasses of `BlockEncoding` from `qualtran.bloqs.block_encoding.block_encoding_base`, each taking `select: Union[BlackBoxSelect, SelectOracle]` and `prepare: Union[BlackBoxPrepare, PrepareOracle]`; `LCUBlockEncoding` additionally takes `control_val: Optional[int] = None`. Neither class is Pauli-specific — the Pauli case arrives through the SELECT slot, and the SELECT built for Pauli strings is a separate bloq, `SelectPauliLCU` in `qualtran/bloqs/multiplexers/select_pauli_lcu.py`, whose module docstring is "Bloqs for applying SELECT unitary for LCU of Pauli Strings" and which "uses the unary iteration scheme to apply `select_unitaries[selection]` to `target` controlled on the single-bit `control` register", for `select_unitaries` given as `cirq.DensePauliString`s.
The repository itself reports no number for these bloqs: the checked-in `lcu_block_encoding.ipynb` calls `show_call_graph` and `show_counts_sigma` on the 3x3 Hubbard example, but every cell in the committed notebook has an empty `outputs` list, so no count is stored there. The published numbers are in the paper's Fig. 10, and they are the cost of a whole simulation rather than of the block encoding alone: the snippet imports `LCUBlockEncoding` from `qualtran.bloqs.block_encoding.lcu_block_encoding`, builds `block_enc = LCUBlockEncoding(select=select, prepare=prepare)`, wraps it as `walk_op = QubitizationWalkOperator(block_enc)` and passes that to `HamiltonianSimulationByGQSP(walk_op, t=t, precision=eps)` with `t, eps = 5, 1e-5`. The top box of that figure gives Qubits 60, CSwaps 37930, Ands 139476, Cliffords 715012, Rotations 14905, Measurements 139476, captioned "Hamiltonian simulation of 2D Hubbard using GQSP for time t = 5 and precision 1.0 × 10−5, and its corresponding gate costs". Three boxes hang below it, each on an edge labelled 1: `Adjoint(subbloq=PrepareHubbard)` and `PrepareHubbard`, carrying identical figures of Qubits 16, CSwaps 9, Ands 7, Cliffords 50, Rotations 4, Measurements 7, and `GeneralizedQSP` at Qubits 58, CSwaps 37912, Ands 139462, Cliffords 714912, Rotations 14897, Measurements 139462. Those two `PrepareHubbard` boxes are not the block encoding's own PREPARE and PREPARE-dagger: they belong to `HamiltonianSimulationByGQSP`, whose `build_composite_bloq` carries the comment "# PREPARE, GQSP, PREPARE†" around its `bb.add_d(self.gqsp, **soqs)` and whose docstring reads "The obtained GQSP operator can then be used with two calls to the PREPARE oracle to simulate the hamiltonian". The two edges labelled 1 are that same class's `counts[Always(self.walk_operator.prepare)] += 1` and `counts[Always(self.walk_operator.prepare.adjoint())] += 1`. The block encoding's own PREPARE and SELECT sit inside the repeated walk operator and are therefore already counted inside the `GeneralizedQSP` box. The paper's Table III, which tabulates α, ancilla count and ϵ for the block encodings, has no row for `LCUBlockEncoding`; its rows are `Unitary`, `SparseMatrix`, `TensorProduct`, `Product`, `Phase`, `LinearCombination` and `ChebyshevPolynomial`. The LCU α is stated in code instead, as `prepare.l1_norm_of_coeffs`.
pyLIQTR's `PauliStringLCU` encoding
pyLIQTR is MIT Lincoln Laboratory's circuit-and-resource-estimation library — "a python library for building quantum circuits derived from quantum algorithms and generating Clifford+T resource estimates", "built extensively atop `cirq` & the recent release of `qualtran`". Its architecture separates the Hamiltonian from the way it is encoded: `pyLIQTR.ProblemInstances` "provides an easy interface (through `pyLIQTR.ProblemInstances.getInstance`) to generate Hamiltonians that capture various physical models of interest", and `pyLIQTR.BlockEncodings` "provides an easy interface (through `pyLIQTR.BlockEncodings.getEncoding`) to generate BlockEncodings that encode the action of a ProblemInstance through various different encodings". The Pauli decomposition is the first member of the `VALID_ENCODINGS` enum, `PauliLCU = 1`, but nothing makes it a default: `getEncoding(encoding, instance=None, prepare_type=None, **kwargs)` gives `encoding` no default value, so a caller must name one. Of the enum's seven members only five are reachable through that function — `PauliLCU`, `FermiHubbardSquare`, `LinearT`, `DoubleFactorized` and `FirstQuantized`, with `Fermionic` and `CarlemanLinearization` falling through to `raise NotImplementedError("Sorry :'(")` — and of those five it is the only one not tied to a particular Hamiltonian family, so it is the route a caller takes when the only structure available is a list of Pauli terms.
`PauliStringLCU` is the concrete Pauli case of the record's construction, and the two quantities the record names appear in it by name. Term padding comes first, because the index register must be a power of two: `self.n_pad = 2**(int(np.ceil(np.log2(self.n_terms)))) - self.n_terms` and `self.n_tot = self.n_terms + self.n_pad`, after which `selection_bitsize = int(np.ceil(np.log2(self.n_tot)))` — the record's , computed on the padded count. The amplitudes are the record's: `alphas = [np.sqrt(np.abs(t.coefficient)) for t in self.getTerms]`, the square roots of the absolute coefficients. SELECT is delegated to Qualtran — `from qualtran.bloqs.multiplexers.select_pauli_lcu import SelectPauliLCU` — and instantiated as `SelectPauliLCU(selection_bitsize=selection_bitsize, target_bitsize=self.PI.n_qubits(), select_unitaries=self.getTerms, control_val=self._control_val)`. PREPARE is where the class offers a choice. With `prepare_type=None` it uses pyLIQTR's own `prepare_pauli_lcu(selection_bitsize=selection_bitsize, alphas=alphas)`, which wraps `QSP_Prepare` and declares a cost analytically rather than by decomposition: `TComplexity(t=0, clifford=2**(d-1), rotations=2**(d))` for `d = selection_bitsize`, under a comment reading "Note the analytical expression from https://arxiv.org/pdf/1905.10724.pdf A.3.2 seemed to be off by a factor of 1/2". With `prepare_type='AS'` it uses Qualtran's `StatePreparationAliasSampling.from_lcu_probs(lcu_probabilities=[...], probability_epsilon=probability_eps)` with `probability_eps=0.002` by default — and that branch carries a restriction the caller must read, because it is a warning and not an error: "Alias sampling preparation with negative coefficients is not supported yet. Circuits and estimates will assume positive coefficients." The circuit is assembled by the parent class `BlockEncoding_select_prepare`, whose `decompose_from_registers` yields `self._prepare_gate`, then the select gate, then `cirq.inverse(self._prepare_gate...)`, with the outer two gated by constructor flags (`do_prepare=True`, `do_prepare_inverse=True`) and the middle one reversible by a third (`invert_select=False`, which yields `cirq.inverse(self._select_gate...)` when set) — so the sandwich is the default but every stage can be switched. Its `_t_complexity_` charges `2*prepare_cost + select_cost` only when both prepare flags are set and `self._inverse_prepare_workaround is None`; where that attribute is set the doubled term is replaced by `t_complexity(self._inverse_prepare_workaround)`, and where either flag is off the total is `prepare_cost + select_cost`. For `PauliStringLCU` the doubled branch is the one taken: the parent's `__init__` sets `self._inverse_prepare_workaround = None` and this subclass never overrides it.
Lattice spin models, supplied as a `ProblemInstance` rather than as a matrix. The fixture the encoding's own test suite uses is a 3-by-3 Heisenberg model on a square lattice: `N=3`, `J_x = J_y = 0.5`, `J_z = 1.0`, `h_x = 1.0`, `h_y = 0.0`, `h_z = 0.5`, built by `getInstance("Heisenberg", shape=(N,N), J=(J_x,J_y,J_z), h=(h_x,h_y,h_z), cell=SquareLattice)`.
`src/pyLIQTR/BlockEncodings/PauliStringLCU.py` in https://github.com/isi-usc-edu/pyLIQTR — Python, 78 lines, class `PauliStringLCU(BlockEncoding_select_prepare)`, reached through `getEncoding(instance=..., encoding=VALID_ENCODINGS.PauliLCU, prepare_type=...)` in `src/pyLIQTR/BlockEncodings/getEncoding.py`, where `VALID_ENCODINGS` is an `Enum` in the package `__init__.py` with `PauliLCU = 1`. The default PREPARE lives in `src/pyLIQTR/circuits/operators/select_prepare_pauli.py` and the sandwich in `src/pyLIQTR/BlockEncodings/BlockEncoding.py`. Distributed on PyPI as `pyLIQTR`, version 1.4.2 (uploaded 2026-01-05), latest tag v1.4.2. The licence is recorded in three different strings and they disagree. The repository's `LICENSE.txt` is the MIT License text under "Copyright (c) 2024 Massachusetts Institute of Technology", which is what the GitHub API reports; every source file read here opens with "SPDX-License-Identifier: BSD-2-Clause", as does the README; and PyPI's metadata field records the licence as the string "BDS-2", which matches no SPDX identifier. `CITATION.cff` carries both of the first two on its own — the SPDX line sits inside its `message:` block, while its top-level machine-readable key reads `license: MIT` — so it is not evidence for either side. There is no paper: `CITATION.cff` says "Please use this DOI number reference, published on https://zenodo.org. when citing the software: https://doi.org/10.5281/zenodo.7221272".
Nothing numerical is asserted. The six tests in `src/pyLIQTR/BlockEncodings/tests/test_PauliLCU.py` run the 3-by-3 Heisenberg fixture through both PREPARE branches and check only that the machinery executes: two check "gate decomposition existence" by asserting `[operation] != cirq.decompose_once(operation)` and `[operation] != cirq.decompose(operation)`; two check "qasm printing functionality" with `assert qasm is not None`; and the two resource tests are documented as "Tests estimate_resources executes without error" and call `resources = estimate_resources(encoding.circuit)` without asserting anything about the value returned. So this artefact demonstrates that the encoding decomposes to gates and can be costed, and reports no Clifford+T count of its own for that instance.
PennyLane's `qml.PrepSelPrep` template
Xanadu's PennyLane ships the sandwich as a single template, and its release note frames it as a convenience over three calls a user was already making: "A new template called `qml.PrepSelPrep` has been added that implements a block-encoding of a linear combination of unitaries", which "acts as a nice wrapper for having to perform `qml.StatePrep`, `qml.Select`, and `qml.adjoint(qml.StatePrep)` in succession, which is quite common in many quantum algorithms (e.g., LCU and block encoding)". The module docstring is the same sentence: "Implements a block-encoding of a linear combination of unitaries." Its input is the decomposition already assembled — the `lcu` argument is typed "Union[.Hamiltonian, .Sum, .Prod, .SProd, .LinearCombination]: The operator written as a linear combination of unitaries" — so it takes over exactly where a Pauli decomposition leaves off.
Three functions do the work, and the first discharges a condition this record states rather than assumes. `_get_new_terms`, documented "Compute a new sum of unitaries with positive coefficients", takes `coeffs, ops = lcu.terms()`, reads `angles = math.angle(coeffs)`, and returns `math.abs(coeffs)` together with `new_ops = [prod(op, GlobalPhase(-angle, wires=op.wires)) for angle, op in zip(angles, ops, strict=True)]` — every phase, a negative Pauli coefficient included, is moved off the amplitude and onto the operator, so that the square roots loaded next are real. That is the condition the record's `state-preparation` hop puts on the slot, where Babbush et al. take the coefficients "real and non-negative, with any phases included in the ": the template performs in code the move the record requires. Second, `compute_decomposition` returns a single `change_op_basis(AmplitudeEmbedding(math.sqrt(coeffs), normalize=True, pad_with=0, wires=control), Select(ops, control, partial=True))`: amplitudes proportional to the square roots of the absolute coefficients, zero-padded to fill the control register, and a `Select` over the phase-corrected operators. Third, `change_op_basis(compute_op, target_op, uncompute_op=None)` is documented as "the product of the operators provided; particularly a compute-uncompute pattern", with "`None` corresponds to `uncompute_op=qp.adjoint(compute_op)`" — so the unprepare is the adjoint of the same preparation, not a separately specified circuit. The template is honest about a limit that the record does not raise: it sets `grad_method = None` and opens its docstring with a warning, "Derivatives of this operator are not always guaranteed to exist." It also refuses an overlapping register outright, raising `ValueError("Control wires should be different from operation wires.")`.
No dataset — the worked instance is a two-term Pauli operator on one wire, `lcu = qp.dot([0.3, -0.1], [qp.X(2), qp.Z(2)])` with `control = [0, 1]`, run on `qp.device("default.qubit")`, PennyLane's state-vector simulator. The docstring names no hardware device.
`pennylane/templates/subroutines/prepselprep.py` in https://github.com/PennyLaneAI/pennylane — Python, Apache License 2.0, header "Copyright 2018-2024 Xanadu Quantum Technologies Inc.", 245 lines on `master` as read on 2026-08-26. The class is `PrepSelPrep(Operation)`. Its signature depends on which version you hold: the stable API documentation page prints `PrepSelPrep(lcu, control, id=None)` and the source at tag v0.45.1 agrees, `def __init__(self, lcu: SymbolicOp | CompositeOp, control: WiresLike, id=None) -> None:`, while on `master` that trailing argument is gone — `def __init__(self, lcu: SymbolicOp | CompositeOp, control: WiresLike) -> None:` — so `id=` is accepted by the released version and rejected by the development one. The pieces it composes come from the same library: `Select` from `.select`; `StatePrep`, `GlobalPhase`, `prod` and `change_op_basis` from `pennylane.ops`; and `AmplitudeEmbedding` from `pennylane.templates.embeddings`. It first shipped in v0.38.0, added by pull request 5756 and pull request 5987; v0.39.0 then reused it elsewhere, its changelog recording that "The decomposition for `qml.Qubitization` has been improved to use `qml.PrepSelPrep`."
The docstring runs the check the record's `summary` describes and prints both halves of it. Taking the circuit's own matrix, `print(matrix_psp.real[0:2, 0:2])` gives `[[-0.25 0.75]` / `[ 0.75 0.25]]`, and the same array comes back from the operator divided by the one-norm of its coefficients, `qp.matrix(lcu).real / sum(abs(np.array(lcu.terms()[0])))` — the top-left block holds the operator rescaled by , which for the quoted coefficients `[0.3, -0.1]` is 0.4. The v0.38.0 release note reports a second check, of the template against the three explicit calls it replaces: applying `qml.PrepSelPrep(lcu, control)` and then `qml.adjoint(prep_sel_prep)(alphas, unitaries)`, where `prep_sel_prep` is written out as `qml.StatePrep`, `qml.Select` and `qml.adjoint(qml.StatePrep)`, returns `np.round(circuit(...), decimals=2)` equal to `tensor([1.+0.j -0.+0.j -0.+0.j -0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j])` — the identity, which is the sense in which the two are the same circuit. That check is run on a different operator from the docstring's, and the difference is the load-bearing one: its coefficients are `coeffs = [0.3, 0.1]`, both non-negative, and the manual route takes `alphas = (np.sqrt(coeffs) / np.linalg.norm(np.sqrt(coeffs)))` with no phase correction at all. On the docstring's `[0.3, -0.1]` the manual route would take the square root of a negative number, while `PrepSelPrep` survives it, precisely because of the `_get_new_terms` phase move above. The equivalence is reported, and holds as reported, for non-negative coefficients. Both are exact-simulator checks on a two-term operator; neither is a resource count, and the module reports none.
What it needs
Every step this method names is listed under Requires above. It walks its own span in one hop and calls out to the rest — that is a fact about the recorded route, not a claim that the span is simple.
Other ways to fill the same slot
Different approaches
- Sparse-access oracle construction
Given row and column index oracles , and an entry oracle , prepare uniform superpositions over the sparsity pattern, rotate an ancilla by arcsin of each entry, and swap registers to leave in the flagged block. This is the standard construction behind the sparse-Hamiltonian line, formalized as a block-encoding by Gilyén, Su, Low and Wiebe.
- FABLE approximate circuit construction
Build the block-encoding directly from uniformly controlled Ry (magnitude) and Rz (phase) rotations between Hadamards and a SWAP, with no oracle assumption at all, then threshold the rotation angles and cancel the resulting CNOT chains to compress the circuit.
- Tensor hypercontraction block encoding
Factorize the chemistry Hamiltonian's two-electron integrals into a product of much smaller matrices first, then build the block-encoding of the factorized form. The saving is not in the encoding technique but in what is being encoded — a tensor with far fewer independent entries than the one the basis handed you.
In the Atlas
- Linear combination of unitaries
A block-encoding primitive that turns a weighted sum of unitary operations into one larger unitary circuit.