Skip to content

Commit

Permalink
Merge pull request #905 from qiboteam/qmdrivefreq
Browse files Browse the repository at this point in the history
Hotfix to allow playing different frequencies on the same qubit with QM
  • Loading branch information
scarrazza committed May 23, 2024
2 parents 49f370a + dd70a1b commit 1405013
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 103 deletions.
206 changes: 108 additions & 98 deletions src/qibolab/instruments/qm/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from qibolab.pulses import PulseType, Rectangular

from .ports import OPXIQ, OctaveInput, OctaveOutput
from .ports import OPXIQ, OctaveInput, OctaveOutput, OPXOutput

SAMPLING_RATE = 1
"""Sampling rate of Quantum Machines OPX in GSps."""
Expand Down Expand Up @@ -92,6 +92,22 @@ def iq_imbalance(g, phi):
float(N * x) for x in [(1 - g) * c, (1 + g) * s, (1 - g) * s, (1 + g) * c]
]

def _new_frequency_element(self, qubit, intermediate_frequency, mode="drive"):
"""Register element on existing port but with different frequency."""
element = f"{mode}{qubit.name}"
current_if = self.elements[element]["intermediate_frequency"]
if intermediate_frequency == current_if:
return element

if isinstance(getattr(qubit, mode).port, (OPXIQ, OPXOutput)):
raise NotImplementedError(
f"Cannot play two different frequencies on the same {mode} line."
)
new_element = f"{element}_{intermediate_frequency}"
self.elements[new_element] = dict(self.elements[f"drive{qubit.name}"])
self.elements[new_element]["intermediate_frequency"] = intermediate_frequency
return new_element

def register_drive_element(self, qubit, intermediate_frequency=0):
"""Register qubit drive elements and controllers in the QM config.
Expand All @@ -101,45 +117,41 @@ def register_drive_element(self, qubit, intermediate_frequency=0):
will send to this qubit. This frequency will be mixed with the
LO connected to the same channel.
"""
if f"drive{qubit.name}" not in self.elements:
if isinstance(qubit.drive.port, OPXIQ):
lo_frequency = math.floor(qubit.drive.lo_frequency)
self.elements[f"drive{qubit.name}"] = {
"mixInputs": {
"I": qubit.drive.port.i.pair,
"Q": qubit.drive.port.q.pair,
"lo_frequency": lo_frequency,
"mixer": f"mixer_drive{qubit.name}",
},
}
drive_g = qubit.mixer_drive_g
drive_phi = qubit.mixer_drive_phi
self.mixers[f"mixer_drive{qubit.name}"] = [
{
"intermediate_frequency": intermediate_frequency,
"lo_frequency": lo_frequency,
"correction": self.iq_imbalance(drive_g, drive_phi),
}
]
else:
self.elements[f"drive{qubit.name}"] = {
"RF_inputs": {"port": qubit.drive.port.pair},
"digitalInputs": qubit.drive.port.digital_inputs,
}
self.elements[f"drive{qubit.name}"].update(
element = f"drive{qubit.name}"
if element in self.elements:
return self._new_frequency_element(qubit, intermediate_frequency, "drive")

if isinstance(qubit.drive.port, OPXIQ):
lo_frequency = math.floor(qubit.drive.lo_frequency)
self.elements[element] = {
"mixInputs": {
"I": qubit.drive.port.i.pair,
"Q": qubit.drive.port.q.pair,
"lo_frequency": lo_frequency,
"mixer": f"mixer_drive{qubit.name}",
},
}
drive_g = qubit.mixer_drive_g
drive_phi = qubit.mixer_drive_phi
self.mixers[f"mixer_drive{qubit.name}"] = [
{
"intermediate_frequency": intermediate_frequency,
"operations": {},
"lo_frequency": lo_frequency,
"correction": self.iq_imbalance(drive_g, drive_phi),
}
)
]
else:
self.elements[f"drive{qubit.name}"][
"intermediate_frequency"
] = intermediate_frequency
if isinstance(qubit.drive.port, OPXIQ):
self.mixers[f"mixer_drive{qubit.name}"][0][
"intermediate_frequency"
] = intermediate_frequency
self.elements[element] = {
"RF_inputs": {"port": qubit.drive.port.pair},
"digitalInputs": qubit.drive.port.digital_inputs,
}
self.elements[element].update(
{
"intermediate_frequency": intermediate_frequency,
"operations": {},
}
)
return element

