Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for QM Octaves #733

Merged
merged 61 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
136ebf8
feat: introduce QM Octaves (wip)
stavros11 Dec 29, 2023
cbda43d
fix: add connectivity to octave section in config
stavros11 Dec 30, 2023
fbd07df
fix: working without octaves
stavros11 Dec 30, 2023
bf075a6
fix: bump qm-qua version
stavros11 Dec 30, 2023
5278b0f
feat: instrument settings in runcard for QM
stavros11 Jan 1, 2024
30ce83f
fix: fix qm devices serialization
stavros11 Jan 1, 2024
898232a
fix: inherit QM devices from Instrument
stavros11 Jan 1, 2024
fbc1f7c
fix: fix execution when using Octaves
stavros11 Jan 3, 2024
53799da
fix: fix duplicate port names for input/output in runcard
stavros11 Jan 3, 2024
2c2f679
fix: port serialization
stavros11 Jan 4, 2024
553870b
fix: port access from QMController
stavros11 Jan 4, 2024
527d1fe
Merge main
stavros11 Jan 4, 2024
e6e2825
fix: tests
stavros11 Jan 4, 2024
b994d10
docs: docstrings for QM devices
stavros11 Jan 4, 2024
cfe0144
docs: docstring for QM ports
stavros11 Jan 4, 2024
df310a9
refactor: simplify devices
stavros11 Jan 4, 2024
05ea6ea
test: test using qm_octave platform
stavros11 Jan 4, 2024
b39a5a2
test: fix mocker order
stavros11 Jan 4, 2024
235e22f
feat: Add method to calibrate mixers when using Octaves
stavros11 Jan 6, 2024
c5a1e27
test: Test newly introduced methods
stavros11 Jan 6, 2024
96656f5
feat: Support sequence unrolling for QM
stavros11 Jan 8, 2024
4c169d8
fix: Recover old QMPulse to fix pulse scheduling
stavros11 Jan 9, 2024
ecd1611
refactor: Remove QMSim class
stavros11 Jan 9, 2024
b4841f7
fix: Fix simulator results
stavros11 Jan 9, 2024
b935dfb
test: Update tests
stavros11 Jan 9, 2024
7d40211
Fix bug with singleshot discrimination
stavros11 Jan 10, 2024
0333f48
refactor: Simplify Ports dict
stavros11 Jan 12, 2024
9c5f966
Merge main
stavros11 Jan 12, 2024
1889424
Merge branch 'octaves' into qmunrolling
stavros11 Jan 12, 2024
b6dae8f
Merge main
stavros11 Jan 23, 2024
932725c
Review comments for controller and devices
stavros11 Jan 23, 2024
f8b3c16
Review comment about qubit.frequencies
stavros11 Jan 23, 2024
510e3ca
Replace port input with output
stavros11 Jan 23, 2024
6f85966
Fix conflicts
stavros11 Jan 23, 2024
8cb49d6
Merge main
stavros11 Jan 30, 2024
6a0d292
Merge branch 'main' into octaves
stavros11 Jan 31, 2024
8e09cb0
Merge branch 'octaves' into qmunrolling
stavros11 Jan 31, 2024
f3aac36
refactor: Move threshold and angle to ShotsAcquisition
stavros11 Feb 5, 2024
555b12f
refactor: Drop error
stavros11 Feb 5, 2024
50af0a8
refactor: Merge acquisition measure and save
stavros11 Feb 5, 2024
77d2d71
test: remove calibration path from testing platform
stavros11 Feb 5, 2024
7711fe9
fix: Mixers update when multiple pulses are used
stavros11 Feb 15, 2024
001f77f
Merge branch 'octaves' into qmunrolling
stavros11 Feb 15, 2024
e02af42
Merge main
stavros11 Feb 15, 2024
d1c8c8a
Merge branch 'octaves' into qmunrolling
stavros11 Feb 15, 2024
7af1dee
Set batch size for unrolling
stavros11 Feb 15, 2024
c1ccab7
fix: Remove default debug script file name
stavros11 Feb 16, 2024
1affed0
feat: Integration weights from qubit.kernel
stavros11 Feb 16, 2024
4889778
Merge branch 'octaves' into qmunrolling
stavros11 Feb 16, 2024
4509421
fix: Zero q-component for rectangular readout pulse
stavros11 Feb 19, 2024
72018be
Merge branch 'octaves' into qmunrolling
stavros11 Feb 19, 2024
ac31d56
fix: tests
stavros11 Feb 19, 2024
eb853ec
chore: naming conventions
stavros11 Feb 19, 2024
d5b8b45
refactor: simplify fetching
stavros11 Feb 19, 2024
0aaf828
refactor: simplify declare_acquisition
stavros11 Feb 19, 2024
27893f8
refactor: Lift result object creation to base Acquisition
stavros11 Feb 19, 2024
2d0420e
fix: names of result_cls
stavros11 Feb 19, 2024
5f77a71
fix: cast samples to uint32
stavros11 Feb 19, 2024
12841b3
Convert acquisitions to list
stavros11 Feb 20, 2024
8af9d88
Merge pull request #738 from qiboteam/qmunrolling
stavros11 Feb 20, 2024
3cf00f2
Merge branch 'main' into octaves
stavros11 Feb 20, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/source/main-documentation/qibolab.rst
Original file line number Diff line number Diff line change
Expand Up @@ -703,7 +703,7 @@ A list of all the supported instruments follows:
Controllers (subclasses of :class:`qibolab.instruments.abstract.Controller`):
- Dummy Instrument: :class:`qibolab.instruments.dummy.DummyInstrument`
- Zurich Instruments: :class:`qibolab.instruments.zhinst.Zurich`
- Quantum Machines: :class:`qibolab.instruments.qm.driver.QMOPX`
- Quantum Machines: :class:`qibolab.instruments.qm.controller.QMController`
- Qblox: :class:`qibolab.instruments.qblox.controller.QbloxCluster`
- Xilinx RFSoCs: :class:`qibolab.instruments.rfsoc.driver.RFSoC`

