Loops#
- jrange(*args)[source]#
Performs a loop with a dynamic bound. Similar to the Python native
range, this iterator can receive one argument (stop) or two arguments (start, stop). Step size is always 1.Warning
Similar to the ClControlEnvironment, this feature must not have external carry values, implying values computed within the loop can’t be used outside of the loop. It is however possible to carry on values from the previous iteration.
Warning
Each loop iteration must perform exactly the same instructions - the only thing that changes is the loop index
- Parameters:
- *argsint
Can be either a single integer
stop, or two integersstart, stop. In both cases,stopis exclusive, as in standard Python range. - If one argument is provided, it acts asstopandstartdefaults to 0. - If two arguments are provided, they act asstartandstop.
Examples
We construct a function that encodes an integer into an arbitrarily sized QuantumVariable:
from qrisp import QuantumFloat, control, x from qrisp import QuantumFloat, control, measure, x from qrisp.jasp import jrange, make_jaspr, qache @qache def int_encoder(qv, encoding_int): for i in jrange(qv.size): with control(encoding_int & (1<<i)): x(qv[i]) def test_f(a, b): qv = QuantumFloat(a) int_encoder(qv, b+1) return measure(qv) jaspr = make_jaspr(test_f)(1,1)
Test the result:
>>> jaspr(5, 8) 9 >>> jaspr(5, 9) 10
We now give examples that violate the above rules (ie. no carries and changing iteration behavior).
To create a loop with carry behavior we return the incremented final loop index
@qache def int_encoder(qv, encoding_int): for i in jrange(qv.size): with control(encoding_int & (1<<i)): x(qv[i]) j = i + 1 return j def test_f(a, b): qv = QuantumFloat(a) int_encoder(qv, b+1) return measure(qv) jaspr = make_jaspr(test_f)(1,1)
>>> jaspr(5, 8) Exception: Found jrange with external carry value
To demonstrate the second kind of illegal behavior, we construct a loop that behaves differently on the first iteration:
@qache def int_encoder(qv, encoding_int): flag = True for i in jrange(qv.size): if flag: with control(encoding_int & (1<<i)): x(qv[i]) else: x(qv[0]) flag = False def test_f(a, b): qv = QuantumFloat(a) int_encoder(qv, b+1) return measure(qv) jaspr = make_jaspr(test_f)(1,1)
In this script,
int_encoderdefines a boolean flag that changes the semantics of the iteration behavior. After the first iteration the flag is set toFalsesuch that the alternate behavior is activated.>>> jaspr(5, 8) Exception: Jax semantics changed during jrange iteration
Since the
stepargument has been removed as of v0.9, multiply the loop variable by your desired step inside the body.The following example steps through every second qubit (equivalent to step 2):
from qrisp.jasp import jrange, make_jaspr, qache from qrisp import QuantumFloat, x, measure @qache def stepped_loop(qv): # Number of iterations for step 2 n = (qv.size + 1) // 2 # Step-1 loop for k in jrange(n): # Multiply by the desired step i = 2 * k x(qv[i]) def test_f(a): qv = QuantumFloat(a) stepped_loop(qv) return measure(qv) jaspr = make_jaspr(test_f)(1)
>>> jaspr(3) 5 >>> jaspr(4) 5
Reversing a
jrangeloop (equivalent to step size -1) can be done in two ways.The first is to compute the index manually:
from qrisp.jasp import jrange, make_jaspr, qache from qrisp import QuantumFloat, x, measure @qache def reversed_loop(qv): # Step-1 loop for j in jrange(qv.size): # Compute index in reverse i = qv.size - j - 1 x(qv[i]) def test_f(a): qv = QuantumFloat(a) reversed_loop(qv) return measure(qv) jaspr = make_jaspr(test_f)(1)
>>> jaspr(3) 7 >>> jaspr(4) 15
The second way is to wrap the forward loop in an
InversionEnvironment():First, the forward loop without inversion:
from qrisp import QuantumVariable, x, invert from qrisp.jasp import jrange, make_jaspr, qache @qache def loop_with_offset(qv, start): # Forward jrange loop for i in jrange(qv.size - start): # Offset the loop variable by start x(qv[i + start]) def test_f(a): qv = QuantumVariable(a) loop_with_offset(qv, 2) return measure(qv) jaspr = make_jaspr(test_f)(1)
>>> jaspr(4) 12
This applies
xto qubits 2 and 3, giving state|0011⟩. Wrapping the same loop ininvert()reverses the iteration order and daggers the operations:@qache def reversed_loop_with_offset(qv, start): # Reverses the enclosed loop with invert(): # Same forward loop, now runs backwards for i in jrange(qv.size - start): x(qv[i + start]) def test_f_rev(a): qv = QuantumVariable(a) reversed_loop_with_offset(qv, 2) return measure(qv) jaspr_rev = make_jaspr(test_f_rev)(1)
>>> jaspr_rev(4) 12
Because
xis self-inverse, the result is the same — the loop still iterates fromqv.size - start - 1down tostart. JASP handles the reversed iteration and proper daggers automatically, including at higher nesting levels.