def register_readout_element(
self, qubit, intermediate_frequency=0, time_of_flight=0, smearing=0
Expand All @@ -152,53 +164,48 @@ def register_readout_element(
will send to this qubit. This frequency will be mixed with the
LO connected to the same channel.
"""
if f"readout{qubit.name}" not in self.elements:
if isinstance(qubit.readout.port, OPXIQ):
lo_frequency = math.floor(qubit.readout.lo_frequency)
self.elements[f"readout{qubit.name}"] = {
"mixInputs": {
"I": qubit.readout.port.i.pair,
"Q": qubit.readout.port.q.pair,
"lo_frequency": lo_frequency,
"mixer": f"mixer_readout{qubit.name}",
},
"outputs": {
"out1": qubit.feedback.port.i.pair,
"out2": qubit.feedback.port.q.pair,
},
}
readout_g = qubit.mixer_readout_g
readout_phi = qubit.mixer_readout_phi
self.mixers[f"mixer_readout{qubit.name}"] = [
{
"intermediate_frequency": intermediate_frequency,
"lo_frequency": lo_frequency,
"correction": self.iq_imbalance(readout_g, readout_phi),
}
]
else:

self.elements[f"readout{qubit.name}"] = {
"RF_inputs": {"port": qubit.readout.port.pair},
"RF_outputs": {"port": qubit.feedback.port.pair},
"digitalInputs": qubit.readout.port.digital_inputs,
}
self.elements[f"readout{qubit.name}"].update(
element = f"readout{qubit.name}"
if element in self.elements:
return self._new_frequency_element(qubit, intermediate_frequency, "readout")

if isinstance(qubit.readout.port, OPXIQ):
lo_frequency = math.floor(qubit.readout.lo_frequency)
self.elements[element] = {
"mixInputs": {
"I": qubit.readout.port.i.pair,
"Q": qubit.readout.port.q.pair,
"lo_frequency": lo_frequency,
"mixer": f"mixer_readout{qubit.name}",
},
"outputs": {
"out1": qubit.feedback.port.i.pair,
"out2": qubit.feedback.port.q.pair,
},
}
readout_g = qubit.mixer_readout_g
readout_phi = qubit.mixer_readout_phi
self.mixers[f"mixer_readout{qubit.name}"] = [
{
"intermediate_frequency": intermediate_frequency,
"operations": {},
"time_of_flight": time_of_flight,
"smearing": smearing,
"lo_frequency": lo_frequency,
"correction": self.iq_imbalance(readout_g, readout_phi),
}
)
]
else:
self.elements[f"readout{qubit.name}"][
"intermediate_frequency"
] = intermediate_frequency
if isinstance(qubit.readout.port, OPXIQ):
self.mixers[f"mixer_readout{qubit.name}"][0][
"intermediate_frequency"
] = intermediate_frequency
self.elements[element] = {
"RF_inputs": {"port": qubit.readout.port.pair},
"RF_outputs": {"port": qubit.feedback.port.pair},
"digitalInputs": qubit.readout.port.digital_inputs,
}
self.elements[element].update(
{
"intermediate_frequency": intermediate_frequency,
"operations": {},
"time_of_flight": time_of_flight,
"smearing": smearing,
}
)
return element

def register_flux_element(self, qubit, intermediate_frequency=0):
"""Register qubit flux elements and controllers in the QM config.
Expand All @@ -209,39 +216,42 @@ def register_flux_element(self, qubit, intermediate_frequency=0):
will send to this qubit. This frequency will be mixed with the
LO connected to the same channel.
"""
if f"flux{qubit.name}" not in self.elements:
self.elements[f"flux{qubit.name}"] = {
"singleInput": {
"port": qubit.flux.port.pair,
},
"intermediate_frequency": intermediate_frequency,
"operations": {},
}
else:
self.elements[f"flux{qubit.name}"][
"intermediate_frequency"
] = intermediate_frequency
element = f"flux{qubit.name}"
if element in self.elements:
return self._new_frequency_element(qubit, intermediate_frequency, "flux")

self.elements[element] = {
"singleInput": {
"port": qubit.flux.port.pair,
},
"intermediate_frequency": intermediate_frequency,
"operations": {},
}
return element

def register_element(self, qubit, pulse, time_of_flight=0, smearing=0):
if pulse.type is PulseType.DRIVE:
# register drive element
if_frequency = pulse.frequency - math.floor(qubit.drive.lo_frequency)
self.register_drive_element(qubit, if_frequency)
element = self.register_drive_element(qubit, if_frequency)
# register flux element (if available)
if qubit.flux:
self.register_flux_element(qubit)
elif pulse.type is PulseType.READOUT:
# register readout element (if it does not already exist)
if_frequency = pulse.frequency - math.floor(qubit.readout.lo_frequency)
self.register_readout_element(qubit, if_frequency, time_of_flight, smearing)
element = self.register_readout_element(
qubit, if_frequency, time_of_flight, smearing
)
# register flux element (if available)
if qubit.flux:
self.register_flux_element(qubit)
else:
# register flux element
self.register_flux_element(qubit, pulse.frequency)
element = self.register_flux_element(qubit, pulse.frequency)
return element

def register_pulse(self, qubit, qmpulse):
def register_pulse(self, qubit, qmpulse, element=None):
"""Registers pulse, waveforms and integration weights in QM config.
Args:
Expand All @@ -266,7 +276,7 @@ def register_pulse(self, qubit, qmpulse):
"digital_marker": "ON",
}
# register drive pulse in elements
self.elements[f"drive{qubit.name}"]["operations"][
self.elements[qmpulse.element]["operations"][
qmpulse.operation
] = qmpulse.operation

Expand All @@ -280,7 +290,7 @@ def register_pulse(self, qubit, qmpulse):
},
}
# register flux pulse in elements
self.elements[f"flux{qubit.name}"]["operations"][
self.elements[qmpulse.element]["operations"][
qmpulse.operation
] = qmpulse.operation

Expand All @@ -303,7 +313,7 @@ def register_pulse(self, qubit, qmpulse):
"digital_marker": "ON",
}
# register readout pulse in elements
self.elements[f"readout{qubit.name}"]["operations"][
self.elements[qmpulse.element]["operations"][
qmpulse.operation
] = qmpulse.operation

Expand Down
8 changes: 4 additions & 4 deletions src/qibolab/instruments/qm/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,21 +298,21 @@ def create_sequence(self, qubits, sequence, sweepers):
if pulse.type is PulseType.READOUT:
self.config.register_port(qubit.feedback.port)

self.config.register_element(
element = self.config.register_element(
qubit, pulse, self.time_of_flight, self.smearing
)
if (
pulse.duration % 4 != 0
or pulse.duration < 16
or pulse.serial in pulses_to_bake
):
qmpulse = BakedPulse(pulse)
qmpulse = BakedPulse(pulse, element)
qmpulse.bake(self.config, durations=[pulse.duration])
else:
qmpulse = QMPulse(pulse)
qmpulse = QMPulse(pulse, element)
if pulse.type is PulseType.READOUT:
ro_pulses.append(qmpulse)
self.config.register_pulse(qubit, qmpulse)
self.config.register_pulse(qubit, qmpulse, element)
qmsequence.add(qmpulse)

qmsequence.shift()
Expand Down
3 changes: 2 additions & 1 deletion src/qibolab/instruments/qm/sequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ class QMPulse:
def __post_init__(self):
pulse_type = self.pulse.type.name.lower()
amplitude = format(self.pulse.amplitude, ".6f").rstrip("0").rstrip(".")
self.element: str = f"{pulse_type}{self.pulse.qubit}"
if self.element is None:
self.element = f"{pulse_type}{self.pulse.qubit}"
self.operation: str = (
f"{pulse_type}({self.pulse.duration}, {amplitude}, {self.pulse.shape})"
)
Expand Down
31 changes: 31 additions & 0 deletions tests/test_instruments_qm.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,37 @@ def test_qm_register_flux_pulse(qmplatform):
assert target_pulse["waveforms"]["single"] in controller.config.waveforms


def test_qm_register_pulses_with_different_frequencies(qmplatform):
platform = qmplatform
controller = platform.instruments["qm"]
qubit = next(iter(platform.qubits.keys()))
qd_pulse1 = platform.create_RX_pulse(qubit, start=0)
qd_pulse2 = platform.create_RX_pulse(qubit, start=qd_pulse1.finish)
qd_pulse2.frequency = qd_pulse2.frequency - int(5e6)
ro_pulse1 = platform.create_MZ_pulse(qubit, start=qd_pulse2.finish)
ro_pulse2 = platform.create_MZ_pulse(qubit, start=qd_pulse2.finish)
ro_pulse2.frequency = ro_pulse2.frequency + int(5e6)

sequence = PulseSequence()
sequence.add(qd_pulse1)
sequence.add(qd_pulse2)
sequence.add(ro_pulse1)
sequence.add(ro_pulse2)

if qmplatform.name == "qm_octave":
qmsequence, ro_pulses = controller.create_sequence(
platform.qubits, sequence, []
)
assert len(qmsequence.qmpulses) == 4
elements = {qmpulse.element for qmpulse in qmsequence.qmpulses}
assert len(elements) == 4
else:
with pytest.raises(NotImplementedError):
qmsequence, ro_pulses = controller.create_sequence(
platform.qubits, sequence, []
)


@pytest.mark.parametrize("duration", [0, 30])
def test_qm_register_baked_pulse(qmplatform, duration):
platform = qmplatform
Expand Down

0 comments on commit 1405013

Please sign in to comment.