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, i.e. arguments of the same abstract type/shape, and, if any arguments are marked static via
kwargs, the same concrete value for those).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
qachedecorator not only improves the compilation speed but also enables the compiler to speed up transformation processes.Warning
Two important rules apply to the
qachedecorator 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.
- kwargsdict, optional
Keyword arguments that are forwarded to jax.jit.
- Returns:
- qached_functioncallable
A function that will be traced on its 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.sleepcommand.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_functionhas 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:QubitArray b:QuantumState. let c:Qubit = jasp.get_qubit a 0:i64[] d:QuantumState = jasp.quantum_gate[gate=h] c b e:Qubit = jasp.get_qubit a 1:i64[] f:QuantumState = jasp.quantum_gate[gate=cx] c e d g:bool[] h:QuantumState = jasp.measure c f in (g, h) } in let inner_function1 = { lambda ; i:QubitArray j:i64[] k:QuantumState. let l:Qubit = jasp.get_qubit i 0:i64[] m:QuantumState = jasp.quantum_gate[gate=h] l k n:Qubit = jasp.get_qubit i 1:i64[] o:QuantumState = jasp.quantum_gate[gate=cx] l n m p:bool[] q:QuantumState = jasp.measure l o in (p, q) } in { lambda ; r:QuantumState. let s:QubitArray t:QuantumState = jasp.create_qubits 2:i64[] r u:QubitArray v:QuantumState = jasp.create_qubits 2:i64[] t w:bool[] x:QuantumState = jit[name=inner_function jaxpr=inner_function] s v y:bool[] z:QuantumState = jit[name=inner_function jaxpr=inner_function1] u 0:i64[] x ba:bool[] bb:QuantumState = jit[name=inner_function jaxpr=inner_function] s z bc:bool[] bd:QuantumState = jit[name=inner_function jaxpr=inner_function1] u 0:i64[] bb be:bool[] = and w y bf:bool[] = and be ba bg:bool[] = and bf bc in (bg, bd) }
As expected, we see three different function definitions:
The first one describes
inner_functioncalled with a QuantumVariable. For this kind of signature only theQubitArrayis required.The second one describes
inner_functioncalled with QuantumFloat. Additionally to theQubitArray, the.exponentattribute is also passed to the function, because it is a traced attribute.The third block is the anonymous top-level function representing
main, 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_functionreturns 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
QuantumVariablethat has been passed as an argument. A traced attribute is for instance theexponentattribute 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