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 65 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
35 changes: 10 additions & 25 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 Expand Up @@ -232,8 +216,9 @@ We leave to the dedicated tutorial a full explanation of the experiment, but her
platform = create_platform("dummy")

qubit = platform.qubits[0]
natives = platform.parameters.native_gates.single_qubit[0]
# define the pulse sequence
sequence = qubit.native_gates.MZ.create_sequence()
sequence = natives.MZ.create_sequence()

# define a sweeper for a frequency scan
sweeper = Sweeper(
Expand Down
38 changes: 19 additions & 19 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 All @@ -68,10 +68,11 @@ Now we can create a simple sequence (again, without explicitly giving any qubit

ps = PulseSequence()
qubit = platform.qubits[0]
ps.concatenate(qubit.native_gates.RX.create_sequence())
ps.concatenate(qubit.native_gates.RX.create_sequence(phi=np.pi / 2))
natives = platform.parameters.native_gates.single_qubit[0]
ps.concatenate(natives.RX.create_sequence())
ps.concatenate(natives.RX.create_sequence(phi=np.pi / 2))
ps.append((qubit.probe.name, Delay(duration=200)))
ps.concatenate(qubit.native_gates.MZ.create_sequence())
ps.concatenate(natives.MZ.create_sequence())

Now we can execute the sequence on hardware:

Expand Down Expand Up @@ -251,11 +252,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 +266,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 Expand Up @@ -341,15 +336,16 @@ Typical experiments may include both pre-defined pulses and new ones:

from qibolab.pulses import Rectangular

natives = platform.parameters.native_gates.single_qubit[0]
sequence = PulseSequence()
sequence.concatenate(platform.qubits[0].native_gates.RX.create_sequence())
sequence.concatenate(natives.RX.create_sequence())
sequence.append(
(
"some_channel",
Pulse(duration=10, amplitude=0.5, relative_phase=0, envelope=Rectangular()),
)
)
sequence.concatenate(platform.qubits[0].native_gates.MZ.create_sequence())
sequence.concatenate(natives.MZ.create_sequence())

results = platform.execute([sequence], options=options)

Expand Down Expand Up @@ -420,15 +416,17 @@ A tipical resonator spectroscopy experiment could be defined with:

from qibolab.sweeper import Parameter, Sweeper, SweeperType

natives = platform.parameters.native_gates.single_qubit

sequence = PulseSequence()
sequence.concatenate(
platform.qubits[0].native_gates.MZ.create_sequence()
natives[0].MZ.create_sequence()
) # readout pulse for qubit 0 at 4 GHz
sequence.concatenate(
platform.qubits[1].native_gates.MZ.create_sequence()
natives[1].MZ.create_sequence()
) # readout pulse for qubit 1 at 5 GHz
sequence.concatenate(
platform.qubits[2].native_gates.MZ.create_sequence()
natives[2].MZ.create_sequence()
) # readout pulse for qubit 2 at 6 GHz

sweeper = Sweeper(
Expand Down Expand Up @@ -465,10 +463,11 @@ For example:
from qibolab.pulses import PulseSequence, Delay

qubit = platform.qubits[0]
natives = platform.parameters.native_gates.single_qubit[0]
sequence = PulseSequence()
sequence.concatenate(qubit.native_gates.RX.create_sequence())
sequence.concatenate(natives.RX.create_sequence())
sequence.append((qubit.probe.name, Delay(duration=sequence.duration)))
sequence.concatenate(qubit.native_gates.MZ.create_sequence())
sequence.concatenate(natives.MZ.create_sequence())

sweeper_freq = Sweeper(
parameter=Parameter.frequency,
Expand Down Expand Up @@ -561,11 +560,12 @@ Let's now delve into a typical use case for result objects within the qibolab fr
.. testcode:: python

qubit = platform.qubits[0]
natives = platform.parameters.native_gates.single_qubit[0]

sequence = PulseSequence()
sequence.concatenate(qubit.native_gates.RX.create_sequence())
sequence.concatenate(natives.RX.create_sequence())
sequence.append((qubit.probe.name, Delay(duration=sequence.duration)))
sequence.concatenate(qubit.native_gates.MZ.create_sequence())
sequence.concatenate(natives.MZ.create_sequence())

options = ExecutionParameters(
nshots=1000,
Expand Down
15 changes: 9 additions & 6 deletions doc/source/tutorials/calibration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ around the pre-defined frequency.
platform = create_platform("dummy")

qubit = platform.qubits[0]
sequence = qubit.native_gates.MZ.create_sequence()
natives = platform.parameters.native_gates.single_qubit[0]
sequence = natives.MZ.create_sequence()

# allocate frequency sweeper
sweeper = Sweeper(
Expand Down Expand Up @@ -118,12 +119,13 @@ 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")

qubit = platform.qubits[0]
natives = platform.parameters.native_gates.single_qubit[0]

# create pulse sequence and add pulses
sequence = PulseSequence(
Expand All @@ -135,7 +137,7 @@ complex pulse sequence. Therefore with start with that:
(qubit.probe.name, Delay(duration=sequence.duration)),
]
)
sequence.concatenate(qubit.native_gates.MZ.create_sequence())
sequence.concatenate(natives.MZ.create_sequence())

# allocate frequency sweeper
sweeper = Sweeper(
Expand Down Expand Up @@ -226,15 +228,16 @@ and its impact on qubit states in the IQ plane.
platform = create_platform("dummy")

qubit = platform.qubits[0]
natives = platform.parameters.native_gates.single_qubit[0]

# create pulse sequence 1 and add pulses
one_sequence = PulseSequence()
one_sequence.concatenate(qubit.native_gates.RX.create_sequence())
one_sequence.concatenate(natives.RX.create_sequence())
one_sequence.append((qubit.probe.name, Delay(duration=one_sequence.duration)))
one_sequence.concatenate(qubit.native_gates.MZ.create_sequence())
one_sequence.concatenate(natives.MZ.create_sequence())

# create pulse sequence 2 and add pulses
zero_sequence = qubit.native_gates.MZ.create_sequence()
zero_sequence = natives.MZ.create_sequence()

options = ExecutionParameters(
nshots=1000,
Expand Down
2 changes: 1 addition & 1 deletion doc/source/tutorials/compiler.rst
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ The following example shows how to modify the compiler in order to execute a cir
# define a compiler rule that translates X to the pi-pulse
def x_rule(gate, qubit):
"""X gate applied with a single pi-pulse."""
return qubit.native_gates.RX.create_sequence()
return qubit.RX.create_sequence()


# the empty dictionary is needed because the X gate does not require any virtual Z-phases
Expand Down
Loading