qrisp.LCU#

LCU(*trial_args)#

Full implementation of the Linear Combination of Unitaries (LCU) algorithmic primitive using the Repeat-Until-Success (RUS) protocol.

This function constructs and executes the LCU protocol using the provided state preparation function and unitaries. It utilizes the qrisp.jasp.RUS() decorator from Jasp to handle the repeat-until-success mechanism, which repeatedly applies the LCU operation until the ancilla register is measured in the \(|0\rangle\) state, indicating a successful implementation. The LCU algorithm enables the implementation of linear combinations of unitary operations on a quantum variable by probabilistically projecting onto the desired transformation. The terminal_sampling decorator is utilized to evaluate the LCU.

For more details on the LCU primitive, refer to Childs and Wiebe (2012).

For more information on the inner workings of this LCU implementation, see inner_LCU().

Parameters:
operand_prepcallable

A function preparing the input state \(\ket{\psi}\). This function must return a QuantumVariable (the operand).

state_prepcallable

A function preparing the coefficient state from the \(\ket{0}\) state. This function receives a QuantumFloat with \(\lceil\log_2m\rceil\) qubits for \(m\) unitiaries \(U_0,\dotsc,U_{m-1}\) as argument and applies

\[\text{PREPARE}\ket{0} = \sum_i\sqrt{\frac{\alpha_i}{\lambda}}\ket{i}\]
unitarieslist[callable] or callable
Either:
  • A list of functions performing some in-place operation on operand, or

  • A function unitaries(i, operand) performing some in-place operation on operand depending on a nonnegative integer index i specifying the case.

num_unitariesint, optional

Required when unitaries is a callable to specify the number \(m\) of unitaries.

oaa_iterint, optional

The number of iterations of oblivious amplitude amplification to perform. The default is 0.

Returns:
QuantumVariable

A variable representing the output state \(A\ket{\psi}\) after successful application of LCU to the input state \(\ket{\psi}\).

Raises:
TypeError

If unitaries is not a list or callable.

ValueError

If num_unitaries is not specified when unitaries is a callable.

See also

inner_LCU

Core LCU implementation without RUS.

view_LCU

Generates the quantum circuit for visualization.

Examples

As a first example, we apply the non-unitary operator \(A\) to the operand \(\ket{\psi}=\ket{1}\) where

\[\begin{split}A = \begin{pmatrix}1 & 1\\ 1 & 1\end{pmatrix} = \begin{pmatrix}1 & 0\\ 0 & 1\end{pmatrix} + \begin{pmatrix}1 & 1\\ 1 & 1\end{pmatrix} = I + X\end{split}\]

This is,

\[A = \alpha_0U_0 + \alpha_1U_1\]

where \(\alpha_0=\alpha_1=1\), and \(U_0=I\), \(U_1=X\).

Accordingly, we define the unitaries

from qrisp import *

def U0(operand):
    pass

def U1(operand):
    x(operand)

unitaries = [U0, U1]

and the state_prep function implementing

\[\text{PREPARE}\ket{0} = \frac{1}{\sqrt{2}}\left(\ket{0}+\ket{1}\right)\]
def state_prep(case):
    h(case)

Next, we define the operand_prep function preparing the state \(\ket{\psi}=\ket{1}\)

def operand_prep():
    operand = QuantumVariable(1)
    x(operand)
    return operand

Finally, we apply LCU

@terminal_sampling
def main():

    qv = LCU(operand_prep, state_prep, unitaries)
    return qv

and simulate

>>> main()
{0: 0.5, 1: 0.5}

As a second example, we apply the operator

\[\cos(H) = \frac{e^{iH}+e^{-iH}}{2}\]

for some Hermitian operator \(H\) to the input state \(\ket{\psi}=\ket{0}\).

First, we define an operator \(H\) and unitaries performing the Hamiltonian evolutions \(e^{iH}\) and \(e^{-iH}\). (In this case, Trotterization will perform Hamiltonian evolution exactly since the individual terms commute.)

from qrisp import *
from qrisp.operators import X,Y,Z

H = Z(0)*Z(1) + X(0)*X(1)

def U0(operand):
    H.trotterization(forward_evolution=False)(operand)

def U1(operand):
    H.trotterization(forward_evolution=True)(operand)

unitaries = [U0, U1]

Next, we define the state_prep and operand_prep functions

def state_prep(case):
    h(case)

def operand_prep():
    operand = QuantumVariable(2)
    return operand

Finally, we apply LCU

@terminal_sampling
def main():

    qv = LCU(operand_prep, state_prep, unitaries)
    return qv

and simulate

>>> main()
{3: 0.85471756539818, 0: 0.14528243460182003}

Let’s compare to the classically calculated result:

>>> A = H.to_array()
>>> from scipy.linalg import cosm
>>> print(cosm(A))
[[ 0.29192658+0.j  0.        +0.j  0.        +0.j -0.70807342+0.j]
[ 0.        +0.j  0.29192658+0.j  0.70807342+0.j  0.        +0.j]
[ 0.        +0.j  0.70807342+0.j  0.29192658+0.j  0.        +0.j]
[-0.70807342+0.j  0.        +0.j  0.        +0.j  0.29192658+0.j]]

That is, starting in state \(\ket{\psi}=\ket{0}=(1,0,0,0)\), we obtain

>>> result = cosm(A)@(np.array([1,0,0,0]).transpose())
>>> result = result/np.linalg.norm(result) # normalise
>>> result = result**2 # compute measurement probabilities
>>> print(result)
[0.1452825+0.j 0.       +0.j 0.       +0.j 0.8547175-0.j]

which are exactly the probabilities we obsered in the quantum simulation!