Skip to content

Commit

Permalink
Merge pull request #993 from qiboteam/qblox-ad-hoc-features
Browse files Browse the repository at this point in the history
collection of small fixes, features, and random pieces of code
  • Loading branch information
hay-k authored Oct 25, 2024
2 parents 32187d4 + f85447a commit 4892a5f
Show file tree
Hide file tree
Showing 14 changed files with 486 additions and 107 deletions.
65 changes: 52 additions & 13 deletions src/qibolab/instruments/qblox/cluster_qcm_bb.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,25 +201,54 @@ def setup(self, **settings):
"""
pass

def _get_next_sequencer(self, port, frequency, qubits: dict):
"""Retrieves and configures the next avaliable sequencer.
def _get_next_sequencer(
self, port: str, frequency: float, qubits: dict, couplers: dict
):
"""Retrieves and configures the next available sequencer.
The parameters of the new sequencer are copied from those of the default sequencer, except for the
intermediate frequency and classification parameters.
Args:
port (str):
frequency ():
qubit ():
port: name of the output port
frequency: NCO frequency
qubits: qubits associated with this sequencer
couplers: couplers associated with this sequencer
Raises:
Exception = If attempting to set a parameter without a connection to the instrument.
"""
# select the qubit with flux line, if present, connected to the specific port
# check if this port is responsible for the flux of any qubit or coupler
qubit = None
for _qubit in qubits.values():
name = _qubit.flux.port.name
module = _qubit.flux.port.module
if _qubit.flux is not None and (name, module) == (port, self):
qubit = _qubit
if _qubit.flux.port is not None:
if (
_qubit.flux.port.name == port
and _qubit.flux.port.module.name == self.name
):
qubit = _qubit
else:
log.warning(f"Qubit {_qubit.name} has no flux line connected")

coupler = None
for _coupler in couplers.values():
if _coupler.flux.port is not None:
if (
_coupler.flux.port.name == port
and _coupler.flux.port.module.name == self.name
):
coupler = _coupler
else:
log.warning(f"Coupler {_coupler.name} has no flux line connected")

if qubit and coupler:
raise ValueError(
f"Port {port} of device {self.name} is configured for more than one line (flux lines of qubit {qubit.name} and coupler {coupler.name}"
)
if qubit:
self._ports[port].offset = qubit.sweetspot
elif coupler:
self._ports[port].offset = coupler.sweetspot
else:
self._ports[port].offset = 0

# select a new sequencer and configure it as required
next_sequencer_number = self._free_sequencers_numbers.pop(0)
Expand Down Expand Up @@ -248,6 +277,7 @@ def _get_next_sequencer(self, port, frequency, qubits: dict):
# create sequencer wrapper
sequencer = Sequencer(next_sequencer_number)
sequencer.qubit = qubit.name if qubit else None
sequencer.coupler = coupler.name if coupler else None
return sequencer

