ConditionEnvironment#

class ConditionEnvironment(cond_eval_function, args, kwargs={})[source]#

This class enables the usage of if-conditionals as we are used to from classical programming:

from qrisp import QuantumChar, QuantumFloat, h, multi_measurement

q_ch = QuantumChar()
qf = QuantumFloat(3, signed = True)

h(q_ch[0])

with q_ch == "a":
    qf += 2
>>> print(multi_measurement([q_ch,qf]))
{('a', 2): 0.5, ('b', 0): 0.5}

In this code snippet, we first bring the QuantumChar q_ch into the superposition

\[\ket{\text{q_ch}} = \frac{1}{\sqrt{2}} \left( \ket{\text{"a"}} + \ket{\text{"b"}} \right)\]

After that, we enter the ConditionEnvironment, which controls the operations on the condition that q_ch is in state \(\ket{a}\). Finally, we simultaneously measure both QuantumVariables. We see that the incrementation of qf only occured on the branch, where q_ch is equal to the character "a". The resulting quantum state is:

\[\ket{\psi} = \frac{1}{\sqrt{2}} \left( \ket{\text{"a"}}\ket{2} + \ket{\text{"b"}}\ket{0} \right)\]

It is furthermore possible to invert the condition truth value or apply phases. For this we acquire the QuantumBool containing the truth value using the as statement

from qrisp import x, p
import numpy as np

with q_ch == "a" as cond_bool:
    qf += 2
    cond_bool.flip()
    qf -= 2
    p(np.pi/4, cond_bool)
>>> qf.qs.statevector()
sqrt(2)*(|a>*|4> + exp(I*pi/4)*|b>*|-2>)/2

Constructing custom conditional environments

Apart from infix notation like the equality operator, Qrisp also provides an interface for creating custom conditonal environments.

Parameters:
cond_eval_functionfunction

A function which evaluates the truth value. Must return a QuantumBool. Intermediate results do not have to be uncomputed or deleted, as this is automatically performed, when the condition truth value is uncomputed.

argslist

The arguments on which to evaluate.

kwargsdict, optional

A dictionary of keyword arguments for cond_eval_function. The default is {}.

Examples

We will now demonstrate how a ConditionEnvironment, that evaluates the equality of two QuantumVariables can be constructed:

from qrisp import QuantumBool, QuantumVariable, x, cx, mcx

