A block encoding places a matrix inside a larger unitary. This lets us use
a Hamiltonian in a quantum circuit without requiring the Hamiltonian itself
to be a gate. The examples below build on each other; a runnable version
with matrix checks is in the
block-encoding demo.
LCU stands for linear combination of unitaries. It represents an operator
as a weighted sum of unitary terms,
\[
H=\sum_j a_jU_j.
\]
Pauli strings are unitary, so a Pauli Hamiltonian has this form directly.
PREPARE loads the term weights, SELECT applies the chosen \(U_j\), and
UNPREPARE reverses the preparation:
Take \(H=0.6X+0.4Y\), so \(\lambda=1\). One preparation qubit is enough:
\[\begin{split}
\begin{aligned}
\mathrm{PREPARE}|0\rangle&=\sqrt{0.6}|0\rangle+\sqrt{0.4}|1\rangle,\\
\mathrm{SELECT}&=|0\rangle\langle0|\otimes X+|1\rangle\langle1|\otimes Y.
\end{aligned}
\end{split}\]
Choose \(\theta=2\arccos\sqrt{0.6}\) in radians. PREPARE is \(R_y(\theta)\),
and UNPREPARE is \(R_y(-\theta)\):
The code below uses multiplexor_prep for the rotation and writes the two
SELECT branches explicitly.
importzixy.qubit.pauliaszqpfromguppylangimportguppyfromguppylang.std.builtinsimportarray,comptime,daggerfromguppylang.std.quantumimportcx,cy,qubit,toffoli,xfromguppyalgos.algorithms.block_encoding.lcuimportLCU,LCUDatafromguppyalgos.algorithms.state_preparationimportmultiplexor_prephamiltonian=zqp.RealTermSum.from_str("(0.6, X0), (0.4, Y0)",1)data=LCUData.from_hamiltonian(hamiltonian)prepare=multiplexor_prep(data.amplitudes)@guppydefselect(prep_qreg:array[qubit,1],qreg:array[qubit,1])->None:# Open control: apply X when the preparation qubit is |0>.x(prep_qreg[0])cx(prep_qreg[0],qreg[0])x(prep_qreg[0])# Closed control: apply Y when the preparation qubit is |1>.cy(prep_qreg[0],qreg[0])@guppydefunprepare(prep_qreg:array[qubit,1])->None:withdagger:prepare(prep_qreg)@guppydefblock_encode(prep_qreg:array[qubit,1],qreg:array[qubit,1])->None:LCU(prepare,select,unprepare).compose(prep_qreg,qreg)
Start prep_qreg at zero. Post-selecting it on zero exposes the block \(H\);
leave it coherent when the encoding is part of a larger algorithm.
A walk step applies the block encoding followed by a reflection on the
preparation register:
\[
W=RB,\qquad R=I-2|0^a\rangle\langle0^a|.
\]
\(B\) contains \(H/\lambda\) in its all-zero preparation block.
\(R\) changes the sign of that preparation state.
Their product turns each eigenvalue of \(H\) into a phase that QPE can read.
For the two-term example, PREPARE is a Y rotation with
\(\theta=2\arccos\sqrt{0.6}\) in radians. The open control selects X at address
zero; the filled control selects Y at address one.
multiplexor_prep supplies the preparation above; Reflection[1,0]
supplies the one-qubit reflection. Qubitization puts them together:
Let \(|E\rangle\) be an eigenstate of \(H\) and put \(x=E/\lambda\). In the
eigenbasis of \(H\), the walk separates into two-dimensional invariant
subspaces. One basis vector has the preparation register in \(|0^a\rangle\);
the other is its orthogonal partner. On this subspace,
The walk is therefore a Y rotation on each eigenvalue subspace. Its two
eigenphases are \(e^{\pm i\vartheta_E}\), and either phase recovers the energy
through \(E=-\lambda\cos\vartheta_E\). This is the link between the block
encoding and qubitized phase estimation.
Repeated steps encode Chebyshev polynomials. In particular,
\(\langle0^a|W^2|0^a\rangle=2(H/\lambda)^2-I\):
QubitizationCntrl adds an external control to SELECT and the reflection.
PREPARE and UNPREPARE remain unconditional and cancel when the control is zero.
For a superposition, each address receives its own operation. The address
is preserved and can become entangled with the target.
The four-term circuit below shows the basic pattern. A direct implementation
would give every \(U_j\) two controls. Unary iteration computes one address flag
and updates it as the little-endian address advances.
The example below supplies eight arbitrary Pauli operations. The builder adds
the address cascade around their controlled forms:
Four-T temporary AND compute with measurement-based uncomputation.
Both choices implement the same SELECT. The temporary-AND version trades
mid-circuit measurement and feed-forward for a lower T cost. The builder also
accepts other compatible three-qubit compute and uncompute functions. In every
case, the work flags are cleared before SELECT returns.
This construction follows the unary-iteration approach introduced in
Babbush et al., Physical Review X8, 041015 (2018).
For a Pauli LCU, build_unary_iteration_select(data) builds the term
operations directly from LCUData.
QROM uses the same unary-iteration machinery under the hood. It computes the
same address flag and updates it with the same adjacent-AND cascade. Only the
controlled operation changes: SELECT applies \(U_j\), while QROM fans the active
flag out to the one-bits of a classical word and XORs them into a data register:
Sequential CNOTs from the flag to each selected bit.
fanout_log
A logarithmic-depth CNOT ladder.
fanout_measurement_parity
Measurement-assisted parity with feed-forward; uses extra ancillas for four or more selected bits.
All three work on arbitrary data-register states. The small table above has
two or three selected bits per word; larger words expose the different
depth and workspace costs. The current fanout_log requires at least one
selected bit per word. Use fanout_from_data_fn when a custom register
structure also needs a different adapter.
See the QROM notebook for a
complete data-loading example.
QSVT alternates a block encoding and its adjoint with phase rotations to
transform its singular values. The alternating sequence needs both \(B\) and
\(B^\dagger\): moving forward through one phase step uses the LCU encoding, and
the next step reverses it with the adjoint. This preserves the two-dimensional
signal subspaces in which the polynomial transformation is constructed.
For the Hermitian example above, \(B^\dagger=B\), so both QSVT arguments can use
the same LCU. The three half-turn phases [0.5,0.5,0.5] encode the odd
cubic polynomial
Project the signal and preparation qubits onto zero to obtain the polynomial
block. For a general LCU whose block encoding is not Hermitian, the second
argument must implement the actual reversed and adjointed circuit \(B^\dagger\)
rather than reusing \(B\).
The QSVT notebook shows how to find
phases for a chosen polynomial using ChebyshevPolynomial and QSPAngleFinder.