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

Serialization interface #971

Merged
merged 72 commits into from
Aug 17, 2024
Merged
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
aae3c8b
feat: Turn Pydantic to strict mode
alecandido Aug 9, 2024
ac3b7e6
fix: Reenable arbitrary types, to allow for custom
alecandido Aug 9, 2024
bbbd2ff
test: Remove already dropped attributes from calls
alecandido Aug 9, 2024
5fb657f
docs: Fix doctests
alecandido Aug 9, 2024
c902294
feat!: Introduce runcard classes to replace previous functions
alecandido Aug 12, 2024
97e3187
fix: Fix some types in the runcard classes
alecandido Aug 12, 2024
cf2b02e
fix: Propagate load classes usage
alecandido Aug 12, 2024
ff89d3c
fix: Propagate load classes usage
alecandido Aug 12, 2024
b479da3
docs: Drop usage of loader functions
alecandido Aug 12, 2024
bde2ade
fix: Delegate qubit name validation to Pydantic
alecandido Aug 12, 2024
7abb40b
fix: Replace dump interface
alecandido Aug 12, 2024
a47c69b
fix: Nest runcard within platform
alecandido Aug 12, 2024
d7b867e
feat: Automate configs deserialization
alecandido Aug 12, 2024
8bf5cb4
docs: Fix doctests to account for runcard deserialization
alecandido Aug 12, 2024
e483558
fix: Drop dataclass usage from zi configs
alecandido Aug 12, 2024
9dd8a2f
docs: Temporarily prevent error generated by import
alecandido Aug 12, 2024
c423e92
fix: Dump kernels directly in json
alecandido Aug 12, 2024
940c3fc
fix: Drop configs custom serialization
alecandido Aug 12, 2024
d9b3c81
feat: Start adapting existing classes to automated serialization
alecandido Aug 12, 2024
477ada1
fix: Rename runcard to parameters
alecandido Aug 13, 2024
2f516f9
fix: Fix two-qubit natives deserialization
alecandido Aug 13, 2024
990c503
fix: Remove unused loaders and serializers
alecandido Aug 13, 2024
27a90cf
fix: Use qubit id definition for names handling
alecandido Aug 13, 2024
7acfaf4
fix: Fix pulse sequence deserialization
alecandido Aug 13, 2024
440c475
fix: Fix pulse sequence serialization
alecandido Aug 13, 2024
9474be6
docs: Fix doctests for natives deserialization
alecandido Aug 13, 2024
948ab3d
fix: Replace dataclasses' fields access and specification
alecandido Aug 13, 2024
3578d7a
build: Ignore Pylint inspection for sequence subclass
alecandido Aug 13, 2024
6070089
feat!: Drop last custom (de)serializers
alecandido Aug 13, 2024
fd44d00
fix: Fix scattered serialization bugs
alecandido Aug 13, 2024
30dcab4
fix: Fully automate parameters (de)serialization
alecandido Aug 13, 2024
f1d7666
test: Whitelist pydantic FieldInfo as well
alecandido Aug 13, 2024
365ee2c
fix: Reintroduce extended support for symmetric two-qubit gates
alecandido Aug 13, 2024
858eb48
feat!: Rename serialize module to parameters
alecandido Aug 13, 2024
1681a41
feat!: Rename the new serialize module to default serialize
alecandido Aug 13, 2024
dfe6a4a
feat!: Serialize kernels together with parameters
alecandido Aug 13, 2024
ccb75ff
docs: Add note about explicit comparison
alecandido Aug 13, 2024
aa20362
chore: Fix pyproject format
alecandido Aug 13, 2024
beb5a6a
test: Disable pylint warning
alecandido Aug 13, 2024
ca77513
feat!: Move instruments settings to components
alecandido Aug 13, 2024
20fe920
fix: Drop useless return schema
alecandido Aug 13, 2024
0ba2bf9
docs: Extend docs, alias type
alecandido Aug 13, 2024
cbba54a
docs: Replace workaround with a less intrusive one
alecandido Aug 13, 2024
6128c37
fix: Remove discontinued kernels file
alecandido Aug 13, 2024
d90b1b4
feat: Allow instruments to report their names to the platform
alecandido Aug 13, 2024
da8ba2c
fix: Tiny dummy platform simplification
alecandido Aug 13, 2024
83a6679
build: Fix rebase leftover
alecandido Aug 16, 2024
4b5b7d7
chore: Poetry lock
alecandido Aug 16, 2024
cfaf95f
fix: Restore previous objects, cut the natives out
alecandido Aug 14, 2024
9ea9319
docs: Describe the role of the restored attributes
alecandido Aug 14, 2024
0026a20
fix: Rely on natives for compilation
alecandido Aug 14, 2024
0445453
fix: Fully rely on natives for pairs
alecandido Aug 14, 2024
ea41a40
feat!: Upgrade load, to decouple effictively decouple channels creation
alecandido Aug 14, 2024
d8ec246
test: Fix dummy tests
alecandido Aug 14, 2024
a690010
test: Fix compiler tests
alecandido Aug 14, 2024
6ef8bea
test: Fix platform and results tests
alecandido Aug 14, 2024
86b5b71
fix: Always pass through qubit retrieval during compilation
alecandido Aug 14, 2024
e285656
fix: Add two-qubit gates container to provide access to symmetric gates
alecandido Aug 15, 2024
4c712cc
fix: Subclass dict, instead of wrapping it
alecandido Aug 15, 2024
ec79d9e
fix: Use automatic reraising
alecandido Aug 15, 2024
237e6ea
docs: Fix doctests for symmetric
alecandido Aug 15, 2024
d3bc2a5
fix: Fix docstring snippets
alecandido Aug 15, 2024
0eb64a8
docs: Fix platforms creation in the docs
alecandido Aug 15, 2024
2171caa
test: Fix minimal platform creation
alecandido Aug 15, 2024
50faccf
Merge pull request #983 from qiboteam/channel-native-decoupling
alecandido Aug 16, 2024
5b58c4c
refactor: Clarify variable names in default compiler rules
alecandido Aug 17, 2024
8b2c7d8
feat: Shortcut native gates with platform property
alecandido Aug 17, 2024
e6e4927
feat!: Drop compiler's pseudo-dictionary interface
alecandido Aug 17, 2024
33dc96b
docs: Fix doctest for compiler getitem removal
alecandido Aug 17, 2024
c52467f
fix: Simplify missing gates handling
alecandido Aug 17, 2024
4f4e6fc
feat!: Drop topology, in favor of pairs
alecandido Aug 17, 2024
caa8da7
test: Switch from topology to pairs
alecandido Aug 17, 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
5 changes: 5 additions & 0 deletions doc/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@

