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}