Expand Down
2 changes: 1 addition & 1 deletion doc/source/tutorials/instrument.rst
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,6 @@ the new controller a new port type. See, for example, the already implemented
ports:

* :class:`qibolab.instruments.rfsoc.driver.RFSoCPort`
* :class:`qibolab.instruments.qm.config.QMPort`
* :class:`qibolab.instruments.qm.ports.QMPort`
* :class:`qibolab.instruments.zhinst.ZhPort`
* :class:`qibolab.instruments.qblox.port`
440 changes: 307 additions & 133 deletions poetry.lock

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ qblox-instruments = { version = "0.11.0", optional = true }
qcodes = { version = "^0.37.0", optional = true }
qcodes_contrib_drivers = { version = "0.18.0", optional = true }
pyvisa-py = { version = "0.5.3", optional = true }
qm-qua = { version = "==1.1.1", optional = true }
qualang-tools = { version = "==0.14.0", optional = true }
qm-qua = { version = "^1.1.6", optional = true }
qualang-tools = { version = "^0.15.0", optional = true}
setuptools = { version = ">67.0.0", optional = true }
laboneq = { version = "==2.24.0", optional = true }
qibosoq = { version = ">=0.0.4,<0.2", optional = true }
Expand Down Expand Up @@ -60,7 +60,7 @@ qblox-instruments = "0.11.0"
qcodes = "^0.37.0"
qcodes_contrib_drivers = "0.18.0"
qibosoq = ">=0.0.4,<0.2"
qualang-tools = "==0.14.0"
qualang-tools = "^0.15.0"
laboneq = "==2.24.0"

[tool.poetry.group.tests]
Expand Down
10 changes: 5 additions & 5 deletions src/qibolab/channels.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,12 @@ def power_range(self, value):
self.port.power_range = value

@property
def filters(self):
return self.port.filters
def filter(self):
stavros11 marked this conversation as resolved.
Show resolved Hide resolved
return self.port.filter

