BlockEncoding#
- class BlockEncoding(alpha: ArrayLike, ancillas: list[QuantumVariable | QuantumVariableTemplate], unitary: Callable[..., None], num_ops: int = 1, is_hermitian: bool = False)[source]#
Central structure for representing block-encodings.
Block-encoding is a foundational technique that enables the implementation of non-unitary operations on a quantum computer by embedding them into a larger unitary operator. Given an operator \(A\), we embed a scaled version \(A/\alpha\) into the upper-left block of a unitary matrix \(U_{A}\):
\[\begin{split}U_A= \begin{pmatrix} A/\alpha & *\\ * & * \end{pmatrix}\end{split}\]More formally, a block-encoding of an operator \(A\) (not necessarily unitary) acting on a Hilbert space \(\mathcal H_{s}\) is a unitary acting on \(\mathcal H_a\otimes H_s\) (for some auxiliary Hilbert space \(\mathcal H_a\)) such that
\[\|A - \alpha (\bra{0}_a \otimes \mathbb I_s) U_A (\ket{0}_a \otimes \mathbb I_s) \| \leq \epsilon\]where
\(\alpha\geq \|A\|\) is a subnormalization factor (or scaling factor) that ensures \(A/\alpha\) has singular values within the unit disk.
\(\epsilon\geq 0\) represents the approximation error.
The block-encoding is termed exact if \(\epsilon=0\), meaning the upper-left block of \(U_{A}\) is exactly \(A/\alpha\).
Implementation mechanism
To apply the operator \(A\) to a quantum state \(\ket{\psi}\):
Prepare the system in state \(\ket{0}_a \otimes \ket{\psi}_s\).
Apply the unitary \(U_A\).
Post-select by measuring the ancillas. If the result is \(\ket{0}_a\), the remaining state is \(\dfrac{A\ket{\psi}}{\|A\ket{\psi}\|}\).
The success probability of this operation is given by
\[P_{\text{success}} = \dfrac{\|A\ket{\psi}\|^2}{\alpha^2}\]- Parameters:
- alphaArrayLike
The scalar scaling factor.
- ancillaslist[QuantumVariable | QuantumVariableTemplate]
A list of QuantumVariables or QuantumVariableTemplates. These serve as templates for the ancilla variables used in the block-encoding.
- unitaryCallable
A function
unitary(*ancillas, *operands)applying the block-encoding unitary. It receives the ancilla and operand QuantumVariables as arguments.- num_opsint
The number of operand quantum variables. The default is 1.
- is_hermitianbool
Indicates whether the block-encoding unitary is Hermitian. The default is False.
- Attributes:
- alphaArrayLike
The scalar scaling factor.
- unitaryCallable
A function
unitary(*ancillas, *operands)applying the block-encoding unitary. It receives the ancilla and operand QuantumVariables as arguments.- num_opsint
The number of operand quantum variables.
- num_ancsint
The number of ancilla quantum variables.
- is_hermitianbool
Indicates whether the block-encoding unitary is Hermitian.
Notes
The shape of the block-encoded operator is determined by the size of the operand variables to which the block-encoding is applied. E.g., if a block-encoded \(4\times 4\) matrix \(A\) is applied to a 3-qubit QuantumVariable, then a block-encoding of the \(8\times 8\) matrix \(\tilde{A}=\mathbb{I}\otimes A\) is applied. This is consistent with the convention that non-occuring indices in a Pauli string are treated as identities. Static-shaped block-encodings may be introduced in a future release.
The
is_hermitianattribute indicates whether the block-encoding unitary \(U_A\) is Hermitian. This is distinct from the operator \(A\) being being Hermitian. A Hermitian operator \(A\) can be block-encoded using a non-Hermitian unitary \(U_A\). Conversely, if the unitary \(U_A\) is Hermitian, then the encoded operator must also be Hermitian.
Examples
Example 1: Pauli Block Encoding
Define a QubitOperator repesenting a Heisenberg Hamiltonian, and construct a block-encoding based on LCU for its Pauli strings.
from qrisp import * from qrisp.block_encodings import BlockEncoding from qrisp.operators import X, Y, Z H = sum(X(i)*X(i+1) + Y(i)*Y(i+1) + Z(i)*Z(i+1) for i in range(3)) BE = BlockEncoding.from_operator(H) # Apply the Hermitian operator to an initial system state # Prepare initial system state def operand_prep(): operand = QuantumFloat(4) h(operand[0]) return operand @terminal_sampling def main(): return BE.apply_rus(operand_prep)() main() # {0.0: 0.6428571295525347, 2.0: 0.2857142963579722, 1.0: 0.07142857408949305}
Example 2: Custom LCU Block Encoding
Define a block-encoding for a discrete Laplace operator in one dimension with periodic boundary conditions.
import numpy as np N = 8 I = np.eye(N) A = 2*I - np.eye(N, k=1) - np.eye(N, k=-1) A[0, N-1] = -1 A[N-1, 0] = -1 print(A) #[[ 2. -1. 0. 0. 0. 0. 0. -1.] # [-1. 2. -1. 0. 0. 0. 0. 0.] # [ 0. -1. 2. -1. 0. 0. 0. 0.] # [ 0. 0. -1. 2. -1. 0. 0. 0.] # [ 0. 0. 0. -1. 2. -1. 0. 0.] # [ 0. 0. 0. 0. -1. 2. -1. 0.] # [ 0. 0. 0. 0. 0. -1. 2. -1.] # [-1. 0. 0. 0. 0. 0. -1. 2.]]
This matrix is decomposed as linear combination of three unitaries: the identity \(I\), and two shift operators \(V\colon\ket{k}\rightarrow-\ket{k+1\mod N}\) and \(V^{\dagger}\colon\ket{k}\rightarrow-\ket{k-1\mod N}\).
from qrisp import * from qrisp.block_encodings import BlockEncoding def I(qv): pass def V(qv): qv += 1 gphase(np.pi, qv[0]) def V_dg(qv): qv -= 1 gphase(np.pi, qv[0]) unitaries = [I, V, V_dg] coeffs = np.array([2.0, 1.0, 1.0]) BE = BlockEncoding.from_lcu(coeffs, unitaries)
Apply the operator to the initial system state \(\ket{0}\).
# Prepare initial system state |0> def operand_prep(): return QuantumFloat(3) @terminal_sampling def main(): operand = BE.apply_rus(operand_prep)() return operand main() # {0.0: 0.6666666567325588, 7.0: 0.16666667908430155, 1.0: 0.1666666641831397}
To perform quantum resource estimation for the quantum program (not counting repetitions), replace the
@terminal_samplingdecorator with@count_ops(meas_behavior="0"):@count_ops(meas_behavior="0") def main(): operand = BE.apply_rus(operand_prep)() return operand main() # {'s': 4, 'gphase': 2, 'u3': 6, 't': 14, 't_dg': 16, 'x': 5, 'cx': 54, # 'p': 2, 'h': 16, 'measure': 10}
To perform resource estimations for the block-encoding use
resources():BE.resources(QuantumFloat(3)) # {'gate counts': {'s': 4, 't_dg': 16, 'h': 16, 't': 14, 'gphase': 2, # 'p': 2, 'x': 5, 'cx': 54, 'u3': 6, 'measure': 4}, 'depth': 48, 'qubits': 9}
Constructors#
Constructs a BlockEncoding from a 2-D array. |
|
Constructs a BlockEncoding using the Linear Combination of Unitaries (LCU) protocol. |
|
Constructs a BlockEncoding from an operator. |
Utilities#
Applies the BlockEncoding unitary to the given operands. |
|
Applies the BlockEncoding using Repeat-Until-Success. |
|
Returns a BlockEncoding representing \(k\)-th Chebyshev polynomial of the first kind applied to the operator. |
|
Returns a list of ancilla QuantumVariables for the BlockEncoding. |
|
Measures the expectation value of the operator using the Hadamard test protocol. |
|
Returns a BlockEncoding representing the qubitization walk operator. |
|
Estimate the quantum resources required for the BlockEncoding. |
Arithmetic#
Returns a BlockEncoding of the sum of two operators. |
|
Returns a BlockEncoding of the product of two operators. |
|
Returns a BlockEncoding of the scaled operator. |
|
Returns a BlockEncoding of the negated operator. |
|
Returns a BlockEncoding of the difference between two operators. |
|
Returns a BlockEncoding of the Kronecker product (tensor product) of two operators. |
Algorithms & Applications#
Returns a BlockEncoding approximating the matrix inversion of the operator. |
|
Returns a BlockEncoding representing a polynomial transformation of the operator. |
|
Returns a BlockEncoding approximating Hamiltonian simulation of the operator. |