Qaching#

qache(*func, **kwargs)[source]#

This decorator allows you to mark a function as “reusable”. Reusable here means that the jasp expression of this function will be cached and reused in the next calls (if the function is called with the same signature).

A qached function therefore has to be traced by the Python interpreter only once and after that the function can be called without any Python-interpreter induced delay. This can significantly speed up the compilation process.

Using the qache decorator not only improves the compilation speed but also enables the compiler to speed up transformation processes.

Warning

Two important rules apply to the qache decorator to adhere to the functional programming paradigm.

  • It is illegal to have a qached function return a QuantumVariable that has been passed as an argument to the function.

  • It is illegal to modify traced attributes of QuantumVariables that have been passed as an argument to the function.

See the examples section for representatives of these cases.

Parameters:
funccallable

The function to be qached.

Returns:
qached_functioncallable

A function that will be traced on it’s first execution and retrieved from the cache in any other call.

Examples

We create a simple function that is qached. To simulate an expensive compilation task we insert a time.sleep command.

import time
from qrisp import *
from qrisp.jasp import qache

@qache
def inner_function(qv):
    h(qv[0])
    cx(qv[0], qv[1])
    res_bl = measure(qv[0])

    # Simulate demanding compilation procedure by calling
    time.sleep(1)

    return res_bl

def main():
    a = QuantumVariable(2)
    b = QuantumFloat(2)

    bl_0 = inner_function(a)
    bl_1 = inner_function(b)
    bl_2 = inner_function(a)
    bl_3 = inner_function(b)

    return bl_0 & bl_1 & bl_2 & bl_3

# Measure the time required for tracing
t0 = time.time()
jaspr = make_jaspr(main)()
print(time.time() - t0) # 2.0225703716278076

Even though inner_function has been called 4 times, we only see a delay of 2 seconds. This is because the function has been called with two different quantum types, implying it has been traced twice and recalled from the cache twice. We take a look at the Jaspr.

>>> print(jaspr)
let inner_function = { lambda ; a:QuantumCircuit b:QubitArray. let
    c:Qubit = jasp.get_qubit b 0
    d:QuantumCircuit = jasp.h a c
    e:Qubit = jasp.get_qubit b 1
    f:QuantumCircuit = jasp.cx d c e
    g:QuantumCircuit h:bool[] = jasp.measure f c
  in (g, h) } in
let inner_function1 = { lambda ; i:QuantumCircuit j:QubitArray k:i32[] l:bool[]. let
    m:Qubit = jasp.get_qubit j 0
    n:QuantumCircuit = jasp.h i m
    o:Qubit = jasp.get_qubit j 1
    p:QuantumCircuit = jasp.cx n m o
    q:QuantumCircuit r:bool[] = jasp.measure p m
  in (q, r) } in
{ lambda ; s:QuantumCircuit. let
    t:QuantumCircuit u:QubitArray = jasp.create_qubits s 2
    v:QuantumCircuit w:QubitArray = jasp.create_qubits t 2
    x:QuantumCircuit y:bool[] = pjit[name=inner_function jaxpr=inner_function] v
      u
    z:QuantumCircuit ba:bool[] = pjit[name=inner_function jaxpr=inner_function1] x
      w 0 False
    bb:QuantumCircuit bc:bool[] = pjit[name=inner_function jaxpr=inner_function] z
      u
    bd:QuantumCircuit be:bool[] = pjit[
      name=inner_function
      jaxpr=inner_function1
    ] bb w 0 False
    bf:bool[] = and y ba
    bg:bool[] = and bf bc
    bh:bool[] = and bg be
    bi:QuantumCircuit = jasp.reset bd u
    bj:QuantumCircuit = jasp.delete_qubits bi u
  in (bj, bh) }

As expected, we see three different function definitions:

  • The first one describes inner_function called with a QuantumVariable. For this kind of signature only the QubitArray is required.

  • The second one describes inner_function called with QuantumFloat. Additionally to the QubitArray, the .exponent and .signed attribute are also passed to the function.

  • The third function definition is outer_function, which calls the previously defined functions.

Illegal functions

We will now demonstrate what type of functions can not be qached.

@qache
def inner_function(qv):
    h(qv[0])
    return qv

@jaspify
def main():
    qf_0 = QuantumFloat(2)
    qf_1 = inner_function(qf_0)
    return measure(qf_1)

main()
# Yields: Exception: Found parameter QuantumVariable within returned results

inner_function returns a QuantumVariable that has been passed as an argument and can therefore not be qached.

The second case of an illegal functions is a function that tries to modify a traced attribute of a QuantumVariable that has been passed as an argument. A traced attribute is for instance the exponent attribute of QuantumFloat.

@qache
def inner_function(qf):
    qf.exponent += 1

@jaspify
def main():
    qf = QuantumFloat(2)
    inner_function(qf)

main()
# Yields: Exception: Found in-place parameter modification of QuantumVariable qf