import qibolab

# TODO: the following is a workaround for Sphinx doctest, cf.
# - https://github.com/qiboteam/qibolab/commit/e04a6ab
# - https://github.com/pydantic/pydantic/discussions/7763
import qibolab.instruments.zhinst

# -- Project information -----------------------------------------------------

project = "qibolab"
Expand Down
32 changes: 8 additions & 24 deletions doc/source/getting-started/experiment.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,10 @@ In this example, the qubit is controlled by a Zurich Instruments' SHFQC instrume
AcquisitionConfig,
OscillatorConfig,
)

from qibolab.instruments.zhinst import ZiChannel, Zurich
from qibolab.parameters import Parameters
from qibolab.platform import Platform
from qibolab.serialize import (
load_instrument_settings,
load_qubits,
load_runcard,
load_settings,
)

NAME = "my_platform" # name of the platform
ADDRESS = "localhost" # ip address of the ZI data server
Expand All @@ -61,8 +57,9 @@ In this example, the qubit is controlled by a Zurich Instruments' SHFQC instrume
device_setup.add_instruments(SHFQC("device_shfqc", address="DEV12146"))

# Load and parse the runcard (i.e. parameters.json)
runcard = load_runcard(FOLDER)
qubits, _, pairs = load_qubits(runcard)
runcard = Parameters.load(FOLDER)
qubits = runcard.native_gates.single_qubit
pairs = runcard.native_gates.pairs
qubit = qubits[0]

# define component names, and load their configurations
Expand All @@ -74,14 +71,6 @@ In this example, the qubit is controlled by a Zurich Instruments' SHFQC instrume
qubit.probe = IqChannel(name=probe, lo=readout_lo, mixer=None, acquisition=acquire)
qubit.acquisition = AcquireChannel(name=acquire, probe=probe, twpa_pump=None)

