InversionEnvironment#

class InversionEnvironment(env_args=[])[source]#

This QuantumEnvironment can be used to invert (i.e. “dagger”) a block of operations.

An alias for this is invert.

Examples

We increment a QuantumFloat and afterwards revert using the InversionEnvironment:

from qrisp import QuantumFloat, invert

qf = QuantumFloat(4)

qf += 3

with invert():
    qf += 3
>>> print(qf)
{0: 1.0}
>>> print(qf.qs)
QuantumCircuit:
--------------
      ┌───────────┐┌──────────────┐
qf.0: ┤0          ├┤0             ├
      │           ││              │
qf.1: ┤1          ├┤1             ├
      │  __iadd__ ││  __iadd___dg │
qf.2: ┤2          ├┤2             ├
      │           ││              │
qf.3: ┤3          ├┤3             ├
      └───────────┘└──────────────┘
Live QuantumVariables:
---------------------
QuantumFloat qf

In the next example, we create a QuantumFloat and bring it into uniform superposition. We calculate the square and set a QuantumBool to True, based on if the result is less than 10. Finally, we use the InversionEnvironment to uncompute the result of the multiplication.

from qrisp import QuantumBool, h, q_mult, multi_measurement

qf = QuantumFloat(3)

h(qf)

mult_res = q_mult(qf, qf)

q_bool = QuantumBool()

with mult_res < 10:
    q_bool.flip()

with invert():
    q_mult(qf, qf, target = mult_res)

mult_res.delete(verify = True)
>>> print(multi_measurement([qf, q_bool]))
{(0, True): 0.125, (1, True): 0.125, (2, True): 0.125, (3, True): 0.125,
(4, False): 0.125, (5, False): 0.125, (6, False): 0.125, (7, False): 0.125}

Note

In many cases, this way of manually uncomputing only works if the uncomputed function (in this case q_mult) allows specifying the target variable. Using the redirect_qfunction decorator, you can turn any quantum function into it’s target specifiable version.

custom_inversion#

custom_inversion(*func, **cusi_kwargs)[source]#

The custom_inversion decorator enables registering a specialized subroutine for the inverted version of the decorated function. This decorator is crucial for functions where the inversion logic cannot be derived simply by reversing the gate order (e.g., subroutines involving measurements or dynamic classical control). In such scenarios, the user can explicitly define how the function should behave when inverted, ensuring that the correct logic is applied in both forward and backward contexts.

To make use of the decorator, the decorated function is required to support a keyword argument inv, which receives a static boolean. This boolean indicates whether the forward or the backward version of the function should be executed. Once defined, the function with decorator applied does not need to be called with the inv keyword. Instead the backward version will be called automatically, if the function is called within an InversionEnvironment.

Warning

Custom inversion is currently only available in dynamic mode.

For more details consult the examples section.

Parameters:
funcfunction

A function of QuantumVariables, which has the inv keyword.

Returns:
adaptive_inversion_functionfunction

A function which will execute it’s custom inverted version, if called within the custom inversion context.

Examples

We demonstrate the use of the custom_inversion decorator with a simple example.

In this example, we define a function that implements Gidney’s logical AND operation in the forward direction and uncomputes the logical AND in the backward direction. As defined by Gidney , the forward and backward implementations of the logical AND are not simply inverses of each other. Thus, one cannot use the general InversionEnvironment to automatically apply the inverse of the forward implementation. Instead, we use the custom_inversion decorator to explicitly define both the forward and backward implementations of the logical AND operation.

The gidney_mcx_impl and gidney_mcx_inv_impl functions are the pre-defined implementations of the forward and backward versions of Gidney’s logical AND operation, respectively. We define gidney_mcx function along with the custom_inversion decorator, such that we simply apply the logical AND operation and then uncompute it using the custom inverse. The final state of the target qubit is returned to its initial state, which is the expected behavior for this example.

from qrisp import QuantumFloat, custom_inversion, invert, make_jaspr, measure
from qrisp.core import x
from qrisp import gidney_mcx_impl, gidney_mcx_inv_impl

@custom_inversion
def gidney_mcx(a, b, c, inv=False):
    if not inv:
        # Forward: In-place AND operation
        gidney_mcx_impl(a, b, c)
    else:
        # Inverse: uncomputation of logical AND
        gidney_mcx_inv_impl(a, b, c)

def main():
    # Initialize QuantumFloats
    a = QuantumFloat(1, name="a")
    b = QuantumFloat(1, name="b")
    c = QuantumFloat(1, name="c")

    # Prepare inputs (apply bit-flip to controlling qubits a and b)
    x(a[0])
    x(b[0])

    # Apply Logical AND
    gidney_mcx(a[0], b[0], c[0])

    # Uncompute using the custom inverse
    with invert():
        gidney_mcx(a[0], b[0], c[0])

    return measure(c)

# Trace and execute
jaspr = make_jaspr(main)()
print("Result:", jaspr())
# Expected Output: 0
Result: 0.0