def get_if(self, pulse):
Expand All @@ -267,6 +297,7 @@ def get_if(self, pulse):
def process_pulse_sequence(
self,
qubits: dict,
couplers: dict,
instrument_pulses: PulseSequence,
navgs: int,
nshots: int,
Expand Down Expand Up @@ -349,6 +380,7 @@ def process_pulse_sequence(
port=port,
frequency=self.get_if(non_overlapping_pulses[0]),
qubits=qubits,
couplers=couplers,
)
# add the sequencer to the list of sequencers required by the port
self._sequencers[port].append(sequencer)
Expand Down Expand Up @@ -383,12 +415,13 @@ def process_pulse_sequence(
port=port,
frequency=self.get_if(non_overlapping_pulses[0]),
qubits=qubits,
couplers=couplers,
)
# add the sequencer to the list of sequencers required by the port
self._sequencers[port].append(sequencer)
else:
sequencer = self._get_next_sequencer(
port=port, frequency=0, qubits=qubits
port=port, frequency=0, qubits=qubits, couplers=couplers
)
# add the sequencer to the list of sequencers required by the port
self._sequencers[port].append(sequencer)
Expand Down Expand Up @@ -493,7 +526,13 @@ def process_pulse_sequence(
)

else: # qubit_sweeper_parameters
if sequencer.qubit in [qubit.name for qubit in sweeper.qubits]:
if (
sweeper.qubits
and sequencer.qubit in [q.name for q in sweeper.qubits]
) or (
sweeper.couplers
and sequencer.coupler in [c.name for c in sweeper.couplers]
):
# plays an active role
if sweeper.parameter == Parameter.bias:
reference_value = self._ports[port].offset
Expand Down Expand Up @@ -616,7 +655,7 @@ def process_pulse_sequence(
and pulses[n].sweeper.type == QbloxSweeperType.duration
):
RI = pulses[n].sweeper.register
if pulses[n].type == PulseType.FLUX:
if pulses[n].type in (PulseType.FLUX, PulseType.COUPLERFLUX):
RQ = pulses[n].sweeper.register
else:
RQ = pulses[n].sweeper.aux_register
Expand Down
7 changes: 6 additions & 1 deletion src/qibolab/instruments/qblox/cluster_qcm_rf.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,10 @@ def connect(self, cluster: Cluster = None):
self._ports[port].lo_frequency = self.settings[port][
"lo_frequency"
]
if "mixer_calibration" in self.settings[port]:
self._ports[port].mixer_calibration = self.settings[port][
"mixer_calibration"
]
self._ports[port].attenuation = self.settings[port]["attenuation"]
self._ports[port].hardware_mod_en = True
self._ports[port].nco_freq = 0
Expand Down Expand Up @@ -289,6 +293,7 @@ def get_if(self, pulse):
def process_pulse_sequence(
self,
qubits: dict,
couplers: dict,
instrument_pulses: PulseSequence,
navgs: int,
nshots: int,
Expand Down Expand Up @@ -610,7 +615,7 @@ def process_pulse_sequence(
and pulses[n].sweeper.type == QbloxSweeperType.duration
):
RI = pulses[n].sweeper.register
if pulses[n].type == PulseType.FLUX:
if pulses[n].type in (PulseType.FLUX, PulseType.COUPLERFLUX):
RQ = pulses[n].sweeper.register
else:
RQ = pulses[n].sweeper.aux_register
Expand Down
15 changes: 13 additions & 2 deletions src/qibolab/instruments/qblox/cluster_qrm_rf.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,10 @@ def connect(self, cluster: Cluster = None):
self._ports["o1"].lo_frequency = self.settings["o1"][
"lo_frequency"
]
if "mixer_calibration" in self.settings["o1"]:
self._ports["o1"].mixer_calibration = self.settings["o1"][
"mixer_calibration"
]
self._ports["o1"].hardware_mod_en = True
self._ports["o1"].nco_freq = 0
self._ports["o1"].nco_phase_offs = 0
Expand Down Expand Up @@ -337,6 +341,7 @@ def get_if(self, pulse: Pulse):
def process_pulse_sequence(
self,
qubits: dict,
couplers: dict,
instrument_pulses: PulseSequence,
navgs: int,
nshots: int,
Expand Down Expand Up @@ -740,7 +745,10 @@ def process_pulse_sequence(
and pulses[n].sweeper.type == QbloxSweeperType.duration
):
RI = pulses[n].sweeper.register
if pulses[n].type == PulseType.FLUX:
if pulses[n].type in (
PulseType.FLUX,
PulseType.COUPLERFLUX,
):
RQ = pulses[n].sweeper.register
else:
RQ = pulses[n].sweeper.aux_register
Expand Down Expand Up @@ -787,7 +795,10 @@ def process_pulse_sequence(
and pulses[n].sweeper.type == QbloxSweeperType.duration
):
RI = pulses[n].sweeper.register
if pulses[n].type == PulseType.FLUX:
if pulses[n].type in (
PulseType.FLUX,
PulseType.COUPLERFLUX,
):
RQ = pulses[n].sweeper.register
else:
RQ = pulses[n].sweeper.aux_register
Expand Down
75 changes: 68 additions & 7 deletions src/qibolab/instruments/qblox/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from qibolab.instruments.qblox.cluster_qcm_rf import QcmRf
from qibolab.instruments.qblox.cluster_qrm_rf import QrmRf
from qibolab.instruments.qblox.sequencer import SAMPLING_RATE
from qibolab.pulses import PulseSequence, PulseType
from qibolab.pulses import Custom, PulseSequence, PulseType, ReadoutPulse
from qibolab.result import SampleResults
from qibolab.sweeper import Parameter, Sweeper, SweeperType
from qibolab.unrolling import Bounds
Expand Down Expand Up @@ -102,7 +102,7 @@ def _termination_handler(self, signum, frame):
log.warning("QbloxController: all modules are disconnected.")
exit(0)

def _set_module_channel_map(self, module: QrmRf, qubits: dict):
def _set_module_channel_map(self, module: QrmRf, qubits: dict, couplers: dict):
"""Retrieve all the channels connected to a specific Qblox module.
This method updates the `channel_port_map` attribute of the
Expand All @@ -115,11 +115,16 @@ def _set_module_channel_map(self, module: QrmRf, qubits: dict):
for channel in qubit.channels:
if channel.port and channel.port.module.name == module.name:
module.channel_map[channel.name] = channel
for coupler in couplers.values():
for channel in coupler.channels:
if channel.port and channel.port.module.name == module.name:
module.channel_map[channel.name] = channel
return list(module.channel_map)

def _execute_pulse_sequence(
self,
qubits: dict,
couplers: dict,
sequence: PulseSequence,
options: ExecutionParameters,
sweepers: list() = [], # list(Sweeper) = []
Expand Down Expand Up @@ -172,12 +177,13 @@ def _execute_pulse_sequence(
data = {}
for name, module in self.modules.items():
# from the pulse sequence, select those pulses to be synthesised by the module
module_channels = self._set_module_channel_map(module, qubits)
module_channels = self._set_module_channel_map(module, qubits, couplers)
module_pulses[name] = sequence.get_channel_pulses(*module_channels)

# ask each module to generate waveforms & program and upload them to the device
module.process_pulse_sequence(
qubits,
couplers,
module_pulses[name],
navgs,
nshots,
Expand Down Expand Up @@ -228,7 +234,7 @@ def _execute_pulse_sequence(
return data

def play(self, qubits, couplers, sequence, options):
return self._execute_pulse_sequence(qubits, sequence, options)
return self._execute_pulse_sequence(qubits, couplers, sequence, options)

def sweep(
self,
Expand Down Expand Up @@ -269,10 +275,56 @@ def sweep(
values=sweeper.values,
pulses=ps,
qubits=sweeper.qubits,
couplers=sweeper.couplers,
type=sweeper.type,
)
)

serial_map = {p.serial: p.serial for p in sequence_copy.ro_pulses}
for sweeper in sweepers_copy:
if sweeper.parameter in (Parameter.duration, Parameter.start) and not any(
pulse.type is PulseType.READOUT for pulse in sweeper.pulses
):
for pulse in sequence_copy.ro_pulses:
current_serial = pulse.serial
if sweeper.parameter is Parameter.duration:
sweep_values = sweeper.get_values(sweeper.pulses[0].duration)
if (
max_finish := sweeper.pulses[0].start + np.max(sweep_values)
) > pulse.start:
pulse.start = max_finish
serial_map[pulse.serial] = current_serial
if sweeper.parameter is Parameter.start:
idx = sequence_copy.index(pulse)
padded_pulse = ReadoutPulse(
start=0,
duration=pulse.start + pulse.duration,
amplitude=pulse.amplitude,
frequency=pulse.frequency,
relative_phase=pulse.relative_phase,
shape=Custom(
envelope_i=np.concatenate(
(
np.zeros(pulse.start),
pulse.envelope_waveform_i().data
/ pulse.amplitude,
)
),
envelope_q=np.concatenate(
(
np.zeros(pulse.start),
pulse.envelope_waveform_q().data
/ pulse.amplitude,
)
),
),
channel=pulse.channel,
qubit=pulse.qubit,
)
serial_map[padded_pulse.serial] = current_serial
sequence_copy[idx] = padded_pulse
sweeper.pulses.append(padded_pulse)

# reverse sweepers exept for res punchout att
contains_attenuation_frequency = any(
sweepers_copy[i].parameter == Parameter.attenuation
Expand All @@ -292,6 +344,7 @@ def sweep(
# execute the each sweeper recursively
self._sweep_recursion(
qubits,
couplers,
sequence_copy,
options,
*tuple(sweepers_copy),
Expand All @@ -301,13 +354,14 @@ def sweep(
# return the results using the original serials
serial_results = {}
for pulse in sequence_copy.ro_pulses:
serial_results[map_id_serial[pulse.id]] = id_results[pulse.id]
serial_results[serial_map[map_id_serial[pulse.id]]] = id_results[pulse.id]
serial_results[pulse.qubit] = id_results[pulse.id]
return serial_results

def _sweep_recursion(
self,
qubits,
couplers,
sequence,
options: ExecutionParameters,
*sweepers,
Expand Down Expand Up @@ -386,14 +440,18 @@ def _sweep_recursion(
if len(sweepers) > 1:
self._sweep_recursion(
qubits,
couplers,
sequence,
options,
*sweepers[1:],
results=results,
)
else:
result = self._execute_pulse_sequence(
qubits=qubits, sequence=sequence, options=options
qubits=qubits,
couplers=couplers,
sequence=sequence,
options=options,
)
for pulse in sequence.ro_pulses:
if results[pulse.id]:
Expand Down Expand Up @@ -437,9 +495,11 @@ def _sweep_recursion(
values=_values,
pulses=sweeper.pulses,
qubits=sweeper.qubits,
couplers=sweeper.couplers,
)
self._sweep_recursion(
qubits,
couplers,
sequence,
options,
*((split_sweeper,) + sweepers[1:]),
Expand Down Expand Up @@ -486,7 +546,7 @@ def _sweep_recursion(
# qubits[pulse.qubit].drive.gain = 1

result = self._execute_pulse_sequence(
qubits, sequence, options, sweepers
qubits, couplers, sequence, options, sweepers
)
self._add_to_results(sequence, results, result)
else:
Expand All @@ -509,6 +569,7 @@ def _sweep_recursion(

res = self._execute_pulse_sequence(
qubits,
couplers,
sequence,
replace(options, nshots=_nshots),
sweepers,
Expand Down
Loading

0 comments on commit 4892a5f

Please sign in to comment.