BlockEncoding.from_lcu#
- classmethod BlockEncoding.from_lcu(coeffs: ndarray[tuple[int, ...], dtype[number]], unitaries: list[Callable[[...], Any]], num_ops: int = 1, is_hermitian: bool = False) BlockEncoding#
Constructs a BlockEncoding using the Linear Combination of Unitaries (LCU) protocol.
For an LCU block encoding, consider a linear combination of unitaries:
\[O = \sum_{i=0}^{M-1} \alpha_i U_i\]where \(\alpha_i\) are complex coefficients, \(\alpha = \sum_i |\alpha_i|\), and \(U_i\) are unitaries acting on the same operand quantum variables.
The block encoding unitary is constructed via the LCU protocol. If all coefficients are real and non-negative, the block encoding unitary can be expressed as
\[U = \text{PREP} \cdot \text{SEL} \cdot \text{PREP}^{\dagger}\]where:
\(SEL\) (Select, in Qrisp: q_switch) applies each unitary \(U_i\) conditioned on the auxiliary variable state \(\ket{i}_a\):
\[\text{SEL} = \sum_{i=0}^{M-1} \ket{i}\bra{i} \otimes U_i\]\(PREP\) (Prepare) prepares the state representing the coefficients:
\[\text{PREP} \ket{0}_a = \sum_{i=0}^{M-1} \sqrt{\frac{\alpha_i}{\alpha}} \ket{i}_a\]If the coefficients are complex or negative, a state preparation pair \((PREP_R, PREP_L)\) is used:
\[U = \text{PREP}_R \cdot \text{SEL} \cdot \text{PREP}_L^{\dagger}\]\(PREP_R\) and \(PREP_L\) prepare the states representing the coefficients and their complex conjugates, respectively:
\[\text{PREP}_R \ket{0}_a = \sum_{i=0}^{M-1} \sqrt{\frac{\alpha_i}{\alpha}} \ket{i}_a, \qquad \text{PREP}_L \ket{0}_a = \sum_{i=0}^{M-1} \sqrt{\frac{\alpha_i}{\alpha}}^* \ket{i}_a\]- Parameters:
- coeffsnp.ndarray
- 1-D array containing the real non-negative or complex coefficients.
If all coefficients are real and non-negative, the block encoding unitary is constructed as \(U = PREP \cdot SEL \cdot PREP^{\dagger}\).
If coefficients are complex or negative, the block encoding unitary is constructed as \(U = PREP_R \cdot SEL \cdot PREP_L^{\dagger}\), where \(PREP_R\) and \(PREP_L\) prepare the states representing the coefficients and their complex conjugates, respectively.
- unitarieslist[Callable]
List of functions, where each
unitary(*operands)applies a unitary transformation in-place to the provided quantum variables. All functions must accept the same signature and operate on the same set of operands.- num_opsint, optional
The number of operand quantum variables. The defaults to 1.
- is_hermitianbool, optional
Indicates whether the block-encoding unitary is Hermitian. The defaults to False. Set to True, if all provided unitaries are Hermitian.
- Returns:
- BlockEncoding
A BlockEncoding representing the operator defined as the linear combination of unitaries.
- Raises:
- ValueError
If a single unitary is provided with a complex coefficient (up to numerical precision).
Notes
Normalization: The block-encoding normalization factor is \(\alpha = \sum_i |\alpha_i|\).
Performance: Complex or negative coefficients require a state preparation pair, which increases the number of gates in the controlled block-encoding unitary compared to the case of real non-negative coefficients. If all coefficients are real and non-negative, the block encoding unitary is constructed as \(U = PREP \cdot SEL \cdot PREP^{\dagger}\). In this case, the controlled block-encoding unitary requires only to control the \(SEL\) operation.
Examples
from qrisp import * from qrisp.block_encodings import BlockEncoding def f0(x): x-=1 def f1(x): x+=1 BE = BlockEncoding.from_lcu(np.array([1., 1.]), [f0, f1]) @terminal_sampling def main(): return BE.apply_rus(lambda : QuantumFloat(2))() main() # {1.0: 0.5, 3.0: 0.5}