@filters.setter
def filters(self, value):
self.port.filters = value
@filter.setter
def filter(self, value):
self.port.filter = value


@dataclass
Expand Down
3 changes: 0 additions & 3 deletions src/qibolab/instruments/port.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,3 @@ class Port:
power_range: int
"""Similar to attenuation (negative) and gain (positive) for (Zurich
instruments)."""
filters: dict
"""Filters to be applied to the channel to reduce the distortions when
sending flux pulses."""
5 changes: 2 additions & 3 deletions src/qibolab/instruments/qm/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
from .config import QMPort
from .driver import QMOPX
from .simulator import QMSim
from .controller import QMController
from .devices import Octave, OPXplus
206 changes: 143 additions & 63 deletions src/qibolab/instruments/qm/acquisition.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Optional

import numpy as np
from qm import qua
Expand All @@ -8,6 +9,8 @@
from qualang_tools.addons.variables import assign_variables_to_element
from qualang_tools.units import unit

from qibolab.execution_parameters import AcquisitionType, AveragingMode
from qibolab.qubits import QubitId
from qibolab.result import (
AveragedIntegratedResults,
AveragedRawWaveformResults,
Expand All @@ -27,10 +30,24 @@ class Acquisition(ABC):
variables.
"""

serial: str
"""Serial of the readout pulse that generates this acquisition."""
name: str
"""Name of the acquisition used as identifier to download results from the
instruments."""
qubit: QubitId
average: bool

keys: list[str] = field(default_factory=list)

RESULT_CLS = IntegratedResults
"""Result object type that corresponds to this acquisition type."""
AVERAGED_RESULT_CLS = AveragedIntegratedResults
"""Averaged result object type that corresponds to this acquisition
type."""

@property
def npulses(self):
return len(self.keys)

@abstractmethod
def assign_element(self, element):
"""Assign acquisition variables to the corresponding QM controlled.
Expand All @@ -50,10 +67,6 @@ def measure(self, operation, element):
element (str): Element (from ``config``) that the pulse will be applied on.
"""

@abstractmethod
def save(self):
"""Save acquired results from variables to streams."""

@abstractmethod
def download(self, *dimensions):
"""Save streams to prepare for fetching from host device.
Expand All @@ -66,6 +79,13 @@ def download(self, *dimensions):
def fetch(self):
"""Fetch downloaded streams to host device."""

def result(self, data):
"""Creates Qibolab result object that is returned to the platform."""
res_cls = self.AVERAGED_RESULT_CLS if self.average else self.RESULT_CLS
if self.npulses > 1:
return [res_cls(data[..., i]) for i in range(self.npulses)]
return [res_cls(data)]


@dataclass
class RawAcquisition(Acquisition):
Expand All @@ -76,82 +96,80 @@ class RawAcquisition(Acquisition):
)
"""Stream to collect raw ADC data."""

RESULT_CLS = RawWaveformResults
AVERAGED_RESULT_CLS = AveragedRawWaveformResults

def assign_element(self, element):
pass

def measure(self, operation, element):
qua.measure(operation, element, self.adc_stream)

def save(self):
pass

def download(self, *dimensions):
i_stream = self.adc_stream.input1()
q_stream = self.adc_stream.input2()
istream = self.adc_stream.input1()
qstream = self.adc_stream.input2()
if self.average:
i_stream = i_stream.average()
q_stream = q_stream.average()
i_stream.save(f"{self.serial}_I")
q_stream.save(f"{self.serial}_Q")
istream = istream.average()
qstream = qstream.average()
istream.save(f"{self.name}_I")
qstream.save(f"{self.name}_Q")

