Skip to content

Commit

Permalink
Merge pull request #971 from qiboteam/serialization-interface
Browse files Browse the repository at this point in the history
Serialization interface
  • Loading branch information
alecandido authored Aug 17, 2024
2 parents 5d94231 + caa8da7 commit ccc4445
Show file tree
Hide file tree
Showing 50 changed files with 1,648 additions and 1,825 deletions.
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.natives.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.natives.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.natives.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.natives.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.natives.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.natives.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.natives.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.natives.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.natives.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
4 changes: 2 additions & 2 deletions doc/source/tutorials/compiler.rst
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,14 @@ 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

backend = QibolabBackend(platform="dummy")
# register the new X rule in the compiler
backend.compiler[gates.X] = x_rule
backend.compiler.rules[gates.X] = x_rule

# execute the circuit
result = backend.execute_circuit(circuit, nshots=1000)
Expand Down
Loading

0 comments on commit ccc4445

Please sign in to comment.