Skip to content

Commit b929bac

Browse files
authored
Merge pull request #1148 from qiboteam/fix-qm-loops
Port exact `from_array` from qualang_tools
2 parents 74d98c6 + 353cbd7 commit b929bac

File tree

2 files changed

+148
-113
lines changed

2 files changed

+148
-113
lines changed

src/qibolab/_core/instruments/qm/program/instructions.py

+6-113
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
from typing import Optional
22

3-
import numpy as np
4-
import numpy.typing as npt
53
from qm import qua
6-
from qm.qua import Cast, Variable, declare, fixed, for_, for_each_
4+
from qm.qua import declare, fixed, for_
75

86
from qibolab._core.execution_parameters import AcquisitionType, ExecutionParameters
97
from qibolab._core.identifier import ChannelId
@@ -13,6 +11,7 @@
1311
from ..config import operation
1412
from .acquisition import Acquisition
1513
from .arguments import ExecutionArguments, Parameters
14+
from .loops import from_array
1615
from .sweepers import INT_TYPE, NORMALIZERS, SWEEPER_METHODS, normalize_phase
1716

1817

@@ -137,115 +136,6 @@ def _process_sweeper(sweeper: Sweeper, args: ExecutionArguments):
137136
return variable, values
138137

139138

140-
def _qua_for_loop(variables: list[Variable], values: list[npt.NDArray]):
141-
"""Generate QUA ``for_`` loop command for the sweeps.
142-
143-
Partly copied from ``qualang_tools.from_array``.
144-
"""
145-
if len(variables) > 1:
146-
return for_each_(variables, values)
147-
148-
var = variables[0]
149-
array = values[0]
150-
151-
if len(array) == 0:
152-
raise ValueError("Sweeper values must have length > 0.")
153-
if len(array) == 1:
154-
return for_(var, array[0], var <= array[0], var + 1)
155-
156-
if not isinstance(var, Variable):
157-
raise TypeError("The first argument must be a QUA variable.")
158-
if (not isinstance(array[0], (np.generic, int, float))) or (
159-
isinstance(array[0], bool)
160-
):
161-
raise TypeError("The array must be an array of python variables.")
162-
163-
start = array[0]
164-
stop = array[-1]
165-
if var.is_fixed():
166-
if not (-8 <= start < 8) or not (-8 <= stop < 8):
167-
raise ValueError("fixed numbers are bounded to [-8, 8).")
168-
elif not var.is_int():
169-
raise TypeError(
170-
"This variable type is not supported. Please use a QUA 'int' or 'fixed' when sweeping."
171-
)
172-
173-
# Linear increment
174-
if np.isclose(np.std(np.diff(array)), 0) == "lin":
175-
step = array[1] - array[0]
176-
177-
if var.is_int():
178-
# Check that the array is an array of int with integer increments
179-
if not all(float(x).is_integer() for x in (step, start, stop)):
180-
raise TypeError(
181-
"When looping over a QUA int variable, the step and array elements must be integers."
182-
)
183-
184-
if step > 0:
185-
return for_(var, int(start), var <= int(stop), var + int(step))
186-
else:
187-
return for_(var, int(start), var >= int(stop), var + int(step))
188-
189-
elif var.is_fixed():
190-
# Generate the loop parameters for positive and negative steps
191-
if step > 0:
192-
return for_(
193-
var,
194-
float(start),
195-
var < float(stop) + float(step) / 2,
196-
var + float(step),
197-
)
198-
else:
199-
return for_(
200-
var,
201-
float(start),
202-
var > float(stop) + float(step) / 2,
203-
var + float(step),
204-
)
205-
206-
# Logarithmic increment
207-
if np.isclose(np.std(array[1:] / array[:-1]), 0, atol=1e-3):
208-
step = array[1] / array[0]
209-
210-
if var.is_int():
211-
if step > 1:
212-
if int(round(start) * float(step)) == int(round(start)):
213-
return for_each_(var, array)
214-
else:
215-
return for_(
216-
var,
217-
round(start),
218-
var < round(stop) * np.sqrt(float(step)),
219-
Cast.mul_int_by_fixed(var, float(step)),
220-
)
221-
else:
222-
return for_(
223-
var,
224-
round(start),
225-
var > round(stop) / np.sqrt(float(step)),
226-
Cast.mul_int_by_fixed(var, float(step)),
227-
)
228-
229-
elif var.is_fixed():
230-
if step > 1:
231-
return for_(
232-
var,
233-
float(start),
234-
var < float(stop) * np.sqrt(float(step)),
235-
var * float(step),
236-
)
237-
else:
238-
return for_(
239-
var,
240-
float(start),
241-
var > float(stop) * np.sqrt(float(step)),
242-
var * float(step),
243-
)
244-
245-
# Non-(linear or logarithmic) increment requires ``for_each_``
246-
return for_each_(var, array)
247-
248-
249139
def sweep(
250140
sweepers: list[ParallelSweepers],
251141
args: ExecutionArguments,
@@ -260,7 +150,10 @@ def sweep(
260150
variables, values = zip(
261151
*(_process_sweeper(sweeper, args) for sweeper in parallel_sweepers)
262152
)
263-
loop = _qua_for_loop(variables, values)
153+
if len(parallel_sweepers) > 1:
154+
loop = qua.for_each_(variables, values)
155+
else:
156+
loop = for_(*from_array(variables[0], values[0]))
264157

265158
with loop:
266159
for sweeper, variable in zip(parallel_sweepers, variables):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import warnings
2+
from typing import Union
3+
4+
import numpy as np
5+
import numpy.typing as npt
6+
from qm.qua import Cast, Variable
7+
8+
9+
def from_array(var: Variable, array: Union[npt.NDArray, list]):
10+
"""Function parametrizing the QUA `for_` loop from a python array.
11+
12+
Taken from qualang_tools to avoid the dependency.
13+
14+
Args:
15+
var: the QUA variable that will be looped over (int or fixed).
16+
array: a Python list or numpy array containing the values over which `a` will be looping.
17+
The spacing must be even in linear or logarithmic scales and it cannot be a QUA array.
18+
19+
Returns:
20+
QUA for_ loop parameters (var, init, cond, update) as defined in https://qm-docs.qualang.io/api_references/qua/dsl_main?highlight=for_#qm.qua._dsl.for_.
21+
"""
22+
23+
# Check for array length
24+
if len(array) == 0:
25+
raise Exception("The array must be of length > 0.")
26+
elif len(array) == 1:
27+
return var, array[0], var <= array[0], var + 1
28+
# Check QUA vs python variables
29+
if not isinstance(var, Variable):
30+
raise Exception("The first argument must be a QUA variable.")
31+
if (not isinstance(array[0], (np.generic, int, float))) or (
32+
isinstance(array[0], bool)
33+
):
34+
raise Exception("The array must be an array of python variables.")
35+
# Check array increment
36+
if np.isclose(np.std(np.diff(array)), 0):
37+
increment = "lin"
38+
elif np.isclose(np.std(array[1:] / array[:-1]), 0, atol=1e-3):
39+
increment = "log"
40+
else:
41+
raise Exception(
42+
"The spacing of the input array must be even in linear or logarithmic scales. Please use `for_each_()` for arbitrary scans."
43+
)
44+
45+
# Get the type of the specified QUA variable
46+
start = array[0]
47+
stop = array[-1]
48+
# Linear increment
49+
if increment == "lin":
50+
step = array[1] - array[0]
51+
52+
if var.is_int():
53+
# Check that the array is an array of int with integer increments
54+
if (
55+
not float(step).is_integer()
56+
or not float(start).is_integer()
57+
or not float(stop).is_integer()
58+
):
59+
raise Exception(
60+
"When looping over a QUA int variable, the step and array elements must be integers."
61+
)
62+
# Generate the loop parameters for positive and negative steps
63+
if step > 0:
64+
return var, int(start), var <= int(stop), var + int(step)
65+
else:
66+
return var, int(start), var >= int(stop), var + int(step)
67+
68+
elif var.is_fixed():
69+
# Check for fixed number overflows
70+
if not (-8 <= start < 8) and not (-8 <= stop < 8):
71+
raise Exception("fixed numbers are bounded to [-8, 8).")
72+
73+
# Generate the loop parameters for positive and negative steps
74+
if step > 0:
75+
return (
76+
var,
77+
float(start),
78+
var < float(stop) + float(step) / 2,
79+
var + float(step),
80+
)
81+
else:
82+
return (
83+
var,
84+
float(start),
85+
var > float(stop) + float(step) / 2,
86+
var + float(step),
87+
)
88+
else:
89+
raise Exception(
90+
"This variable type is not supported. Please use a QUA 'int' or 'fixed' or contact a QM member for assistance."
91+
)
92+
# Logarithmic increment
93+
elif increment == "log":
94+
step = array[1] / array[0]
95+
96+
if var.is_int():
97+
warnings.warn(
98+
"When using logarithmic increments with QUA integers, the resulting values will slightly differ from the ones in numpy.logspace() because of rounding errors. \n Please use the get_equivalent_log_array() function to get the exact values taken by the QUA variable and note that the number of points may also change."
99+
)
100+
if step > 1:
101+
if int(round(start) * float(step)) == int(round(start)):
102+
raise ValueError(
103+
"Two successive values in the scan are equal after being cast to integers which will make the QUA for_ loop fail. \nEither increase the logarithmic step or use for_each_(): https://docs.quantum-machines.co/1.1.6/qm-qua-sdk/docs/Guides/features/?h=for_ea#for_each."
104+
)
105+
else:
106+
return (
107+
var,
108+
round(start),
109+
var < round(stop) * np.sqrt(float(step)),
110+
Cast.mul_int_by_fixed(var, float(step)),
111+
)
112+
else:
113+
return (
114+
var,
115+
round(start),
116+
var > round(stop) / np.sqrt(float(step)),
117+
Cast.mul_int_by_fixed(var, float(step)),
118+
)
119+
120+
elif var.is_fixed():
121+
# Check for fixed number overflows
122+
if not (-8 <= start < 8) and not (-8 <= stop < 8):
123+
raise Exception("fixed numbers are bounded to [-8, 8).")
124+
125+
if step > 1:
126+
return (
127+
var,
128+
float(start),
129+
var < float(stop) * np.sqrt(float(step)),
130+
var * float(step),
131+
)
132+
else:
133+
return (
134+
var,
135+
float(start),
136+
var > float(stop) * np.sqrt(float(step)),
137+
var * float(step),
138+
)
139+
else:
140+
raise Exception(
141+
"This variable type is not supported. Please use a QUA 'int' or 'fixed' or contact a QM member for assistance."
142+
)

0 commit comments

Comments
 (0)