def quantum_eq(qv_0, qv_1):

    if qv_0.size != qv_1.size:
        raise Exception("Tried to evaluate equality condition for
        QuantumVariables of differing size")

    temp_qv = QuantumVariable(qv_0.size)

    cx(qv_0, temp_qv)
    cx(qv_1, temp_qv)
    x(temp_qv)

    res = QuantumBool()

    mcx(temp_qv, res)

    return res

In this function, we create a temporary variable where we apply CX gates controlled on the inputs onto. The qubits where qv_0 and qv_1 agree, will then be in state \(\ket{0}\). After this, we apply regular X gates onto temp_qv such that the qubits where the inputs agree, are in state \(\ket{1}\). Finally, we apply a multi-controlled X gate onto the result to flip the result, if all of qubits of the qubits in temp_qv are in the \(\ket{0}\) state.

We inspect the resulting QuantumCircuit

>>> from qrisp import QuantumChar
>>> q_ch_0 = QuantumChar()
>>> q_ch_1 = QuantumChar()
>>> res_bool = quantum_eq(q_ch_0, q_ch_1)
>>> print(q_ch_0.qs)
QuantumCircuit:
--------------
 q_ch_0.0: ──■─────────────────────────────────────────────────────────
             │
 q_ch_0.1: ──┼────■────────────────────────────────────────────────────
             │    │
 q_ch_0.2: ──┼────┼────■───────────────────────────────────────────────
             │    │    │
 q_ch_0.3: ──┼────┼────┼────■──────────────────────────────────────────
             │    │    │    │
 q_ch_0.4: ──┼────┼────┼────┼────■─────────────────────────────────────
             │    │    │    │    │
 q_ch_1.0: ──┼────┼────┼────┼────┼────■────────────────────────────────
             │    │    │    │    │    │
 q_ch_1.1: ──┼────┼────┼────┼────┼────┼────■───────────────────────────
             │    │    │    │    │    │    │
 q_ch_1.2: ──┼────┼────┼────┼────┼────┼────┼────■──────────────────────
             │    │    │    │    │    │    │    │
 q_ch_1.3: ──┼────┼────┼────┼────┼────┼────┼────┼────■─────────────────
             │    │    │    │    │    │    │    │    │
 q_ch_1.4: ──┼────┼────┼────┼────┼────┼────┼────┼────┼────■────────────
           ┌─┴─┐  │    │    │    │  ┌─┴─┐  │    │    │    │  ┌───┐
temp_qv.0: ┤ X ├──┼────┼────┼────┼──┤ X ├──┼────┼────┼────┼──┤ X ├──■──
           └───┘┌─┴─┐  │    │    │  └───┘┌─┴─┐  │    │    │  ├───┤  │
temp_qv.1: ─────┤ X ├──┼────┼────┼───────┤ X ├──┼────┼────┼──┤ X ├──■──
                └───┘┌─┴─┐  │    │       └───┘┌─┴─┐  │    │  ├───┤  │
temp_qv.2: ──────────┤ X ├──┼────┼────────────┤ X ├──┼────┼──┤ X ├──■──
                     └───┘┌─┴─┐  │            └───┘┌─┴─┐  │  ├───┤  │
temp_qv.3: ───────────────┤ X ├──┼─────────────────┤ X ├──┼──┤ X ├──■──
                          └───┘┌─┴─┐               └───┘┌─┴─┐├───┤  │
temp_qv.4: ────────────────────┤ X ├────────────────────┤ X ├┤ X ├──■──
                               └───┘                    └───┘└───┘┌─┴─┐
    res.0: ───────────────────────────────────────────────────────┤ X ├
                                                                  └───┘
Live QuantumVariables:
---------------------
QuantumChar q_ch_0
QuantumChar q_ch_1
QuantumVariable temp_qv
QuantumBool res

We can now construct the conditional environment from this function

from qrisp import ConditionEnvironment, multi_measurement, h

#Create some sample arguments on which to evaluate the condition

q_bool_0 = QuantumBool()
q_bool_1 = QuantumBool()
q_bool_2 = QuantumBool()

h(q_bool_0)

with ConditionEnvironment(cond_eval_function = quantum_eq,
                          args = [q_bool_0, q_bool_1]):
    q_bool_2.flip()
>>> print(multi_measurement([q_bool_0, q_bool_1, q_bool_2]))
{(False, False, True): 0.5, (True, False, False): 0.5}

This agrees with our expectation, that q_bool_2 is only True if the other two agree.

The quantum_condition decorator

Creating quantum conditions like this seems a bit unwieldy. For a more convenient solution, we provide the quantum_condition decorator. This decorator can be applied to a function returning a QuantumBool, which is then returning the corresponding ConditionEnvironment instead. To demonstrate, we construct a “less than” condition for QuantumFloats

from qrisp import quantum_condition, cx

@quantum_condition
def less_than(qf_0, qf_1):

    temp_qf = qf_0 - qf_1

    res = QuantumBool()

    cx(temp_qf.sign(), res)

    return res

qf_0 = QuantumFloat(3)
qf_1 = qf_0.duplicate()

qf_0[:] = 2
h(qf_1[:2])

res_q_bool = QuantumBool()

with less_than(qf_0, qf_1):
    res_q_bool.flip()
>>> print(multi_measurement([qf_0, qf_1, res_q_bool]))
{(2, 0, False): 0.25, (2, 1, False): 0.25, (2, 2, False): 0.25, (2, 3, True): 0.25}

Quantum-Loops

An interesting application of conditional environments is the qRange iterator. Using this construct, we can mimic a loop as we are used from classical computing, where the end of the loop is determined by a quantum state:

from qrisp import QuantumFloat, qRange, h

n = QuantumFloat(3)
qf = QuantumFloat(5)

n[:] = 4

h(n[0])

n_results = n.get_measurement()

for i in qRange(n):
    qf += i
>>> print(qf)
{10: 0.5, 15: 0.5}

This script calculates the sum of all integers up to a certain threshold. The threshold (n) is a QuantumFloat in superposition, implying the result of the sum is also in a superposition. The expected results can be quickly determined by using Gauß’s formula:

\[\sum_{i = 0}^n i = \frac{n(n+1)}{2}\]
>>> print("Excpected outcomes:", [n*(n+1)/2 for n in n_results.keys()])
Excpected outcomes: [10.0, 15.0]