configs = {}
component_params = runcard["components"]
configs[drive] = IqConfig(**component_params[drive])
configs[probe] = IqConfig(**component_params[probe])
configs[acquire] = AcquisitionConfig(**component_params[acquire])
configs[drive_lo] = OscillatorConfig(**component_params[drive_lo])
configs[readout_lo] = OscillatorConfig(**component_params[readout_lo])

zi_channels = [
ZiChannel(qubit.drive, device="device_shfqc", path="SGCHANNELS/0/OUTPUT"),
ZiChannel(qubit.probe, device="device_shfqc", path="QACHANNELS/0/OUTPUT"),
Expand All @@ -90,15 +79,10 @@ In this example, the qubit is controlled by a Zurich Instruments' SHFQC instrume

controller = Zurich(NAME, device_setup=device_setup, channels=zi_channels)

instruments = load_instrument_settings(runcard, {controller.name: controller})
settings = load_settings(runcard)
return Platform(
NAME,
qubits,
pairs,
configs,
instruments,
settings,
name=NAME,
runcard=runcard,
instruments={controller.name: controller},
resonator_type="3D",
)

Expand Down
8 changes: 1 addition & 7 deletions doc/source/main-documentation/qibolab.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ We can easily access the names of channels and other components, and based on th
:hide:

Drive channel name: qubit_0/drive
Drive frequency: 4000000000
Drive frequency: 4000000000.0
Drive channel qubit_0/drive does not use an LO.

Now we can create a simple sequence (again, without explicitly giving any qubit specific parameter, as these are loaded automatically from the platform, as defined in the runcard):
Expand Down Expand Up @@ -251,11 +251,8 @@ To illustrate, here are some examples of single pulses using the Qibolab API:
pulse = Pulse(
duration=40, # Pulse duration in ns
amplitude=0.5, # Amplitude relative to instrument range
frequency=1e8, # Frequency in Hz
relative_phase=0, # Phase in radians
envelope=Rectangular(),
channel="channel",
qubit=0,
)

In this way, we defined a rectangular drive pulse using the generic Pulse object.
Expand All @@ -268,11 +265,8 @@ Alternatively, you can achieve the same result using the dedicated :class:`qibol
pulse = Pulse(
duration=40, # timing, in all qibolab, is expressed in ns
amplitude=0.5, # this amplitude is relative to the range of the instrument
frequency=1e8, # frequency are in Hz
relative_phase=0, # phases are in radians
envelope=Rectangular(),
channel="channel",
qubit=0,
)

Both the Pulses objects and the PulseShape object have useful plot functions and several different various helper methods.
Expand Down
2 changes: 1 addition & 1 deletion doc/source/tutorials/calibration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ complex pulse sequence. Therefore with start with that:
AveragingMode,
AcquisitionType,
)
from qibolab.serialize_ import replace
from qibolab.serialize import replace

# allocate platform
platform = create_platform("dummy")
Expand Down
117 changes: 54 additions & 63 deletions doc/source/tutorials/lab.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ using different Qibolab primitives.
instrument = DummyInstrument("my_instrument", "0.0.0.0:0")

# create the qubit object
qubit = Qubit(0)
qubit = Qubit(name=0)

# assign channels to the qubit
qubit.probe = IqChannel(name="probe", mixer=None, lo=None, acquisition="acquire")
qubit.acquire = AcquireChannel(name="acquire", twpa_pump=None, probe="probe")
qubit.acquisition = AcquireChannel(name="acquire", twpa_pump=None, probe="probe")
qubit.drive = Iqchannel(name="drive", mixer=None, lo=None)

# define configuration for channels
Expand Down Expand Up @@ -112,16 +112,16 @@ hold the parameters of the two-qubit gates.
)

# create the qubit objects
qubit0 = Qubit(0)
qubit1 = Qubit(1)
qubit0 = Qubit(name=0)
qubit1 = Qubit(name=1)

# assign channels to the qubits
qubit0.probe = IqChannel(name="probe_0", mixer=None, lo=None, acquisition="acquire_0")
qubit0.acquire = AcquireChannel(name="acquire_0", twpa_pump=None, probe="probe_0")
qubit0.acquisition = AcquireChannel(name="acquire_0", twpa_pump=None, probe="probe_0")
qubit0.drive = IqChannel(name="drive_0", mixer=None, lo=None)
qubit0.flux = DcChannel(name="flux_0")
qubit1.probe = IqChannel(name="probe_1", mixer=None, lo=None, acquisition="acquire_1")
qubit1.acquire = AcquireChannel(name="acquire_1", twpa_pump=None, probe="probe_1")
qubit1.acquisition = AcquireChannel(name="acquire_1", twpa_pump=None, probe="probe_1")
qubit1.drive = IqChannel(name="drive_1", mixer=None, lo=None)

# assign single-qubit native gates to each qubit
Expand Down Expand Up @@ -177,18 +177,21 @@ hold the parameters of the two-qubit gates.
)

# define the pair of qubits
pair = QubitPair(qubit0.name, qubit1.name)
pair.native_gates = TwoQubitNatives(
CZ=FixedSequenceFactory(
PulseSequence(
[
(
qubit0.flux.name,
Pulse(duration=30, amplitude=0.005, envelope=Rectangular()),
),
]
pair = QubitPair(
qubit1=qubit0.name,
qubit2=qubit1.name,
native_gates=TwoQubitNatives(
CZ=FixedSequenceFactory(
PulseSequence(
[
(
qubit0.flux.name,
Pulse(duration=30, amplitude=0.005, envelope=Rectangular()),
),
]
)
)
)
),
)

Some architectures may also have coupler qubits that mediate the interactions.
Expand All @@ -209,9 +212,9 @@ coupler but qibolab will take them into account when calling :class:`qibolab.nat
)

