diff --git a/doc/source/getting-started/experiment.rst b/doc/source/getting-started/experiment.rst index 50db88446..4ce04808c 100644 --- a/doc/source/getting-started/experiment.rst +++ b/doc/source/getting-started/experiment.rst @@ -5,7 +5,19 @@ Define the platform ------------------- To launch experiments on quantum hardware, users have first to define their platform. -A platform is composed of a python file, with instruments information, and of a runcard file, with calibration parameters. +To define a platform the user needs to provide a folder with the following structure: + +.. code-block:: bash + + my_platform/ + platform.py + parameters.yml + kernels.npz # (optional) + +where ``platform.py`` contains instruments information, ``parameters.yml`` +includes calibration parameters and ``kernels.npz`` is an optional +file with additional calibration parameters. + More information about defining platforms is provided in :doc:`../tutorials/lab` and several examples can be found at `TII dedicated repository `_. For a first experiment, let's define a single qubit platform at the path previously specified. @@ -13,7 +25,7 @@ For simplicity, the qubit will be controlled by a RFSoC-based system, althought .. testcode:: python - # my_platform.py + # my_platform/platform.py import pathlib @@ -24,14 +36,14 @@ For simplicity, the qubit will be controlled by a RFSoC-based system, althought from qibolab.serialize import load_qubits, load_runcard, load_settings NAME = "my_platform" # name of the platform - ADDRESS = "192.168.0.1" # ip adress of the controller + ADDRESS = "192.168.0.1" # ip address of the controller PORT = 6000 # port of the controller - # path to runcard file with calibration parameter - RUNCARD = pathlib.Path.cwd() / "my_platform.yml" + # folder containing runcard with calibration parameters + FOLDER = pathlib.Path.cwd() - def create(runcard_path=RUNCARD): + def create(folder=FOLDER): # Instantiate controller instruments controller = RFSoC(NAME, ADDRESS, PORT) @@ -42,7 +54,7 @@ For simplicity, the qubit will be controlled by a RFSoC-based system, althought channels |= Channel("drive", port=controller[0]) # create qubit objects - runcard = load_runcard(runcard_path) + runcard = load_runcard(folder) qubits, pairs = load_qubits(runcard) # assign channels to qubits qubits[0].readout = channels["L3-22_ro"] @@ -53,7 +65,20 @@ For simplicity, the qubit will be controlled by a RFSoC-based system, althought settings = load_settings(runcard) return Platform(NAME, qubits, pairs, instruments, settings, resonator_type="3D") -And the we can define the runcard: +.. note:: + + The ``platform.py`` file must contain a ``create_function`` with the following signature: + + .. code-block:: python + + import pathlib + from qibolab.platform import Platform + + + def create(folder: Path) -> Platform: + """Function that generates Qibolab platform.""" + +And the we can define the runcard ``my_platform/parameters.yml``: .. code-block:: yaml @@ -106,19 +131,20 @@ And the we can define the runcard: Setting up the environment -------------------------- -After defining the platform, we must instruct ``qibolab`` of the location of the create file. +After defining the platform, we must instruct ``qibolab`` of the location of the platform(s). +We need to define the path that contains platform folders. This can be done using an environment variable: for Unix based systems: .. code-block:: bash - export QIBOLAB_PLATFORMS= + export QIBOLAB_PLATFORMS= for Windows: .. code-block:: bash - $env:QIBOLAB_PLATFORMS="" + $env:QIBOLAB_PLATFORMS="" To avoid having to repeat this export command for every session, this line can be added to the ``.bashrc`` file (or alternatives as ``.zshrc``). diff --git a/doc/source/main-documentation/qibolab.rst b/doc/source/main-documentation/qibolab.rst index e7e772b37..59c74a615 100644 --- a/doc/source/main-documentation/qibolab.rst +++ b/doc/source/main-documentation/qibolab.rst @@ -213,14 +213,14 @@ Following the tutorial in :doc:`/tutorials/lab`, we can continue the initializat from pathlib import Path from qibolab.serialize import load_qubits, load_runcard - runcard_path = Path.cwd().parent / "src" / "qibolab" / "dummy.yml" + path = Path.cwd().parent / "src" / "qibolab" / "dummy" ch_map = ChannelMap() ch_map |= channel1 ch_map |= channel2 ch_map |= channel3 - runcard = load_runcard(runcard_path) + runcard = load_runcard(path) qubits, couplers, pairs = load_qubits(runcard) qubits[0].drive = channel1 diff --git a/doc/source/tutorials/lab.rst b/doc/source/tutorials/lab.rst index 9a41bc6eb..d0ba8ba71 100644 --- a/doc/source/tutorials/lab.rst +++ b/doc/source/tutorials/lab.rst @@ -408,7 +408,7 @@ the above runcard: from qibolab.instruments.dummy import DummyInstrument - def create(): + def create(folder: Path): # Create a controller instrument instrument = DummyInstrument("my_instrument", "0.0.0.0:0") @@ -422,7 +422,7 @@ the above runcard: channels |= Channel("chf2", port=instrument["o5"]) # create ``Qubit`` and ``QubitPair`` objects by loading the runcard - runcard = load_runcard(Path(__file__).parent / "my_platform.yml") + runcard = load_runcard(folder) qubits, couplers, pairs = load_qubits(runcard) # assign channels to the qubit @@ -444,7 +444,7 @@ With the following additions for coupler architectures: .. testcode:: python - def create(): + def create(folder): # Create a controller instrument instrument = DummyInstrument("my_instrument", "0.0.0.0:0") @@ -459,7 +459,7 @@ With the following additions for coupler architectures: channels |= Channel("chfc0", port=instrument["o6"]) # create ``Qubit`` and ``QubitPair`` objects by loading the runcard - runcard = load_runcard(Path(__file__).parent / "my_platform.yml") + runcard = load_runcard(folder) qubits, couplers, pairs = load_qubits(runcard) # assign channels to the qubit @@ -486,8 +486,8 @@ With the following additions for coupler architectures: couplers=couplers, ) -Note that this assumes that the runcard is saved as ``my_platform.yml`` in the -same directory with the Python file that contains ``create()``. +Note that this assumes that the runcard is saved as ``/parameters.yml`` where ```` +is the directory containing ``platform.py``. Instrument settings @@ -612,7 +612,7 @@ in this case ``"twpa_pump"``. from qibolab.instruments.oscillator import LocalOscillator - def create(): + def create(folder: Path): # Create a controller instrument instrument = DummyInstrument("my_instrument", "0.0.0.0:0") twpa = LocalOscillator("twpa_pump", "0.0.0.1") @@ -625,7 +625,7 @@ in this case ``"twpa_pump"``. channels |= Channel("ch1in", port=instrument["i1"]) # create ``Qubit`` and ``QubitPair`` objects by loading the runcard - runcard = load_runcard(Path(__file__).parent / "my_platform.yml") + runcard = load_runcard(folder) qubits, pairs = load_qubits(runcard) # assign channels to the qubit diff --git a/src/qibolab/__init__.py b/src/qibolab/__init__.py index 885318857..9afb03f12 100644 --- a/src/qibolab/__init__.py +++ b/src/qibolab/__init__.py @@ -12,6 +12,7 @@ ExecutionParameters, ) from qibolab.platform import Platform +from qibolab.serialize import PLATFORM __version__ = im.version(__package__) @@ -29,13 +30,14 @@ def get_platforms_path(): return Path(profiles) -def create_platform(name, runcard=None): +def create_platform(name, path: Path = None) -> Platform: """A platform for executing quantum algorithms. It consists of a quantum processor QPU and a set of controlling instruments. Args: name (str): name of the platform. Options are 'tiiq', 'qili' and 'icarusq'. + path (pathlib.Path): path with platform serialization Returns: The plaform class. """ @@ -44,17 +46,17 @@ def create_platform(name, runcard=None): return create_dummy(with_couplers=name == "dummy_couplers") - platform = get_platforms_path() / f"{name}.py" + platform = get_platforms_path() / f"{name}" if not platform.exists(): raise_error(ValueError, f"Platform {name} does not exist.") - spec = importlib.util.spec_from_file_location("platform", platform) + spec = importlib.util.spec_from_file_location("platform", platform / PLATFORM) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) - if runcard is None: + if path is None: return module.create() - return module.create(runcard) + return module.create(path) def execute_qasm(circuit: str, platform, runcard=None, initial_state=None, nshots=1000): diff --git a/src/qibolab/dummy/__init__.py b/src/qibolab/dummy/__init__.py new file mode 100644 index 000000000..137e075db --- /dev/null +++ b/src/qibolab/dummy/__init__.py @@ -0,0 +1 @@ +from .platform import create_dummy diff --git a/src/qibolab/dummy/kernels.npz b/src/qibolab/dummy/kernels.npz new file mode 100644 index 000000000..15cb0ecb3 Binary files /dev/null and b/src/qibolab/dummy/kernels.npz differ diff --git a/src/qibolab/dummy.yml b/src/qibolab/dummy/parameters.yml similarity index 100% rename from src/qibolab/dummy.yml rename to src/qibolab/dummy/parameters.yml diff --git a/src/qibolab/dummy.py b/src/qibolab/dummy/platform.py similarity index 92% rename from src/qibolab/dummy.py rename to src/qibolab/dummy/platform.py index fef0b6b10..05e91fcd0 100644 --- a/src/qibolab/dummy.py +++ b/src/qibolab/dummy/platform.py @@ -3,9 +3,12 @@ from qibolab.channels import Channel, ChannelMap from qibolab.instruments.dummy import DummyInstrument, DummyLocalOscillator +from qibolab.kernels import Kernels from qibolab.platform import Platform from qibolab.serialize import load_qubits, load_runcard, load_settings +FOLDER = pathlib.Path(__file__).parent + def remove_couplers(runcard): """Remove coupler sections from runcard to create a dummy platform without @@ -21,11 +24,6 @@ def remove_couplers(runcard): return runcard -def load_dummy_runcard(): - """Loads the runcard YAML of the dummy platform.""" - return load_runcard(pathlib.Path(__file__).parent / "dummy.yml") - - def create_dummy(with_couplers: bool = True): """Create a dummy platform using the dummy instrument. @@ -40,7 +38,9 @@ def create_dummy(with_couplers: bool = True): twpa_pump.frequency = 1e9 twpa_pump.power = 10 - runcard = load_dummy_runcard() + runcard = load_runcard(FOLDER) + kernels = Kernels.load(FOLDER) + if not with_couplers: runcard = remove_couplers(runcard) @@ -64,7 +64,7 @@ def create_dummy(with_couplers: bool = True): channels["readout"].attenuation = 0 channels["twpa"].local_oscillator = twpa_pump - qubits, couplers, pairs = load_qubits(runcard) + qubits, couplers, pairs = load_qubits(runcard, kernels) settings = load_settings(runcard) # map channels to qubits diff --git a/src/qibolab/instruments/zhinst.py b/src/qibolab/instruments/zhinst.py index ba6d68ef7..9049ce883 100644 --- a/src/qibolab/instruments/zhinst.py +++ b/src/qibolab/instruments/zhinst.py @@ -4,7 +4,6 @@ import os from collections import defaultdict from dataclasses import dataclass, replace -from pathlib import Path from typing import Dict, List, Tuple, Union import laboneq._token @@ -318,7 +317,6 @@ def __init__( self.smearing = smearing self.chip = "iqm5q" "Parameters read from the runcard not part of ExecutionParameters" - self.kernels = defaultdict(Path) self.exp = None self.experiment = None @@ -442,9 +440,6 @@ def register_readout_line(self, qubit, intermediate_frequency, options): f"q{q}" ].logical_signals["acquire_line"] - if qubit.kernel_path: - self.kernels[q] = qubit.kernel_path - oscillator = lo.Oscillator( frequency=intermediate_frequency, modulation_type=lo.ModulationType.SOFTWARE, @@ -452,7 +447,7 @@ def register_readout_line(self, qubit, intermediate_frequency, options): threshold = None if options.acquisition_type == AcquisitionType.DISCRIMINATION: - if self.kernels[q].is_file(): + if qubit.kernel is not None: # Kernels don't work with the software modulation on the acquire signal oscillator = None else: @@ -1039,15 +1034,15 @@ def measure_relax(self, exp, qubits, couplers, relaxation_time, acquisition_type iq_angle_readout_schedule[i].append(iq_angle) weights = {} - for i, (pulses, qubits, iq_angles) in enumerate( + for i, (pulses, qubits_readout, iq_angles) in enumerate( zip( readout_schedule.values(), qubit_readout_schedule.values(), iq_angle_readout_schedule.values(), ) ): - qd_finish = self.find_subsequence_finish(i, "drive", qubits) - qf_finish = self.find_subsequence_finish(i, "flux", qubits) + qd_finish = self.find_subsequence_finish(i, "drive", qubits_readout) + qf_finish = self.find_subsequence_finish(i, "flux", qubits_readout) cf_finish = self.find_subsequence_finish(i, "couplerflux", couplers) finish_times = np.array( [ @@ -1064,7 +1059,7 @@ def measure_relax(self, exp, qubits, couplers, relaxation_time, acquisition_type play_after = f"sequence_{latest_sequence['line']}_{i}" # Section on the outside loop allows for multiplex with exp.section(uid=f"sequence_measure_{i}", play_after=play_after): - for pulse, q, iq_angle in zip(pulses, qubits, iq_angles): + for pulse, q, iq_angle in zip(pulses, qubits_readout, iq_angles): pulse.zhpulse.uid += str(i) exp.delay( @@ -1073,13 +1068,13 @@ def measure_relax(self, exp, qubits, couplers, relaxation_time, acquisition_type ) if ( - self.kernels[q].is_file() + qubits[q].kernel is not None and acquisition_type == lo.AcquisitionType.DISCRIMINATION ): - kernels = np.load(self.kernels[q]) + kernel = qubits[q].kernel weight = lo.pulse_library.sampled_pulse_complex( uid="weight" + str(q), - samples=kernels[str(q)] * np.exp(1j * iq_angle), + samples=kernel * np.exp(1j * iq_angle), ) else: diff --git a/src/qibolab/kernels.py b/src/qibolab/kernels.py new file mode 100644 index 000000000..9ee1ae60d --- /dev/null +++ b/src/qibolab/kernels.py @@ -0,0 +1,39 @@ +import json +from pathlib import Path + +import numpy as np + +from qibolab.qubits import QubitId + +KERNELS = "kernels.npz" + + +class Kernels(dict[QubitId, np.ndarray]): + """A dictionary subclass for handling Qubit Kernels. + + This class extends the built-in dict class and maps QubitId to numpy + arrays. It provides methods to load and dump the kernels from and to + a file. + """ + + @classmethod + def load(cls, path: Path): + """Class method to load kernels from a file. + + The file should contain a serialized dictionary where keys are + serialized QubitId and values are numpy arrays. + """ + return cls( + {json.loads(key): value for key, value in np.load(path / KERNELS).items()} + ) + + def dump(self, path: Path): + """Instance method to dump the kernels to a file. + + The keys (QubitId) are serialized to strings and the values + (numpy arrays) are kept as is. + """ + np.savez( + path / KERNELS, + **{json.dumps(qubit_id): value for qubit_id, value in self.items()} + ) diff --git a/src/qibolab/qubits.py b/src/qibolab/qubits.py index b701ee07a..867640401 100644 --- a/src/qibolab/qubits.py +++ b/src/qibolab/qubits.py @@ -1,7 +1,8 @@ from dataclasses import dataclass, field, fields -from pathlib import Path from typing import List, Optional, Tuple, Union +import numpy as np + from qibolab.channels import Channel from qibolab.couplers import Coupler from qibolab.native import SingleQubitNatives, TwoQubitNatives @@ -14,7 +15,7 @@ Not all channels are required to operate a qubit. """ -EXCLUDED_FIELDS = CHANNEL_NAMES + ("name", "native_gates", "_flux") +EXCLUDED_FIELDS = CHANNEL_NAMES + ("name", "native_gates", "kernel", "_flux") """Qubit dataclass fields that are excluded by the ``characterization`` property.""" @@ -75,7 +76,7 @@ class Qubit: # parameters for single shot classification threshold: Optional[float] = None iq_angle: float = 0.0 - kernel_path: Optional[Path] = None + kernel: Optional[np.ndarray] = field(default=None, repr=False) # required for mixers (not sure if it should be here) mixer_drive_g: float = 0.0 mixer_drive_phi: float = 0.0 diff --git a/src/qibolab/serialize.py b/src/qibolab/serialize.py index 9d99054c0..6ddcdb7ad 100644 --- a/src/qibolab/serialize.py +++ b/src/qibolab/serialize.py @@ -11,6 +11,7 @@ import yaml from qibolab.couplers import Coupler +from qibolab.kernels import Kernels from qibolab.native import CouplerNatives, SingleQubitNatives, TwoQubitNatives from qibolab.platform import ( CouplerMap, @@ -22,10 +23,13 @@ ) from qibolab.qubits import Qubit, QubitPair +RUNCARD = "parameters.yml" +PLATFORM = "platform.py" + def load_runcard(path: Path) -> dict: """Load runcard YAML to a dictionary.""" - return yaml.safe_load(path.read_text()) + return yaml.safe_load((path / RUNCARD).read_text()) def load_settings(runcard: dict) -> Settings: @@ -34,7 +38,7 @@ def load_settings(runcard: dict) -> Settings: def load_qubits( - runcard: dict, extras_folder: Path = None + runcard: dict, kernels: Kernels = None ) -> Tuple[QubitMap, CouplerMap, QubitPairMap]: """Load qubits and pairs from the runcard. @@ -48,10 +52,11 @@ def load_qubits( q: Qubit(q, **char) for q, char in runcard["characterization"]["single_qubit"].items() } - if extras_folder is not None: - single_qubit = runcard["characterization"]["single_qubit"] - for qubit in qubits.values(): - qubit.kernel_path = extras_folder / single_qubit[qubit.name]["kernel_path"] + + if kernels is not None: + for q in kernels: + qubits[q].kernel = kernels[q] + couplers = {} pairs = {} if "coupler" in runcard["characterization"]: @@ -145,11 +150,6 @@ def dump_characterization(qubits: QubitMap, couplers: CouplerMap = None) -> dict characterization = { "single_qubit": {q: qubit.characterization for q, qubit in qubits.items()}, } - for q in qubits: - qubit = characterization["single_qubit"][q] - kernel_path = qubit["kernel_path"] - if kernel_path is not None: - qubit["kernel_path"] = kernel_path.name if couplers: characterization["coupler"] = { @@ -205,6 +205,37 @@ def dump_runcard(platform: Platform, path: Path): platform.qubits, platform.couplers ) - path.write_text( + (path / RUNCARD).write_text( yaml.dump(settings, sort_keys=False, indent=4, default_flow_style=None) ) + + +def dump_kernels(platform: Platform, path: Path): + """Creates Kernels instance from platform and dumps as npz. + + Args: + platform (qibolab.platform.Platform): The platform to be serialized. + path (pathlib.Path): Path that the kernels file will be saved. + """ + + # create kernels + kernels = Kernels() + for qubit in platform.qubits.values(): + if qubit.kernel is not None: + kernels[qubit.name] = qubit.kernel + + # dump only if not None + if kernels: + kernels.dump(path) + + +def dump_platform(platform: Platform, path: Path): + """Platform serialization as runcard (yaml) and kernels (npz). + + Args: + platform (qibolab.platform.Platform): The platform to be serialized. + path (pathlib.Path): Path where yaml and npz will be dumped. + """ + + dump_kernels(platform=platform, path=path) + dump_runcard(platform=platform, path=path) diff --git a/tests/dummy_qrc/qblox.yml b/tests/dummy_qrc/qblox/parameters.yml similarity index 100% rename from tests/dummy_qrc/qblox.yml rename to tests/dummy_qrc/qblox/parameters.yml diff --git a/tests/dummy_qrc/qblox.py b/tests/dummy_qrc/qblox/platform.py similarity index 94% rename from tests/dummy_qrc/qblox.py rename to tests/dummy_qrc/qblox/platform.py index 644c5c424..6c7c47be3 100644 --- a/tests/dummy_qrc/qblox.py +++ b/tests/dummy_qrc/qblox/platform.py @@ -14,20 +14,19 @@ load_settings, ) -NAME = "qblox" ADDRESS = "192.168.0.6" TIME_OF_FLIGHT = 500 -RUNCARD = pathlib.Path(__file__).parent / "qblox.yml" +FOLDER = pathlib.Path(__file__).parent -def create(runcard_path=RUNCARD): +def create(folder: pathlib.Path = FOLDER): """QuantWare 5q-chip controlled using qblox cluster. Args: runcard_path (str): Path to the runcard file. """ - runcard = load_runcard(runcard_path) + runcard = load_runcard(folder) modules = {} # DEBUG: debug folder = report folder @@ -111,4 +110,6 @@ def create(runcard_path=RUNCARD): settings = load_settings(runcard) - return Platform("qblox", qubits, pairs, instruments, settings, resonator_type="2D") + return Platform( + str(FOLDER), qubits, pairs, instruments, settings, resonator_type="2D" + ) diff --git a/tests/dummy_qrc/qm.yml b/tests/dummy_qrc/qm/parameters.yml similarity index 100% rename from tests/dummy_qrc/qm.yml rename to tests/dummy_qrc/qm/parameters.yml diff --git a/tests/dummy_qrc/qm.py b/tests/dummy_qrc/qm/platform.py similarity index 94% rename from tests/dummy_qrc/qm.py rename to tests/dummy_qrc/qm/platform.py index 745a77f6e..3c54d6ccc 100644 --- a/tests/dummy_qrc/qm.py +++ b/tests/dummy_qrc/qm/platform.py @@ -11,10 +11,10 @@ load_settings, ) -RUNCARD = pathlib.Path(__file__).parent / "qm.yml" +FOLDER = pathlib.Path(__file__).parent -def create(runcard_path=RUNCARD): +def create(folder: pathlib.Path = FOLDER): """Dummy platform using Quantum Machines (QM) OPXs and Rohde Schwarz local oscillators. @@ -69,7 +69,7 @@ def create(runcard_path=RUNCARD): channels["L4-26"].local_oscillator = local_oscillators[5] # create qubit objects - runcard = load_runcard(runcard_path) + runcard = load_runcard(folder) qubits, couplers, pairs = load_qubits(runcard) # assign channels to qubits @@ -101,4 +101,6 @@ def create(runcard_path=RUNCARD): instruments.update({lo.name: lo for lo in local_oscillators}) settings = load_settings(runcard) instruments = load_instrument_settings(runcard, instruments) - return Platform("qm", qubits, pairs, instruments, settings, resonator_type="2D") + return Platform( + str(FOLDER), qubits, pairs, instruments, settings, resonator_type="2D" + ) diff --git a/tests/dummy_qrc/rfsoc.yml b/tests/dummy_qrc/rfsoc/parameters.yml similarity index 100% rename from tests/dummy_qrc/rfsoc.yml rename to tests/dummy_qrc/rfsoc/parameters.yml diff --git a/tests/dummy_qrc/rfsoc.py b/tests/dummy_qrc/rfsoc/platform.py similarity index 87% rename from tests/dummy_qrc/rfsoc.py rename to tests/dummy_qrc/rfsoc/platform.py index 5bab3f5f1..b8729571b 100644 --- a/tests/dummy_qrc/rfsoc.py +++ b/tests/dummy_qrc/rfsoc/platform.py @@ -12,10 +12,10 @@ load_settings, ) -RUNCARD = pathlib.Path(__file__).parent / "rfsoc.yml" +FOLDER = pathlib.Path(__file__).parent -def create(runcard_path=RUNCARD): +def create(folder: pathlib.Path = FOLDER): """Dummy platform using QICK project on the RFSoC4x2 board. Used in ``test_instruments_rfsoc.py``. @@ -34,7 +34,7 @@ def create(runcard_path=RUNCARD): lo_era = ERA("ErasynthLO", "192.168.0.212", ethernet=True) channels["L3-18_ro"].local_oscillator = lo_era - runcard = load_runcard(runcard_path) + runcard = load_runcard(FOLDER) qubits, couplers, pairs = load_qubits(runcard) # assign channels to qubits @@ -46,4 +46,6 @@ def create(runcard_path=RUNCARD): instruments = {inst.name: inst for inst in [controller, lo_twpa, lo_era]} settings = load_settings(runcard) instruments = load_instrument_settings(runcard, instruments) - return Platform("rfsoc", qubits, pairs, instruments, settings, resonator_type="3D") + return Platform( + str(FOLDER), qubits, pairs, instruments, settings, resonator_type="3D" + ) diff --git a/tests/dummy_qrc/zurich/kernels.npz b/tests/dummy_qrc/zurich/kernels.npz new file mode 100644 index 000000000..3cb33f155 Binary files /dev/null and b/tests/dummy_qrc/zurich/kernels.npz differ diff --git a/tests/dummy_qrc/zurich.yml b/tests/dummy_qrc/zurich/parameters.yml similarity index 97% rename from tests/dummy_qrc/zurich.yml rename to tests/dummy_qrc/zurich/parameters.yml index b3a7ef795..91dba6003 100644 --- a/tests/dummy_qrc/zurich.yml +++ b/tests/dummy_qrc/zurich/parameters.yml @@ -240,7 +240,6 @@ characterization: # parameters for single shot classification threshold: 0.8836 iq_angle: -1.551 - kernel_path: "kernel_q0.npz" 1: readout_frequency: 4_931_000_000 @@ -250,7 +249,6 @@ characterization: sweetspot: 0.0 mean_gnd_states: [0, 0] mean_exc_states: [0, 0] - kernel_path: "kernel_q1.npz" 2: readout_frequency: 6.109e+9 #6_112_000_000 drive_frequency: 4_300_587_281 # 4_401_600_000 #4_541_100_000 @@ -262,7 +260,6 @@ characterization: # parameters for single shot classification threshold: -0.0593 iq_angle: -0.667 - kernel_path: "kernel_q2.npz" 3: readout_frequency: 5_783_000_000 drive_frequency: 4_100_000_000 @@ -271,7 +268,6 @@ characterization: sweetspot: 0.0 mean_gnd_states: [0, 0] mean_exc_states: [0, 0] - kernel_path: "kernel_q3.npz" 4: readout_frequency: 5_515_000_000 drive_frequency: 4_196_800_000 @@ -283,7 +279,6 @@ characterization: # parameters for single shot classification threshold: 0.233806 #0.370954 #0.350665 iq_angle: 0.481 # -91.712 #191.016 - kernel_path: "kernel_q4.npz" coupler: 0: sweetspot: 0.0 diff --git a/tests/dummy_qrc/zurich.py b/tests/dummy_qrc/zurich/platform.py similarity index 94% rename from tests/dummy_qrc/zurich.py rename to tests/dummy_qrc/zurich/platform.py index 7a251739c..e74c11672 100644 --- a/tests/dummy_qrc/zurich.py +++ b/tests/dummy_qrc/zurich/platform.py @@ -9,6 +9,7 @@ from qibolab.channels import Channel, ChannelMap from qibolab.instruments.dummy import DummyLocalOscillator as LocalOscillator from qibolab.instruments.zhinst import Zurich +from qibolab.kernels import Kernels from qibolab.serialize import ( load_instrument_settings, load_qubits, @@ -16,16 +17,15 @@ load_settings, ) -RUNCARD = pathlib.Path(__file__).parent / "zurich.yml" -FOLDER = pathlib.Path(__file__).parent / "iqm5q/" +FOLDER = pathlib.Path(__file__).parent N_QUBITS = 5 -def create(runcard_path=RUNCARD): - """IQM 5q-chip controlled Zurich Instrumetns (Zh) SHFQC, HDAWGs and PQSC. +def create(path: pathlib.Path = FOLDER): + """IQM 5q-chip controlled Zurich Instruments (Zh) SHFQC, HDAWGs and PQSC. Args: - runcard_path (str): Path to the runcard file. + path (str): Path to configuration folder. """ device_setup = DeviceSetup("EL_ZURO") @@ -171,8 +171,9 @@ def create(runcard_path=RUNCARD): channels[ch].local_oscillator = local_oscillators[lo] # create qubit objects - runcard = load_runcard(runcard_path) - qubits, couplers, pairs = load_qubits(runcard, FOLDER) + runcard = load_runcard(path) + kernels = Kernels.load(path) + qubits, couplers, pairs = load_qubits(runcard, kernels) settings = load_settings(runcard) # assign channels to qubits and sweetspots(operating points) @@ -193,7 +194,7 @@ def create(runcard_path=RUNCARD): instruments.update({lo.name: lo for lo in local_oscillators}) instruments = load_instrument_settings(runcard, instruments) return Platform( - "zurich", + str(FOLDER), qubits, pairs, instruments, diff --git a/tests/test_dummy.py b/tests/test_dummy.py index ffc02b422..8109833b9 100644 --- a/tests/test_dummy.py +++ b/tests/test_dummy.py @@ -165,7 +165,6 @@ def test_dummy_single_sweep_coupler( sweeper = Sweeper(parameter, parameter_range, couplers=[platform.couplers[0]]) else: sweeper = Sweeper(parameter, parameter_range, pulses=[coupler_pulse]) - print(sweeper) options = ExecutionParameters( nshots=nshots, averaging_mode=average, diff --git a/tests/test_instruments_rfsoc.py b/tests/test_instruments_rfsoc.py index 67aa7f796..555ddd3fa 100644 --- a/tests/test_instruments_rfsoc.py +++ b/tests/test_instruments_rfsoc.py @@ -107,20 +107,79 @@ def test_convert_pulse(dummy_qrc): qubit.feedback.port = controller.ports(1) qubit.readout.local_oscillator.frequency = 1e6 - pulse = Pulse(0, 40, 0.9, 50e6, 0, Drag(5, 2), 0, PulseType.DRIVE, 0) + pulse = Pulse( + start=0, + duration=40, + amplitude=0.9, + frequency=50e6, + relative_phase=0, + shape=Drag(5, 2), + channel=0, + type=PulseType.DRIVE, + qubit=0, + ) targ = rfsoc_pulses.Drag( - 50, 0.9, 0, 0, 0.04, pulse.serial, "drive", 4, None, rel_sigma=5, beta=2 + type="drive", + frequency=50, + amplitude=0.9, + start_delay=0, + duration=0.04, + adc=None, + dac=4, + name=pulse.serial, + relative_phase=0, + rel_sigma=5, + beta=2, ) assert convert(pulse, platform.qubits, 0, sampling_rate=1) == targ - pulse = Pulse(0, 40, 0.9, 50e6, 0, Gaussian(2), 0, PulseType.DRIVE, 0) + pulse = Pulse( + start=0, + duration=40, + amplitude=0.9, + frequency=50e6, + relative_phase=0, + shape=Gaussian(2), + channel=0, + type=PulseType.DRIVE, + qubit=0, + ) targ = rfsoc_pulses.Gaussian( - 50, 0.9, 0, 0, 0.04, pulse.serial, "drive", 4, None, rel_sigma=2 + frequency=50, + amplitude=0.9, + start_delay=0, + relative_phase=0, + duration=0.04, + name=pulse.serial, + type="drive", + dac=4, + adc=None, + rel_sigma=2, ) assert convert(pulse, platform.qubits, 0, sampling_rate=1) == targ - pulse = Pulse(0, 40, 0.9, 50e6, 0, Rectangular(), 0, PulseType.READOUT, 0) - targ = rfsoc_pulses.Rectangular(49, 0.9, 0, 0, 0.04, pulse.serial, "readout", 2, 1) + pulse = Pulse( + start=0, + duration=40, + amplitude=0.9, + frequency=50e6, + relative_phase=0, + shape=Rectangular(), + channel=0, + type=PulseType.READOUT, + qubit=0, + ) + targ = rfsoc_pulses.Rectangular( + frequency=49, + amplitude=0.9, + start_delay=0, + relative_phase=0, + duration=0.04, + name=pulse.serial, + type="readout", + dac=2, + adc=1, + ) assert convert(pulse, platform.qubits, 0, sampling_rate=1) == targ diff --git a/tests/test_platform.py b/tests/test_platform.py index 09c0193a1..0dca4522b 100644 --- a/tests/test_platform.py +++ b/tests/test_platform.py @@ -1,6 +1,5 @@ """Tests :class:`qibolab.platforms.multiqubit.MultiqubitPlatform` and :class:`qibolab.platforms.platform.DesignPlatform`.""" -import os import pathlib import pickle import warnings @@ -8,16 +7,25 @@ import numpy as np import pytest from qibo.models import Circuit +from qibo.result import CircuitResult from qibolab import create_platform from qibolab.backends import QibolabBackend -from qibolab.dummy import load_dummy_runcard +from qibolab.dummy import create_dummy +from qibolab.dummy.platform import FOLDER from qibolab.execution_parameters import ExecutionParameters from qibolab.instruments.qblox.controller import QbloxController from qibolab.instruments.rfsoc.driver import RFSoC +from qibolab.kernels import Kernels from qibolab.platform import Platform, unroll_sequences from qibolab.pulses import PulseSequence, Rectangular -from qibolab.serialize import dump_runcard, load_runcard +from qibolab.serialize import ( + dump_kernels, + dump_platform, + dump_runcard, + load_runcard, + load_settings, +) from .conftest import find_instrument @@ -60,16 +68,13 @@ def test_platform_pickle(platform): assert new_platform.is_connected == platform.is_connected -def test_dump_runcard(platform): - path = pathlib.Path(__file__).parent / "test.yml" - dump_runcard(platform, path) - final_runcard = load_runcard(path) +def test_dump_runcard(platform, tmp_path): + dump_runcard(platform, tmp_path) + final_runcard = load_runcard(tmp_path) if platform.name == "dummy" or platform.name == "dummy_couplers": - target_runcard = load_dummy_runcard() + target_runcard = load_runcard(FOLDER) else: - target_path = ( - pathlib.Path(__file__).parent / "dummy_qrc" / f"{platform.name}.yml" - ) + target_path = pathlib.Path(__file__).parent / "dummy_qrc" / f"{platform.name}" target_runcard = load_runcard(target_path) # for the characterization section the dumped runcard may contain # some default ``Qubit`` parameters @@ -83,7 +88,46 @@ def test_dump_runcard(platform): target_instruments = target_runcard.pop("instruments") final_instruments = final_runcard.pop("instruments") assert final_instruments == target_instruments - os.remove(path) + + +@pytest.mark.parametrize("has_kernels", [False, True]) +def test_kernels(tmp_path, has_kernels): + """Test dumping and loading of `Kernels`.""" + + platform = create_dummy() + if has_kernels: + for qubit in platform.qubits: + platform.qubits[qubit].kernel = np.random.rand(10) + + dump_kernels(platform, tmp_path) + + if has_kernels: + kernels = Kernels.load(tmp_path) + for qubit in platform.qubits: + np.testing.assert_array_equal(platform.qubits[qubit].kernel, kernels[qubit]) + else: + with pytest.raises(FileNotFoundError): + Kernels.load(tmp_path) + + +@pytest.mark.parametrize("has_kernels", [False, True]) +def test_dump_platform(tmp_path, has_kernels): + """Test platform dump and loading runcard and kernels.""" + + platform = create_dummy() + if has_kernels: + for qubit in platform.qubits: + platform.qubits[qubit].kernel = np.random.rand(10) + + dump_platform(platform, tmp_path) + + settings = load_settings(load_runcard(tmp_path)) + if has_kernels: + kernels = Kernels.load(tmp_path) + for qubit in platform.qubits: + np.testing.assert_array_equal(platform.qubits[qubit].kernel, kernels[qubit]) + + assert settings == platform.settings @pytest.fixture(scope="module")