def fetch(self, handles):
ires = handles.get(f"{self.serial}_I").fetch_all()
qres = handles.get(f"{self.serial}_Q").fetch_all()
ires = handles.get(f"{self.name}_I").fetch_all()
qres = handles.get(f"{self.name}_Q").fetch_all()
# convert raw ADC signal to volts
u = unit()
ires = u.raw2volts(ires)
qres = u.raw2volts(qres)
if self.average:
return AveragedRawWaveformResults(ires + 1j * qres)
return RawWaveformResults(ires + 1j * qres)
signal = u.raw2volts(ires) + 1j * u.raw2volts(qres)
return self.result(signal)


@dataclass
class IntegratedAcquisition(Acquisition):
"""QUA variables used for integrated acquisition."""

I: _Variable = field(default_factory=lambda: declare(fixed))
Q: _Variable = field(default_factory=lambda: declare(fixed))
i: _Variable = field(default_factory=lambda: declare(fixed))
q: _Variable = field(default_factory=lambda: declare(fixed))
"""Variables to save the (I, Q) values acquired from a single shot."""
I_stream: _ResultSource = field(default_factory=lambda: declare_stream())
Q_stream: _ResultSource = field(default_factory=lambda: declare_stream())
istream: _ResultSource = field(default_factory=lambda: declare_stream())
qstream: _ResultSource = field(default_factory=lambda: declare_stream())
"""Streams to collect the results of all shots."""

RESULT_CLS = IntegratedResults
AVERAGED_RESULT_CLS = AveragedIntegratedResults

def assign_element(self, element):
assign_variables_to_element(element, self.I, self.Q)
assign_variables_to_element(element, self.i, self.q)

def measure(self, operation, element):
qua.measure(
operation,
element,
None,
qua.dual_demod.full("cos", "out1", "sin", "out2", self.I),
qua.dual_demod.full("minus_sin", "out1", "cos", "out2", self.Q),
qua.dual_demod.full("cos", "out1", "sin", "out2", self.i),
qua.dual_demod.full("minus_sin", "out1", "cos", "out2", self.q),
)

def save(self):
qua.save(self.I, self.I_stream)
qua.save(self.Q, self.Q_stream)
qua.save(self.i, self.istream)
qua.save(self.q, self.qstream)

def download(self, *dimensions):
Istream = self.I_stream
Qstream = self.Q_stream
istream = self.istream
qstream = self.qstream
if self.npulses > 1:
istream = istream.buffer(self.npulses)
qstream = qstream.buffer(self.npulses)
for dim in dimensions:
Istream = Istream.buffer(dim)
Qstream = Qstream.buffer(dim)
istream = istream.buffer(dim)
qstream = qstream.buffer(dim)
if self.average:
Istream = Istream.average()
Qstream = Qstream.average()
Istream.save(f"{self.serial}_I")
Qstream.save(f"{self.serial}_Q")
istream = istream.average()
qstream = qstream.average()
istream.save(f"{self.name}_I")
qstream.save(f"{self.name}_Q")

def fetch(self, handles):
ires = handles.get(f"{self.serial}_I").fetch_all()
qres = handles.get(f"{self.serial}_Q").fetch_all()
if self.average:
# TODO: calculate std
return AveragedIntegratedResults(ires + 1j * qres)
return IntegratedResults(ires + 1j * qres)
ires = handles.get(f"{self.name}_I").fetch_all()
qres = handles.get(f"{self.name}_Q").fetch_all()
return self.result(ires + 1j * qres)