# create the qubit and coupler objects
qubit0 = Qubit(0)
qubit1 = Qubit(1)
coupler_01 = Qubit(100)
qubit0 = Qubit(name=0)
qubit1 = Qubit(name=1)
coupler_01 = Qubit(name=100)

# assign channel(s) to the coupler
coupler_01.flux = DcChannel(name="flux_coupler_01")
Expand All @@ -220,24 +223,21 @@ coupler but qibolab will take them into account when calling :class:`qibolab.nat
# Look above example

# define the pair of qubits
pair = QubitPair(qubit0.name, qubit1.name)
pair.native_gates = TwoQubitNatives(
CZ=FixedSequenceFactory(
PulseSequence(
[
(
coupler_01.flux.name,
Pulse(
duration=30,
amplitude=0.005,
frequency=1e9,
envelope=Rectangular(),
qubit=qubit1.name,
),
)
],
pair = QubitPair(
qubit1=qubit0.name,
qubit2=qubit1.name,
native_gates=TwoQubitNatives(
CZ=FixedSequenceFactory(
PulseSequence(
[
(
coupler_01.flux.name,
Pulse(duration=30, amplitude=0.005, envelope=Rectangular()),
)
],
)
)
)
),
)

The platform automatically creates the connectivity graph of the given chip
Expand Down Expand Up @@ -277,7 +277,7 @@ since ``create()`` is part of a Python module, is is possible to load parameters
from an external file or database.

Qibolab provides some utility functions, accessible through
:py:mod:`qibolab.serialize`, for loading calibration parameters stored in a JSON
:py:mod:`qibolab.parameters`, for loading calibration parameters stored in a JSON
file with a specific format. We call such file a runcard. Here is a runcard for
a two-qubit system:

Expand Down Expand Up @@ -476,7 +476,7 @@ however the pulses under ``native_gates`` should comply with the
Providing the above runcard is not sufficient to instantiate a
:class:`qibolab.platform.Platform`. This should still be done using a
``create()`` method, however this is significantly simplified by
``qibolab.serialize``. The ``create()`` method should be put in a
``qibolab.parameters``. The ``create()`` method should be put in a
file named ``platform.py`` inside the ``my_platform`` directory.
Here is the ``create()`` method that loads the parameters of
the above runcard:
Expand All @@ -495,11 +495,7 @@ the above runcard:
DcConfig,
IqConfig,
)
from qibolab.serialize import (
load_runcard,
load_qubits,
load_settings,
)
from qibolab.parameters import Parameters
from qibolab.instruments.dummy import DummyInstrument

FOLDER = Path.cwd()
Expand All @@ -511,8 +507,9 @@ the above runcard:
instrument = DummyInstrument("my_instrument", "0.0.0.0:0")

# create ``Qubit`` and ``QubitPair`` objects by loading the runcard
runcard = load_runcard(folder)
qubits, _, pairs = load_qubits(runcard)
runcard = Parameters.load(folder)
qubits = runcard.native_gates.single_qubit
pairs = runcard.native_gates.pairs

# define channels and load component configs
configs = {}
Expand Down Expand Up @@ -540,14 +537,13 @@ the above runcard:
# create dictionary of instruments
instruments = {instrument.name: instrument}
# load ``settings`` from the runcard
settings = load_settings(runcard)
return Platform(
"my_platform",
qubits,
pairs,
configs,
instruments,
settings,
settings=runcard.settings,
resonator_type="2D",
)

Expand All @@ -563,8 +559,10 @@ With the following additions for coupler architectures:
instrument = DummyInstrument("my_instrument", "0.0.0.0:0")

# create ``Qubit`` and ``QubitPair`` objects by loading the runcard
runcard = load_runcard(folder)
qubits, couplers, pairs = load_qubits(runcard)
runcard = Parameters.load(folder)
qubits = runcard.native_gates.single_qubit
couplers = runcard.native_gates.coupler
pairs = runcard.native_gates.pairs

# define channels and load component configs
configs = {}
Expand Down Expand Up @@ -644,7 +642,7 @@ The runcard can contain an ``instruments`` section that provides these parameter
}


These settings are loaded when creating the platform using :meth:`qibolab.serialize.load_instrument_settings`.
These settings are loaded when creating the platform using :meth:`qibolab.parameters.load_instrument_settings`.
Note that the key used in the runcard should be the same with the name used when instantiating the instrument,
in this case ``"twpa_pump"``.

Expand All @@ -662,11 +660,7 @@ in this case ``"twpa_pump"``.
DcConfig,
IqConfig,
)
from qibolab.serialize import (
load_runcard,
load_qubits,
load_settings,
)
from qibolab.parameters import Parameters
from qibolab.instruments.dummy import DummyInstrument

FOLDER = Path.cwd()
Expand All @@ -678,8 +672,9 @@ in this case ``"twpa_pump"``.
instrument = DummyInstrument("my_instrument", "0.0.0.0:0")

# create ``Qubit`` and ``QubitPair`` objects by loading the runcard
runcard = load_runcard(folder)
qubits, _, pairs = load_qubits(runcard)
runcard = Parameters.load(folder)
qubits = runcard.native_gates.single_qubit
pairs = runcard.native_gates.pairs

# define channels and load component configs
configs = {}
Expand All @@ -706,16 +701,12 @@ in this case ``"twpa_pump"``.

# create dictionary of instruments
instruments = {instrument.name: instrument}
# load instrument settings from the runcard
instruments = load_instrument_settings(runcard, instruments)
# load ``settings`` from the runcard
settings = load_settings(runcard)
return Platform(
"my_platform",
qubits,
pairs,
configs,
instruments,
settings,
settings=runcard.settings,
resonator_type="2D",
)
2 changes: 1 addition & 1 deletion doc/source/tutorials/pulses.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pulses (:class:`qibolab.pulses.Pulse`) through the
envelope=Gaussian(rel_sigma=0.2),
),
),
("channel_1", Delay(duration=100, channel="1")),
("channel_1", Delay(duration=100)),
(
"channel_1",
Pulse(
Expand Down
3 changes: 2 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ qm = ["qm-qua", "qualang-tools"]
zh = ["laboneq"]
rfsoc = ["qibosoq"]
los = ["qcodes", "qcodes_contrib_drivers", "pyvisa-py"]
twpa = ["qcodes", "qcodes_contrib_drivers", "pyvisa-py"]
emulator = ["qutip"]


Expand All @@ -101,6 +102,7 @@ test-docs = "make -C doc doctest"
[tool.pylint.master]
output-format = "colorized"
disable = ["E1123", "E1120", "C0301"]
generated-members = ["qibolab.native.RxyFactory", "pydantic.fields.FieldInfo"]

[tool.pytest.ini_options]
testpaths = ['tests/']
Expand Down
Loading