1515from qibolab ._core .instruments .abstract import Controller
1616from qibolab ._core .pulses .pulse import PulseId
1717from qibolab ._core .sequence import PulseSequence
18+ from qibolab ._core .serialize import Model
1819from qibolab ._core .sweeper import ParallelSweepers , normalize_sweepers
1920
2021from . import config
3132SAMPLING_RATE = 1
3233
3334
35+ def _compute_duration (
36+ ps : PulseSequence ,
37+ sweepers : list [ParallelSweepers ],
38+ options : ExecutionParameters ,
39+ configs : Configs ,
40+ ) -> float :
41+ """Compute the total program duration including time of flight and
42+ synchronization waiting time.
43+ """
44+
45+ # TODO: include the time of flight calculation at the level of
46+ # Platform.execute rather than in the qblox driver. This will require
47+ # propagating the changes also to qibocal.
48+ time_of_flight = max (
49+ [time_of_flights (configs )[ch [0 ]] for ch in ps if hasattr (ch [1 ], "acquisition" )],
50+ default = 0.0 ,
51+ )
52+
53+ # TODO: wait_sync duration is determined as explained in this comment
54+ # https://github.com/qiboteam/qibolab/pull/1389#issuecomment-3884129213.
55+ # It should be checked with Qblox if the sync time can indeed be of the
56+ # order of 1000 ns.
57+ wait_sync_duration = 1000
58+ duration = options .estimate_duration (
59+ [ps ], sweepers , time_of_flight + wait_sync_duration
60+ )
61+ return duration
62+
63+
64+ class ClusterConfigs (Model ):
65+ modules : dict [int , config .ModuleConfig ]
66+ sequencers : dict [int , dict [int , config .SequencerConfig ]]
67+
68+
3469class Cluster (Controller ):
3570 """Controller object for Qblox cluster."""
3671
@@ -134,13 +169,13 @@ def play(
134169
135170 # then configure modules and sequencers
136171 # (including sequences upload)
137- sequencers = self ._configure (
138- sequences_ , configs , options_ .acquisition_type
172+ sequencers , _ = self .configure (
173+ configs , options_ .acquisition_type , sequences = sequences_
139174 )
140175 log .status (self .cluster , sequencers )
141176
142177 # finally execute the experiment, and fetch results
143- duration = options_ . estimate_duration ([ ps ] , sweepers_ )
178+ duration = _compute_duration ( ps , sweepers_ , options_ , configs )
144179 data = self ._execute (
145180 sequencers , sequences_ , duration , options_ .acquisition_type
146181 )
@@ -161,18 +196,33 @@ def play(
161196 results |= concat_shots (psres , options )
162197 return results
163198
164- def _configure (
199+ def configure (
165200 self ,
166- sequences : dict [ChannelId , Q1Sequence ],
167201 configs : Configs ,
168- acquisition : AcquisitionType ,
169- ) -> SequencerMap :
202+ acquisition : AcquisitionType = AcquisitionType .INTEGRATION ,
203+ sequences : Optional [dict [ChannelId , Q1Sequence ]] = None ,
204+ ) -> tuple [SequencerMap , ClusterConfigs ]:
170205 """Configure modules and sequencers.
171206
172207 The return value consists of the association map from channels
173208 to sequencers, for each module.
209+
210+ For configuration testing purpose, it is possible to also configure modules and
211+ sequencers with no sequence provided. In which case, it will attempt to assign
212+ sequencers to all available channels (as opposed to just those involved in the
213+ experiment, and thus in the sequences).
214+ For the sake of simplifiying the usage of this function, a default acquisition
215+ type is provided (:attr:`AcquisitionType.INTEGRATION`). The only true
216+ alternative to this value is :attr:`AcquisitionType.RAW`, since further
217+ configurations are required to operate in scope mode.
174218 """
175219 sequencers = defaultdict (dict )
220+ exec_mode = sequences is not None
221+ sequences_ = defaultdict (lambda : None , sequences if exec_mode else {})
222+
223+ modcfgs = {}
224+ seqcfgs = {}
225+
176226 for slot , chs in self ._channels_by_module .items ():
177227 module = self ._modules [slot ]
178228
@@ -185,24 +235,42 @@ def _configure(
185235 los = config .module .los (self ._los , configs , ids )
186236 mixers = config .module .mixers (self ._mixers , configs , ids )
187237 # compute module configurations, and apply them
188- config .ModuleConfig .build (
189- channels , configs , los , mixers , module .is_qrm_type
190- ).apply (module )
238+ modcfg = modcfgs [slot ] = config .ModuleConfig .build (
239+ channels , configs , los , mixers
240+ )
241+ modcfg .apply (module )
242+ seqcfgs [slot ] = {}
191243
192- # configure all sequencers, and store active ones' association to channels
244+ # configure all sequencers, and store association to channels
193245 rf = module .is_rf_type
194246 for idx , ((ch , address ), sequencer ) in enumerate (
195247 zip (chs , module .sequencers )
196248 ):
197- seq = sequences .get (ch , Q1Sequence .empty ())
198- config .SequencerConfig .build (
199- address , seq , ch , self .channels , configs , acquisition , idx , rf
200- ).apply (sequencer )
201- # only collect active sequencers
202- if not seq .is_empty :
203- sequencers [slot ][ch ] = idx
249+ # only configure and register sequencer for active channels
250+ # for passive channels the sequencer operations are not relevant, e.g. a
251+ # flux channel with no registered pulse will still set an offset, but
252+ # this will happen at port level, and it is consumed in the
253+ # `ModuleConfig` above
254+ # if not in execution mode, cnfigure all channels, to test the
255+ # configuration itself
256+ if exec_mode and ch not in sequences :
257+ continue
258+
259+ seqcfg = seqcfgs [slot ][idx ] = config .SequencerConfig .build (
260+ address ,
261+ ch ,
262+ self .channels ,
263+ configs ,
264+ acquisition ,
265+ idx ,
266+ rf ,
267+ sequence = sequences_ [ch ],
268+ )
269+ seqcfg .apply (sequencer )
270+ # populate channel-to-sequencer mapping
271+ sequencers [slot ][ch ] = idx
204272
205- return sequencers
273+ return sequencers , ClusterConfigs ( modules = modcfgs , sequencers = seqcfgs )
206274
207275 def _execute (
208276 self ,
@@ -219,8 +287,8 @@ def _execute(
219287 module .arm_sequencer (seq )
220288 module .start_sequencer ()
221289
222- # approximately wait for experiment completion
223- time .sleep (duration + 1 )
290+ # wait for experiment completion
291+ time .sleep (duration )
224292
225293 # fetch acquired results
226294 acquisitions = {}
0 commit comments