@dataclass
Expand All @@ -161,53 +179,115 @@ class ShotsAcquisition(Acquisition):
Threshold and angle must be given in order to classify shots.
"""

threshold: float
threshold: Optional[float] = None
"""Threshold to be used for classification of single shots."""
angle: float
angle: Optional[float] = None
"""Angle in the IQ plane to be used for classification of single shots."""

I: _Variable = field(default_factory=lambda: declare(fixed))
Q: _Variable = field(default_factory=lambda: declare(fixed))
i: _Variable = field(default_factory=lambda: declare(fixed))
q: _Variable = field(default_factory=lambda: declare(fixed))
"""Variables to save the (I, Q) values acquired from a single shot."""
shot: _Variable = field(default_factory=lambda: declare(int))
"""Variable for calculating an individual shots."""
shots: _ResultSource = field(default_factory=lambda: declare_stream())
"""Stream to collect multiple shots."""

RESULT_CLS = SampleResults
AVERAGED_RESULT_CLS = AveragedSampleResults

def __post_init__(self):
self.cos = np.cos(self.angle)
self.sin = np.sin(self.angle)

def assign_element(self, element):
assign_variables_to_element(element, self.I, self.Q, self.shot)
assign_variables_to_element(element, self.i, self.q, self.shot)

def measure(self, operation, element):
qua.measure(
operation,
element,
None,
qua.dual_demod.full("cos", "out1", "sin", "out2", self.I),
qua.dual_demod.full("minus_sin", "out1", "cos", "out2", self.Q),
qua.dual_demod.full("cos", "out1", "sin", "out2", self.i),
qua.dual_demod.full("minus_sin", "out1", "cos", "out2", self.q),
)
qua.assign(
self.shot,
qua.Cast.to_int(self.I * self.cos - self.Q * self.sin > self.threshold),
qua.Cast.to_int(self.i * self.cos - self.q * self.sin > self.threshold),
)

def save(self):
qua.save(self.shot, self.shots)

def download(self, *dimensions):
shots = self.shots
if self.npulses > 1:
shots = shots.buffer(self.npulses)
for dim in dimensions:
shots = shots.buffer(dim)
if self.average:
shots = shots.average()
shots.save(f"{self.serial}_shots")
shots.save(f"{self.name}_shots")

def fetch(self, handles):
shots = handles.get(f"{self.serial}_shots").fetch_all()
if self.average:
# TODO: calculate std
return AveragedSampleResults(shots)
return SampleResults(shots.astype(int))
shots = handles.get(f"{self.name}_shots").fetch_all()
return self.result(shots)


ACQUISITION_TYPES = {
AcquisitionType.RAW: RawAcquisition,
AcquisitionType.INTEGRATION: IntegratedAcquisition,
AcquisitionType.DISCRIMINATION: ShotsAcquisition,
}


def declare_acquisitions(ro_pulses, qubits, options):
"""Declares variables for saving acquisition in the QUA program.

Args:
ro_pulses (list): List of readout pulses in the sequence.
qubits (dict): Dictionary containing all the :class:`qibolab.qubits.Qubit`
objects of the platform.
options (:class:`qibolab.execution_parameters.ExecutionParameters`): Execution
options containing acquisition type and averaging mode.

Returns:
List of all :class:`qibolab.instruments.qm.acquisition.Acquisition` objects.
"""
acquisitions = {}
for qmpulse in ro_pulses:
qubit = qmpulse.pulse.qubit
name = f"{qmpulse.operation}_{qubit}"
if name not in acquisitions:
average = options.averaging_mode is AveragingMode.CYCLIC
kwargs = {}
if options.acquisition_type is AcquisitionType.DISCRIMINATION:
kwargs["threshold"] = qubits[qubit].threshold
kwargs["angle"] = qubits[qubit].iq_angle

acquisition = ACQUISITION_TYPES[options.acquisition_type](
name, qubit, average, **kwargs
)
acquisition.assign_element(qmpulse.element)
acquisitions[name] = acquisition

acquisitions[name].keys.append(qmpulse.pulse.serial)
qmpulse.acquisition = acquisitions[name]
return list(acquisitions.values())


def fetch_results(result, acquisitions):
"""Fetches results from an executed experiment.

Args:
result: Result of the executed experiment.
acquisition (dict): Dictionary containing :class:`qibolab.instruments.qm.acquisition.Acquisition` objects.

Returns:
Dictionary with the results in the format required by the platform.
"""
handles = result.result_handles
handles.wait_for_all_values() # for async replace with ``handles.is_processing()``
results = {}
for acquisition in acquisitions:
data = acquisition.fetch(handles)
for serial, result in zip(acquisition.keys, data):
results[acquisition.qubit] = results[serial] = result
return results
Loading
Loading