From 543da5e931bed26be0ea351956d22910af866d42 Mon Sep 17 00:00:00 2001 From: victor Date: Thu, 1 Aug 2024 12:50:14 +0200 Subject: [PATCH 01/69] add parameter class and one qubit analog transpiler --- src/qililab/analog/__init__.py | 1 + src/qililab/analog/one_qubit_transpiler.py | 52 ++++++++++++++++++++++ src/qililab/parameter.py | 28 ++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 src/qililab/analog/__init__.py create mode 100644 src/qililab/analog/one_qubit_transpiler.py create mode 100644 src/qililab/parameter.py diff --git a/src/qililab/analog/__init__.py b/src/qililab/analog/__init__.py new file mode 100644 index 000000000..cb6b57100 --- /dev/null +++ b/src/qililab/analog/__init__.py @@ -0,0 +1 @@ +from .one_qubit_transpiler import Qubit2LevelTranspiler diff --git a/src/qililab/analog/one_qubit_transpiler.py b/src/qililab/analog/one_qubit_transpiler.py new file mode 100644 index 000000000..a5760b633 --- /dev/null +++ b/src/qililab/analog/one_qubit_transpiler.py @@ -0,0 +1,52 @@ +""" +Emulator for the single qubit using the 2level approximation +""" + +from typing import Any, Callable + +from ..parameter import Parameter + + +class Qubit2LevelTranspiler: + """Implementation of the transpiler for the 2 level qubit. This is done mainly by inverting the same functions + used int he single_qubit_2level emulator model. + + Args: + eps_model (Callable): epsilon model + delta_model (Callable): delta model + qubitData (DataClass): dataclass containing info about the physical parameters of the qubit. Defaults to None. + """ + + def __init__(self, epsilon_model: Callable, delta_model: Callable): + """Init method. See class description for more details""" + + self.delta_model = delta_model + self.epsilon_model = epsilon_model + + # Magnetic energy bias (epsilon) + self.epsilon = Parameter(name="epsilon", set_method=self._set_epsilon) + # Qubit gap (delta) + self.delta = Parameter(name="delta", set_method=self._set_delta) + # flux parameters + self.phiz = Parameter(name="phiz") + self.phix = Parameter(name="phix") + + def __call__(self, delta: float, epsilon: float) -> tuple[Any, Any]: + """Transpiles Delta and Epsilon to phix, phiz""" + self.delta(delta) + self.epsilon(epsilon) + return self.phix(), self.phiz() # type: ignore[func-returns-value] + + def _set_delta(self, delta): + # sets the value of delta via raw and updates phix accordingly + phix = self.delta_model(delta, inverse=True) + self.phix.set_raw(phix) + return delta + + def _set_epsilon(self, epsilon): + """sets the value of epsilon via raw and updates phiz accordingly. Does not update phix + since this is meant to be used in the transpiler in conjunction with setting delta (which + already updates phix) + """ + self.phiz.set_raw(self.epsilon_model(phix=self.phix(), phiz_epsilon=epsilon, inverse=True)) + return epsilon diff --git a/src/qililab/parameter.py b/src/qililab/parameter.py new file mode 100644 index 000000000..93dba610c --- /dev/null +++ b/src/qililab/parameter.py @@ -0,0 +1,28 @@ +""" Basic implementation of a qcodes like parameter class, but much simpler. +Working, but still work in progress +""" + +from typing import Any, Callable + + +class Parameter: + def __init__(self, name, value=None, set_method: Callable | None = None): + # TODO: add units and other useful info + self.name = name + self._value = value + self.set = set_method + + def __call__(self, *args, **kwargs: Any) -> None: + # TODO: implement get_raw like in qcodes + if args or kwargs: + self._value = self.set(*args, **kwargs) if self.set is not None else self.set_raw(args[0]) + + else: + return self.get(*args, **kwargs) + + def get(self): + return self._value + + def set_raw(self, value): + self._value = value + return value From 6c3a0f68cc0d2554e182100d71b81a275cefd147 Mon Sep 17 00:00:00 2001 From: victor Date: Thu, 1 Aug 2024 14:34:00 +0200 Subject: [PATCH 02/69] add unit tests --- src/qililab/__init__.py | 1 + .../{parameter.py => fluqe_parameter.py} | 0 tests/analog/test_one_qubit_transpiler.py | 0 tests/test_parameter.py | 31 +++++++++++++++++++ 4 files changed, 32 insertions(+) rename src/qililab/{parameter.py => fluqe_parameter.py} (100%) create mode 100644 tests/analog/test_one_qubit_transpiler.py create mode 100644 tests/test_parameter.py diff --git a/src/qililab/__init__.py b/src/qililab/__init__.py index f1ddc325b..be162a6c7 100644 --- a/src/qililab/__init__.py +++ b/src/qililab/__init__.py @@ -23,6 +23,7 @@ from .execute_circuit import execute from .qprogram import Calibration, Domain, QbloxCompiler, QProgram, QuantumMachinesCompiler from .result import Results, stream_results +from .fluqe_parameter import Parameter as FluqeParameter from .typings import Parameter from .utils import Loop from .utils.serialization import serialize, serialize_to, deserialize, deserialize_from diff --git a/src/qililab/parameter.py b/src/qililab/fluqe_parameter.py similarity index 100% rename from src/qililab/parameter.py rename to src/qililab/fluqe_parameter.py diff --git a/tests/analog/test_one_qubit_transpiler.py b/tests/analog/test_one_qubit_transpiler.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_parameter.py b/tests/test_parameter.py new file mode 100644 index 000000000..c25d343ae --- /dev/null +++ b/tests/test_parameter.py @@ -0,0 +1,31 @@ +import pytest + +from qililab import FluqeParameter as Parameter + + +@pytest.fixture +@pytest.fixture(name="test_parameter") +def test_parameter(): + """Dummy parameter for testing""" + return Parameter(name="foo", value=2, set_method=lambda x: 2 * x) + + +class TestParameter: + """Unit tests for the ``Parameter`` class.""" + + def test_init(self, test_parameter): + """Test init method""" + assert test_parameter.name == "foo" + assert test_parameter._value == 2 + # test set method + test_parameter(2) + assert test_parameter._value == 4 + + def test_get(self, test_parameter): + """Test get method""" + assert test_parameter() == 2 + + def test_set_raw(self, test_parameter): + """Test set_raw method""" + test_parameter.set_raw(7) + assert test_parameter._value == 7 From 5bb6efc1025c033795f8d420871e7d873caa785a Mon Sep 17 00:00:00 2001 From: victor Date: Thu, 1 Aug 2024 18:07:31 +0200 Subject: [PATCH 03/69] add unit tests --- src/qililab/analog/one_qubit_transpiler.py | 7 ++-- tests/analog/test_one_qubit_transpiler.py | 41 ++++++++++++++++++++++ tests/test_parameter.py | 1 - 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/qililab/analog/one_qubit_transpiler.py b/src/qililab/analog/one_qubit_transpiler.py index a5760b633..45a6bb1c1 100644 --- a/src/qililab/analog/one_qubit_transpiler.py +++ b/src/qililab/analog/one_qubit_transpiler.py @@ -4,7 +4,7 @@ from typing import Any, Callable -from ..parameter import Parameter +from qililab.fluqe_parameter import Parameter class Qubit2LevelTranspiler: @@ -39,8 +39,7 @@ def __call__(self, delta: float, epsilon: float) -> tuple[Any, Any]: def _set_delta(self, delta): # sets the value of delta via raw and updates phix accordingly - phix = self.delta_model(delta, inverse=True) - self.phix.set_raw(phix) + self.phix.set_raw(self.delta_model(delta)) return delta def _set_epsilon(self, epsilon): @@ -48,5 +47,5 @@ def _set_epsilon(self, epsilon): since this is meant to be used in the transpiler in conjunction with setting delta (which already updates phix) """ - self.phiz.set_raw(self.epsilon_model(phix=self.phix(), phiz_epsilon=epsilon, inverse=True)) + self.phiz.set_raw(self.epsilon_model(phix=self.phix(), epsilon=epsilon)) return epsilon diff --git a/tests/analog/test_one_qubit_transpiler.py b/tests/analog/test_one_qubit_transpiler.py index e69de29bb..88b7f3dd5 100644 --- a/tests/analog/test_one_qubit_transpiler.py +++ b/tests/analog/test_one_qubit_transpiler.py @@ -0,0 +1,41 @@ +from unittest import mock +from unittest.mock import MagicMock, patch + +import pytest + +import qililab +from qililab.analog import Qubit2LevelTranspiler + + +@patch.object(qililab.fluqe_parameter.Parameter, "foo") +@pytest.fixture(name="dummy_transpiler") +def dummy_transpiler(): + mock_delta = MagicMock(return_value=2) + mock_delta.name = "mock_delta" + mock_epsilon = MagicMock(return_value=3) + mock_epsilon.name = "mock_epsilon" + return Qubit2LevelTranspiler(epsilon_model=mock_epsilon, delta_model=mock_delta) + + +class TestQubit2LevelTranspiler: + def test_init(self, dummy_transpiler): + assert dummy_transpiler.delta_model.name == "mock_delta" + assert dummy_transpiler.epsilon_model.name == "mock_epsilon" + assert dummy_transpiler.delta.name == "delta" + assert dummy_transpiler.epsilon.name == "epsilon" + assert dummy_transpiler.phiz.name == "phiz" + assert dummy_transpiler.phix.name == "phix" + + def test_transpiler(self, dummy_transpiler): + # test set delta + dummy_transpiler.delta(4) + dummy_transpiler.delta_model.assert_called_once_with(4) + assert dummy_transpiler.phix() == 2 + # test set epsilon + dummy_transpiler.epsilon(5) + dummy_transpiler.epsilon_model.assert_called_once_with(phix=2, epsilon=5) + assert dummy_transpiler.phiz() == 3 + # test call method + phix, phiz = dummy_transpiler(delta=1, epsilon=2) + assert phix == 2 + assert phiz == 3 diff --git a/tests/test_parameter.py b/tests/test_parameter.py index c25d343ae..835a2bc90 100644 --- a/tests/test_parameter.py +++ b/tests/test_parameter.py @@ -3,7 +3,6 @@ from qililab import FluqeParameter as Parameter -@pytest.fixture @pytest.fixture(name="test_parameter") def test_parameter(): """Dummy parameter for testing""" From 57bbf9c78c67d8b0b6128e6d743917f2234cd8d7 Mon Sep 17 00:00:00 2001 From: victor Date: Thu, 1 Aug 2024 18:31:49 +0200 Subject: [PATCH 04/69] move fluqe parameter inside analog, fix pylint complains --- src/qililab/__init__.py | 1 - src/qililab/analog/__init__.py | 1 + src/qililab/analog/fluqe_parameter.py | 64 +++++++++++++++++++ src/qililab/analog/one_qubit_transpiler.py | 22 +++++-- src/qililab/fluqe_parameter.py | 28 -------- .../test_fluqe_parameter.py} | 2 +- tests/analog/test_one_qubit_transpiler.py | 6 +- 7 files changed, 88 insertions(+), 36 deletions(-) create mode 100644 src/qililab/analog/fluqe_parameter.py delete mode 100644 src/qililab/fluqe_parameter.py rename tests/{test_parameter.py => analog/test_fluqe_parameter.py} (94%) diff --git a/src/qililab/__init__.py b/src/qililab/__init__.py index be162a6c7..f1ddc325b 100644 --- a/src/qililab/__init__.py +++ b/src/qililab/__init__.py @@ -23,7 +23,6 @@ from .execute_circuit import execute from .qprogram import Calibration, Domain, QbloxCompiler, QProgram, QuantumMachinesCompiler from .result import Results, stream_results -from .fluqe_parameter import Parameter as FluqeParameter from .typings import Parameter from .utils import Loop from .utils.serialization import serialize, serialize_to, deserialize, deserialize_from diff --git a/src/qililab/analog/__init__.py b/src/qililab/analog/__init__.py index cb6b57100..716829087 100644 --- a/src/qililab/analog/__init__.py +++ b/src/qililab/analog/__init__.py @@ -1 +1,2 @@ +from .fluqe_parameter import Parameter from .one_qubit_transpiler import Qubit2LevelTranspiler diff --git a/src/qililab/analog/fluqe_parameter.py b/src/qililab/analog/fluqe_parameter.py new file mode 100644 index 000000000..ac19274b6 --- /dev/null +++ b/src/qililab/analog/fluqe_parameter.py @@ -0,0 +1,64 @@ +# Copyright 2023 Qilimanjaro Quantum Tech +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Any, Callable + + +class Parameter: + """Basic implementation of a qcodes like parameter class, but much simpler. + Working, but still work in progress + + + Args: + name(str): name of the parameter + value(any|none): initial value of the parameter + set_method(callable|None): the set method of the parameter. If not is given, defaults to `set_raw()`. + Calling + """ + + def __init__(self, name: str, value: Any | None = None, set_method: Callable | None = None): + """init method""" + # TODO: add units and other useful info + self.name = name + self._value = value + self.set = set_method + + def __call__(self, *args: Any, **kwargs: Any) -> None: + """Call method for the parameter class""" + # TODO: implement get_raw like in qcodes + if args or kwargs: + self._value = self.set(*args, **kwargs) if self.set is not None else self.set_raw(args[0]) + + else: + return self.get(*args, **kwargs) + + def get(self) -> Any: + """Get method. Returns the parameter current value. + + Returns: + Any: parameter value + """ + return self._value + + def set_raw(self, value: Any) -> Any: + """set_raw method. This is the default if no set method is given. The parameter is assigned the value given as argument. + + Args: + value (Any): parameter value to set + + Returns: + Any: parameter value set. + """ + self._value = value + return value diff --git a/src/qililab/analog/one_qubit_transpiler.py b/src/qililab/analog/one_qubit_transpiler.py index 45a6bb1c1..c31e9fec3 100644 --- a/src/qililab/analog/one_qubit_transpiler.py +++ b/src/qililab/analog/one_qubit_transpiler.py @@ -1,13 +1,23 @@ -""" -Emulator for the single qubit using the 2level approximation -""" +# Copyright 2023 Qilimanjaro Quantum Tech +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. from typing import Any, Callable -from qililab.fluqe_parameter import Parameter +from qililab.analog import Parameter -class Qubit2LevelTranspiler: +class Qubit2LevelTranspiler: # pylint: disable=too-few-public-methods """Implementation of the transpiler for the 2 level qubit. This is done mainly by inverting the same functions used int he single_qubit_2level emulator model. @@ -15,6 +25,8 @@ class Qubit2LevelTranspiler: eps_model (Callable): epsilon model delta_model (Callable): delta model qubitData (DataClass): dataclass containing info about the physical parameters of the qubit. Defaults to None. + + Calling an instance of this class returns the fluxes phix, phiz for some given Delta, epsilon. """ def __init__(self, epsilon_model: Callable, delta_model: Callable): diff --git a/src/qililab/fluqe_parameter.py b/src/qililab/fluqe_parameter.py deleted file mode 100644 index 93dba610c..000000000 --- a/src/qililab/fluqe_parameter.py +++ /dev/null @@ -1,28 +0,0 @@ -""" Basic implementation of a qcodes like parameter class, but much simpler. -Working, but still work in progress -""" - -from typing import Any, Callable - - -class Parameter: - def __init__(self, name, value=None, set_method: Callable | None = None): - # TODO: add units and other useful info - self.name = name - self._value = value - self.set = set_method - - def __call__(self, *args, **kwargs: Any) -> None: - # TODO: implement get_raw like in qcodes - if args or kwargs: - self._value = self.set(*args, **kwargs) if self.set is not None else self.set_raw(args[0]) - - else: - return self.get(*args, **kwargs) - - def get(self): - return self._value - - def set_raw(self, value): - self._value = value - return value diff --git a/tests/test_parameter.py b/tests/analog/test_fluqe_parameter.py similarity index 94% rename from tests/test_parameter.py rename to tests/analog/test_fluqe_parameter.py index 835a2bc90..eda55ea4d 100644 --- a/tests/test_parameter.py +++ b/tests/analog/test_fluqe_parameter.py @@ -1,6 +1,6 @@ import pytest -from qililab import FluqeParameter as Parameter +from qililab.analog import Parameter @pytest.fixture(name="test_parameter") diff --git a/tests/analog/test_one_qubit_transpiler.py b/tests/analog/test_one_qubit_transpiler.py index 88b7f3dd5..ce78d33d4 100644 --- a/tests/analog/test_one_qubit_transpiler.py +++ b/tests/analog/test_one_qubit_transpiler.py @@ -1,3 +1,4 @@ +"""Test the one qubit 2 level transpiler""" from unittest import mock from unittest.mock import MagicMock, patch @@ -7,9 +8,10 @@ from qililab.analog import Qubit2LevelTranspiler -@patch.object(qililab.fluqe_parameter.Parameter, "foo") +@patch.object(qililab.analog.fluqe_parameter.Parameter, "foo") @pytest.fixture(name="dummy_transpiler") def dummy_transpiler(): + """Transpiler dummy fixture""" mock_delta = MagicMock(return_value=2) mock_delta.name = "mock_delta" mock_epsilon = MagicMock(return_value=3) @@ -19,6 +21,7 @@ def dummy_transpiler(): class TestQubit2LevelTranspiler: def test_init(self, dummy_transpiler): + """Test init method""" assert dummy_transpiler.delta_model.name == "mock_delta" assert dummy_transpiler.epsilon_model.name == "mock_epsilon" assert dummy_transpiler.delta.name == "delta" @@ -27,6 +30,7 @@ def test_init(self, dummy_transpiler): assert dummy_transpiler.phix.name == "phix" def test_transpiler(self, dummy_transpiler): + """Test transpiler""" # test set delta dummy_transpiler.delta(4) dummy_transpiler.delta_model.assert_called_once_with(4) From 1c07db70f4f21460e4f94973e694412b54effe73 Mon Sep 17 00:00:00 2001 From: victor Date: Thu, 1 Aug 2024 18:53:23 +0200 Subject: [PATCH 05/69] draft of anneal program with transpiler for 1q --- src/qililab/analog/annealing_program.py | 35 +++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/qililab/analog/annealing_program.py diff --git a/src/qililab/analog/annealing_program.py b/src/qililab/analog/annealing_program.py new file mode 100644 index 000000000..c9b54edaa --- /dev/null +++ b/src/qililab/analog/annealing_program.py @@ -0,0 +1,35 @@ +from typing import Callable + + +class AnnealingProgram: + """Class for an Annealing Program. The program should have the format + + [ + {"qubit_0": {"sigma_x" : 0, "sigma_y" : 1, "sigma_z" : 2}, + "coupler_1_0 : {...}, + }, # time=0ns + {...}, # time=1ns + . + . + . + ] + """ + + def __init__(self, anneal_program: list[dict[str, dict[str, float]]]): + # implemented only for single qubits so far + if len(anneal_program[0].keys()) != 1: + raise ValueError( + f"The annealing program only supports single qubit anneal so far. Got elements {anneal_program[0].keys()} for the anneal instead" + ) + self.anneal_program = anneal_program + + def transpile(self, transpiler: Callable): + """First implementation of a transpiler, pretty basic but good as a first step""" + # iterate over each anneal step and transpile ising to fluxes + for anneal_step in self.anneal_program: + for circuit_element in anneal_step: + phix, phiz = transpiler( + delta=anneal_step[circuit_element]["sigma_x"], epsilon=anneal_step[circuit_element]["sigma_z"] + ) + anneal_step[circuit_element]["phix"] = phix + anneal_step[circuit_element]["phiz"] = phiz From c9f49d8c0d622f12687a74ab5f55b7750713b947 Mon Sep 17 00:00:00 2001 From: victor Date: Tue, 6 Aug 2024 16:26:14 +0200 Subject: [PATCH 06/69] add analog program execution workflow --- examples/runcards/galadriel.yml | 14 ++++++ src/qililab/__init__.py | 1 + src/qililab/analog/__init__.py | 1 + src/qililab/analog/annealing_program.py | 54 ++++++++++++++++++---- src/qililab/constants.py | 2 + src/qililab/platform/platform.py | 61 ++++++++++++++++++++++++- src/qililab/settings/runcard.py | 13 ++++++ 7 files changed, 136 insertions(+), 10 deletions(-) diff --git a/examples/runcards/galadriel.yml b/examples/runcards/galadriel.yml index 7cfe04377..284cc0c99 100644 --- a/examples/runcards/galadriel.yml +++ b/examples/runcards/galadriel.yml @@ -169,6 +169,20 @@ gates_settings: t_phi: 1 b: 0.5 +flux_control_topology: # example for the flux to bus mapping (analog) + - flux: "phix_q0" + bus: "flux_line_q0_bus" + - flux: "phiz_q0" + bus: "flux_line_q0_bus" + - flux: "phix_q1" + bus: "flux_line_q1_bus" + - flux: "phiz_q1" + bus: "flux_line_q1_bus" + - flux: "phix_c1_0" + bus: "flux_line_q0_bus" + - flux: "phix_c1_0" + bus: "flux_line_q0_bus" + chip: nodes: - name: qubit diff --git a/src/qililab/__init__.py b/src/qililab/__init__.py index f1ddc325b..b749e7b11 100644 --- a/src/qililab/__init__.py +++ b/src/qililab/__init__.py @@ -30,6 +30,7 @@ # moving circuit_transpiler module imports here because it has instruments module dependencies so circular imports can be avoided from .circuit_transpiler import Drag, Wait +from .analog import AnnealingProgram # same as circuit transpiler, top modules should be imported at top with contextlib.suppress(NameError, ImportError): # Since Ipython magic methods can only be imported from inside a Jupyter Notebook, diff --git a/src/qililab/analog/__init__.py b/src/qililab/analog/__init__.py index 716829087..d7e8e0588 100644 --- a/src/qililab/analog/__init__.py +++ b/src/qililab/analog/__init__.py @@ -1,2 +1,3 @@ +from .annealing_program import AnnealingProgram from .fluqe_parameter import Parameter from .one_qubit_transpiler import Qubit2LevelTranspiler diff --git a/src/qililab/analog/annealing_program.py b/src/qililab/analog/annealing_program.py index c9b54edaa..0b7b98ba9 100644 --- a/src/qililab/analog/annealing_program.py +++ b/src/qililab/analog/annealing_program.py @@ -1,6 +1,13 @@ -from typing import Callable +from dataclasses import dataclass +from typing import Any, Callable +import numpy as np +from qililab.platform.components import Bus +from qililab.waveforms import Arbitrary as ArbitraryWave + + +@dataclass class AnnealingProgram: """Class for an Annealing Program. The program should have the format @@ -15,16 +22,17 @@ class AnnealingProgram: ] """ - def __init__(self, anneal_program: list[dict[str, dict[str, float]]]): - # implemented only for single qubits so far - if len(anneal_program[0].keys()) != 1: - raise ValueError( - f"The annealing program only supports single qubit anneal so far. Got elements {anneal_program[0].keys()} for the anneal instead" - ) - self.anneal_program = anneal_program + platform: Any + anneal_program: list[dict[str, dict[str, float]]] def transpile(self, transpiler: Callable): - """First implementation of a transpiler, pretty basic but good as a first step""" + """First implementation of a transpiler, pretty basic but good as a first step. Transpiles from ising coefficients to fluxes + + Args: + transpiler (Callable): Transpiler to use. The transpiler should take 2 values as arguments (delta, epsilon) + and return 2 values (phix, phiz) + """ + # iterate over each anneal step and transpile ising to fluxes for anneal_step in self.anneal_program: for circuit_element in anneal_step: @@ -33,3 +41,31 @@ def transpile(self, transpiler: Callable): ) anneal_step[circuit_element]["phix"] = phix anneal_step[circuit_element]["phiz"] = phiz + + def get_waveforms(self) -> dict[str, tuple[Bus, ArbitraryWave]]: + """Returns a dictionary containing (bus, waveform) for each flux control from the transpiled fluxes. `AnnealingProgram.transpile` should be run first. The waveform is an arbitrary waveform obtained from the transpiled fluxes. + + Returns: + dict[str,tuple[Bus,ArbitraryWave]]: Dictionary containing (bus, waveform) for each flux control (i.e. phix or phiz). + """ + # parse names from algorithm notation (e.g. qubit_0) to runcard notation (e.g. q0) + element_name_map = {"qubit": "q", "coupler": "c"} + circuit_element_map = { + (element, flux): f"{flux}_{element_name_map[element.split('_', 1)[0]]}{element.split('_', 1)[1]}" + for element in self.anneal_program[0].keys() + for flux in self.anneal_program[0][element].keys() + if "phi" in flux + } # {(element, flux): flux_line} + + # Initialize dictionary with flux_lines pointing to (corresponding bus, waveform) + anneal_waveforms = { # type: ignore[var-annotated] + flux_line: (self.platform.get_element(flux_line), []) for flux_line in circuit_element_map.values() + } + # unravel each point of the anneal program to get timewise arrays of waveforms + for anneal_step in self.anneal_program: + for circuit_element, flux in circuit_element_map.keys(): + anneal_waveforms[circuit_element_map[circuit_element, flux]][1].append( + anneal_step[circuit_element][flux] + ) + + return {key: (value[0], ArbitraryWave(np.array(value[1]))) for key, value in anneal_waveforms.items()} diff --git a/src/qililab/constants.py b/src/qililab/constants.py index c30900e52..eeda140d3 100644 --- a/src/qililab/constants.py +++ b/src/qililab/constants.py @@ -28,6 +28,8 @@ GATE_ALIAS_REGEX = r"(?P[a-zA-Z]+)\((?P\d+(?:,\s*\d+)*)\)" +FLUX_CONTROL_REGEX = r"^(?Pphi[xzy])_(?:q(?P\d+)|c(?P\d+_\d+))$" # regex to identify flux control to bus mapping. Example flux control "phix_c0_1" corresponding to phix control of the coupler between qubits 0 and 1 + # TODO: Distribute constants over different classes diff --git a/src/qililab/platform/platform.py b/src/qililab/platform/platform.py index 9f91e77f2..224607aae 100644 --- a/src/qililab/platform/platform.py +++ b/src/qililab/platform/platform.py @@ -19,6 +19,7 @@ from copy import deepcopy from dataclasses import asdict from queue import Queue +from typing import Callable import numpy as np from qibo.gates import M @@ -27,10 +28,11 @@ from qpysequence import Sequence as QpySequence from ruamel.yaml import YAML +from qililab.analog import AnnealingProgram from qililab.chip import Chip from qililab.circuit_transpiler import CircuitTranspiler from qililab.config import logger -from qililab.constants import GATE_ALIAS_REGEX, RUNCARD +from qililab.constants import FLUX_CONTROL_REGEX, GATE_ALIAS_REGEX, RUNCARD from qililab.instrument_controllers import InstrumentController, InstrumentControllers from qililab.instrument_controllers.utils import InstrumentControllerFactory from qililab.instruments.instrument import Instrument @@ -296,6 +298,9 @@ def __init__(self, runcard: Runcard): ) """All the buses of the platform and their necessary settings (``dataclass``). Each individual bus is contained in a list within the dataclass.""" + self.flux_to_bus_topology = runcard.flux_control_topology + """Flux to bus mapping for analog control""" + self._connected_to_instruments: bool = False """Boolean indicating the connection status to the instruments. Defaults to False (not connected).""" @@ -374,6 +379,7 @@ def get_element(self, alias: str): Returns: tuple[object, list | None]: Element class together with the index of the bus where the element is located. """ + # TODO: fix docstring, bus is not returned in most cases if alias is not None: if alias == "platform": return self.gates_settings @@ -384,6 +390,23 @@ def get_element(self, alias: str): qubits = ast.literal_eval(qubits_str) if f"{name}({qubits_str})" in self.gates_settings.gate_names: return self.gates_settings.get_gate(name=name, qubits=qubits) + regex_match = re.search(FLUX_CONTROL_REGEX, alias) + if regex_match is not None: + element_type = regex_match.lastgroup + element_shorthands = {"qubit": "q", "coupler": "c"} + if element_type not in element_shorthands: + raise ValueError("Invalid element selected in runcard for flux {flux} with alias {alias}") + flux = regex_match["flux"] + return self._get_bus_by_alias( + next( + ( + element.bus + for element in self.flux_to_bus_topology + if element.flux == f"{flux}_{element_shorthands[element_type]}{regex_match[element_type]}" + ), + None, + ) + ) element = self.instruments.get_instrument(alias=alias) if element is None: @@ -567,6 +590,42 @@ def __str__(self) -> str: """ return str(YAML().dump(self.to_dict(), io.BytesIO())) + def execute_anneal_program( + self, anneal_program_dict: list[dict[str, dict[str, float]]], transpiler: Callable, averages=1 + ): # TODO: determine default average + """Given an anneal program execute it as a qprogram. + + The anneal program should contain a time ordered list of circuit elements and their corresponging ising coefficients as a dictionary. Example structure: + [ + {"qubit_0": {"sigma_x" : 0, "sigma_y" : 1, "sigma_z" : 2}, + "coupler_1_0 : {...}, + }, # time=0ns + {...}, # time=1ns + . + . + . + ] + This dictionary containing ising coefficients is transpiled to fluxes using the given transpiler. Then the correspoinding waveforms are obtained and assigned to a bus + from the bus to flux mapping given by the runcard. + + Args: + anneal_program_dict (list[dict[str, dict[str, float]]]): anneal program to run + transpiler (Callable): ising to flux transpiler. The transpiler should take 2 values as arguments (delta, epsilon) and return 2 values (phix, phiz) + averages (int, optional): Amount of times to run and average the program over. Defaults to 1. + """ + anneal_program = AnnealingProgram(self, anneal_program_dict) + anneal_program.transpile(transpiler) + anneal_waveforms = anneal_program.get_waveforms() + + qp_anneal = QProgram() + with qp_anneal.average(averages): + for bus, waveform in anneal_waveforms.values(): + qp_anneal.play(bus=bus.alias, waveform=waveform) + + # TODO: define readout + + self.execute_qprogram(qprogram=qp_anneal) + def execute_qprogram( self, qprogram: QProgram, diff --git a/src/qililab/settings/runcard.py b/src/qililab/settings/runcard.py index 3af9c6851..b606ee527 100644 --- a/src/qililab/settings/runcard.py +++ b/src/qililab/settings/runcard.py @@ -85,6 +85,13 @@ class Chip: nodes: list[dict] + @dataclass + class FluxControlTopology: + """Dataclass fluxes (e.g. phix_q0 for phix control of qubit 0) and their corresponding bus (e.g. flux_line_q0_x)""" + + flux: str + bus: str + @nested_dataclass class GatesSettings(Settings): """Dataclass with all the settings and gates definitions needed to decompose gates into pulses.""" @@ -254,10 +261,16 @@ def get_parameter( instruments: list[dict] instrument_controllers: list[dict] gates_settings: GatesSettings + flux_control_topology: list[FluxControlTopology] device_id: int | None = None def __post_init__(self): self.buses = [self.Bus(**bus) for bus in self.buses] if self.buses is not None else None + self.flux_control_topology = ( + [self.FluxControlTopology(**flux_control) for flux_control in self.flux_control_topology] + if self.flux_control_topology is not None + else None + ) if self.device_id is not None: warn( "`device_id` argument is deprecated and will be removed soon. Please remove it from your runcard file.", From d5543d38d04547e264bdc87acfa694b55c992367 Mon Sep 17 00:00:00 2001 From: victor Date: Tue, 6 Aug 2024 17:00:08 +0200 Subject: [PATCH 07/69] add changelog --- docs/releases/changelog-dev.md | 44 ++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/docs/releases/changelog-dev.md b/docs/releases/changelog-dev.md index 3fcf97096..aaa3a9130 100644 --- a/docs/releases/changelog-dev.md +++ b/docs/releases/changelog-dev.md @@ -2,6 +2,50 @@ ### New features since last release +- Add workflow for the execution of annealing programs. + Example: + + ```python + import qililab as ql + + platform = ql.build_platform("examples/runcards/galadriel.yml") + anneal_program_dict = [ + {qubit_0": {"sigma_x" : 0, "sigma_y": 0, "sigma_z": 1, "phix":1, "phiz":1}, + "qubit_1": {"sigma_x" : 0.1, "sigma_y": 0.1, "sigma_z": 0.1}, + "coupler_0_1": {"sigma_x" : 1, "sigma_y": 0.2, "sigma_z": 0.2} + }, + {"qubit_0": {"sigma_x" : 0.1, "sigma_y": 0.1, "sigma_z": 1.1}, + "qubit_1": {"sigma_x" : 0.2, "sigma_y": 0.2, "sigma_z": 0.2}, + "coupler_0_1": {"sigma_x" : 0.9, "sigma_y": 0.1, "sigma_z": 0.1} + }, + {"qubit_0": {"sigma_x" : 0.3, "sigma_y": 0.3, "sigma_z": 0.7}, + "qubit_1": {"sigma_x" : 0.5, "sigma_y": 0.2, "sigma_z": 0.01}, + "coupler_0_1": {"sigma_x" : 0.5, "sigma_y": 0, "sigma_z": -1} + } + ] + + results = platform.execute_anneal_program(anneal_program_dict=anneal_program_dict,transpiler=lambda delta, epsilon: (delta, epsilon), averages=100_000) + ``` + + Alternatively, each step of the workflow can be executed separately i.e. the following is equivalent to the above: + + ```python + import qililab as ql + platform = ql.build_platform("examples/runcards/galadriel.yml") + anneal_program_dict = [...] # same as in the above example + # intialize annealing program class + anneal_program = ql.AnnealingProgram(platform=platform, anneal_program=anneal_program_dict) + # transpile ising to flux, now flux values can be accessed same as ising coeff values + # eg. for phix qubit 0 at t=1ns anneal_program.anneal_program[1]["qubit_0"]["phix"] + anneal_program.transpile(lambda delta,epsilon: (delta,epsilon)) + # get a dictionary {control_flux: (bus, waveform) from the transpiled fluxes + anneal_waveforms = anneal_program.get_waveforms() + # from here on we can create a qprogram to execute the annealing schedule + [#767](https://github.com/qilimanjaro-tech/qililab/pull/767) + + + ``` + - Added the Qblox-specific `set_markers()` method in `QProgram`. This method takes a 4-bit binary mask as input, where `0` means that the associated marker will be open (no signal) and `1` means that the associated marker will be closed (signal). The mapping between bit indexes and markers depends on the Qblox module that the compiled `QProgram` will run on. Example: From a2a0bec0bfea0f040c0c37f54bab990d2c4ad604 Mon Sep 17 00:00:00 2001 From: victor Date: Wed, 7 Aug 2024 14:16:40 +0200 Subject: [PATCH 08/69] fix mypy --- src/qililab/analog/annealing_program.py | 14 ++++++++++++++ src/qililab/platform/platform.py | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/qililab/analog/annealing_program.py b/src/qililab/analog/annealing_program.py index 0b7b98ba9..6b1e8aec2 100644 --- a/src/qililab/analog/annealing_program.py +++ b/src/qililab/analog/annealing_program.py @@ -1,3 +1,17 @@ +# Copyright 2023 Qilimanjaro Quantum Tech +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + from dataclasses import dataclass from typing import Any, Callable diff --git a/src/qililab/platform/platform.py b/src/qililab/platform/platform.py index 1f6b0285b..55b7cc81f 100644 --- a/src/qililab/platform/platform.py +++ b/src/qililab/platform/platform.py @@ -401,7 +401,7 @@ def get_element(self, alias: str): next( ( element.bus - for element in self.flux_to_bus_topology + for element in self.flux_to_bus_topology # type: ignore[union-attr] if element.flux == f"{flux}_{element_shorthands[element_type]}{regex_match[element_type]}" ), None, From aa52461d432bb8ff811249aa085ab483dc1f5bb2 Mon Sep 17 00:00:00 2001 From: victor Date: Wed, 7 Aug 2024 14:36:24 +0200 Subject: [PATCH 09/69] fix docstring --- src/qililab/platform/platform.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/qililab/platform/platform.py b/src/qililab/platform/platform.py index 55b7cc81f..6e63434dd 100644 --- a/src/qililab/platform/platform.py +++ b/src/qililab/platform/platform.py @@ -594,7 +594,6 @@ def execute_anneal_program( self, anneal_program_dict: list[dict[str, dict[str, float]]], transpiler: Callable, averages=1 ): # TODO: determine default average """Given an anneal program execute it as a qprogram. - The anneal program should contain a time ordered list of circuit elements and their corresponging ising coefficients as a dictionary. Example structure: [ {"qubit_0": {"sigma_x" : 0, "sigma_y" : 1, "sigma_z" : 2}, From 6527d4c1c526a29db10679f004e27b36f4e24bce Mon Sep 17 00:00:00 2001 From: victor Date: Wed, 7 Aug 2024 17:29:22 +0200 Subject: [PATCH 10/69] add unit test for anneal program --- src/qililab/constants.py | 1 + src/qililab/platform/platform.py | 1 + tests/analog/test_annealing_program.py | 140 +++++++++++++++++++++++++ tests/data.py | 10 ++ 4 files changed, 152 insertions(+) create mode 100644 tests/analog/test_annealing_program.py diff --git a/src/qililab/constants.py b/src/qililab/constants.py index eeda140d3..031b43633 100644 --- a/src/qililab/constants.py +++ b/src/qililab/constants.py @@ -58,6 +58,7 @@ class RUNCARD: CURRENT_SOURCE = "current_source" DISTORTIONS = "distortions" DELAY = "delay" + FLUX_CONTROL_TOPOLOGY = "flux_control_topology" class PLATFORM: diff --git a/src/qililab/platform/platform.py b/src/qililab/platform/platform.py index 6e63434dd..6416a40e2 100644 --- a/src/qililab/platform/platform.py +++ b/src/qililab/platform/platform.py @@ -397,6 +397,7 @@ def get_element(self, alias: str): if element_type not in element_shorthands: raise ValueError("Invalid element selected in runcard for flux {flux} with alias {alias}") flux = regex_match["flux"] + # TODO: support commuting the name of the coupler eg. c1_0 = c0_1 return self._get_bus_by_alias( next( ( diff --git a/tests/analog/test_annealing_program.py b/tests/analog/test_annealing_program.py new file mode 100644 index 000000000..c67f5be16 --- /dev/null +++ b/tests/analog/test_annealing_program.py @@ -0,0 +1,140 @@ +"""Test the annealing program class""" +from unittest.mock import MagicMock + +import numpy as np +import pytest + +from qililab import AnnealingProgram +from tests.data import Galadriel +from tests.test_utils import build_platform + + +@pytest.fixture(name="anneal_program_dictionary") +def anneal_program_dictionary(): + """Dummy anneal program dictionary""" + return [ + { + "qubit_0": {"sigma_x": 0, "sigma_y": 0, "sigma_z": 1}, + "qubit_1": {"sigma_x": 0.1, "sigma_y": 0.1, "sigma_z": 0.1}, + "coupler_0_1": {"sigma_x": 1, "sigma_y": 0.2, "sigma_z": 0.2}, + }, + { + "qubit_0": {"sigma_x": 0.1, "sigma_y": 0.1, "sigma_z": 1.1}, + "qubit_1": {"sigma_x": 0.2, "sigma_y": 0.2, "sigma_z": 0.2}, + "coupler_0_1": {"sigma_x": 0.9, "sigma_y": 0.1, "sigma_z": 0.1}, + }, + { + "qubit_0": {"sigma_x": 0.3, "sigma_y": 0.3, "sigma_z": 0.7}, + "qubit_1": {"sigma_x": 0.5, "sigma_y": 0.2, "sigma_z": 0.01}, + "coupler_0_1": {"sigma_x": 0.5, "sigma_y": 0, "sigma_z": -1}, + }, + ] + + +@pytest.fixture(name="anneal_program_dictionary_with_flux") +def anneal_program_dictionary_with_flux(): + """Dummy anneal program dictionary with transpiled fluxes""" + return [ + { + "qubit_0": {"sigma_x": 0, "sigma_y": 0, "sigma_z": 1, "phix": 1, "phiz": 0}, + "qubit_1": {"sigma_x": 0.1, "sigma_y": 0.1, "sigma_z": 0.1, "phix": 1.1, "phiz": 0.1}, + "coupler_0_1": {"sigma_x": 1, "sigma_y": 0.2, "sigma_z": 0.2, "phix": 1.2, "phiz": 0.2}, + }, + { + "qubit_0": {"sigma_x": 0.1, "sigma_y": 0.1, "sigma_z": 1.1, "phix": 0.9, "phiz": 0.8}, + "qubit_1": {"sigma_x": 0.2, "sigma_y": 0.2, "sigma_z": 0.2, "phix": 0.7, "phiz": 0.6}, + "coupler_0_1": {"sigma_x": 0.9, "sigma_y": 0.1, "sigma_z": 0.1, "phix": 0.5, "phiz": 0.4}, + }, + ] + + +@pytest.fixture(name="annealing_program") +def dummy_annealing_program(anneal_program_dictionary): + """Build dummy annealing program""" + return AnnealingProgram( + platform=build_platform(runcard=Galadriel.runcard), anneal_program=anneal_program_dictionary + ) + + +@pytest.fixture(name="annealing_program_with_flux") +def dummy_annealing_program_with_flux(anneal_program_dictionary_with_flux): + """Build dummy annealing program with fluxes already transpiled""" + return AnnealingProgram( + platform=build_platform(runcard=Galadriel.runcard), anneal_program=anneal_program_dictionary_with_flux + ) + + +def flux_to_bus(flux): + """Return the corresponding bus to a given flux from the runcard topology""" + return next(element["bus"] for element in Galadriel.runcard["flux_control_topology"] if element["flux"] == flux) + + +def dummy_transpiler(delta, epsilon): + """Dummy transpiler for testing""" + return (2 * delta, 3 * epsilon) + + +class TestAnnealingProgram: + def test_init(self, annealing_program, anneal_program_dictionary): + """Test init method""" + assert annealing_program.platform.to_dict() == Galadriel.runcard + assert annealing_program.anneal_program == anneal_program_dictionary + + def test_transpile(self, annealing_program, anneal_program_dictionary): + """Test transpile method""" + annealing_program.transpile(transpiler=dummy_transpiler) + assert sum( + anneal_step[element]["phix"] == 2 * anneal_program_dictionary[i][element]["sigma_x"] + for i, anneal_step in enumerate(anneal_program_dictionary) + for element in anneal_step.keys() + ) == len(anneal_program_dictionary) * len(anneal_program_dictionary[0]) + assert sum( + anneal_step[element]["phiz"] == 3 * anneal_program_dictionary[i][element]["sigma_z"] + for i, anneal_step in enumerate(anneal_program_dictionary) + for element in anneal_step.keys() + ) == len(anneal_program_dictionary) * len(anneal_program_dictionary[0]) + + def test_get_waveforms(self, annealing_program_with_flux, anneal_program_dictionary_with_flux): + """Test get waveforms method works as intended""" + anneal_waveforms = annealing_program_with_flux.get_waveforms() + anneal_waveforms = {key: (value[0].alias, value[1].envelope()) for key, value in anneal_waveforms.items()} + + phix_q0_waveform = ( + flux_to_bus("phix_q0"), + np.array([anneal_step["qubit_0"]["phix"] for anneal_step in anneal_program_dictionary_with_flux]), + ) + phiz_q0_waveform = ( + flux_to_bus("phiz_q0"), + np.array([anneal_step["qubit_0"]["phiz"] for anneal_step in anneal_program_dictionary_with_flux]), + ) + phix_q1_waveform = ( + flux_to_bus("phix_q1"), + np.array([anneal_step["qubit_1"]["phix"] for anneal_step in anneal_program_dictionary_with_flux]), + ) + phiz_q1_waveform = ( + flux_to_bus("phiz_q1"), + np.array([anneal_step["qubit_1"]["phiz"] for anneal_step in anneal_program_dictionary_with_flux]), + ) + phix_c0_1_waveform = ( + flux_to_bus("phix_c0_1"), + np.array([anneal_step["coupler_0_1"]["phix"] for anneal_step in anneal_program_dictionary_with_flux]), + ) + phiz_c0_1_waveform = ( + flux_to_bus("phiz_c0_1"), + np.array([anneal_step["coupler_0_1"]["phiz"] for anneal_step in anneal_program_dictionary_with_flux]), + ) + + assert anneal_waveforms["phix_q0"][0] == phix_q0_waveform[0] + np.allclose(anneal_waveforms["phix_q0"][1], phix_q0_waveform[1]) + assert anneal_waveforms["phiz_q0"][0] == phiz_q0_waveform[0] + np.allclose(anneal_waveforms["phiz_q0"][1], phiz_q0_waveform[1]) + + assert anneal_waveforms["phix_q1"][0] == phix_q1_waveform[0] + np.allclose(anneal_waveforms["phix_q1"][1], phix_q1_waveform[1]) + assert anneal_waveforms["phiz_q1"][0] == phiz_q1_waveform[0] + np.allclose(anneal_waveforms["phiz_q1"][1], phiz_q1_waveform[1]) + + assert anneal_waveforms["phix_c0_1"][0] == phix_c0_1_waveform[0] + np.allclose(anneal_waveforms["phix_c0_1"][1], phix_c0_1_waveform[1]) + assert anneal_waveforms["phiz_c0_1"][0] == phiz_c0_1_waveform[0] + np.allclose(anneal_waveforms["phiz_c0_1"][1], phiz_c0_1_waveform[1]) diff --git a/tests/data.py b/tests/data.py index 2d7984c08..580183969 100644 --- a/tests/data.py +++ b/tests/data.py @@ -237,6 +237,15 @@ class Galadriel: }, } + flux_control_topology: list[dict[str, str]] = [ + {"flux": "phix_q0", "bus": "flux_line_q0_bus"}, + {"flux": "phiz_q0", "bus": "flux_line_q0_bus"}, + {"flux": "phix_q1", "bus": "drive_line_q0_bus"}, + {"flux": "phiz_q1", "bus": "drive_line_q0_bus"}, + {"flux": "phix_c0_1", "bus": "flux_line_q0_bus"}, + {"flux": "phiz_c0_1", "bus": "flux_line_q0_bus"}, + ] + pulsar_controller_qcm_0: dict[str, Any] = { "name": InstrumentControllerName.QBLOX_PULSAR, "alias": "pulsar_controller_qcm_0", @@ -761,6 +770,7 @@ class Galadriel: runcard: dict[str, Any] = { RUNCARD.NAME: name, RUNCARD.GATES_SETTINGS: gates_settings, + RUNCARD.FLUX_CONTROL_TOPOLOGY: flux_control_topology, RUNCARD.CHIP: chip, RUNCARD.BUSES: buses, RUNCARD.INSTRUMENTS: instruments, From 81bb027cbc50f4c337126b364cb6aa427b38e518 Mon Sep 17 00:00:00 2001 From: victor Date: Thu, 8 Aug 2024 17:02:25 +0200 Subject: [PATCH 11/69] wip platform unit tests --- tests/platform/test_platform.py | 41 ++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index 572e1a365..42b78b276 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -5,6 +5,7 @@ import re from pathlib import Path from queue import Queue +from unittest import mock from unittest.mock import MagicMock, patch import numpy as np @@ -14,7 +15,8 @@ from qpysequence import Sequence from ruamel.yaml import YAML -from qililab import save_platform +import qililab +from qililab import Arbitrary, save_platform from qililab.chip import Chip, Qubit from qililab.constants import DEFAULT_PLATFORM_NAME from qililab.instrument_controllers import InstrumentControllers @@ -109,6 +111,31 @@ def fixture_qblox_results(): ] +@pytest.fixture(name="anneal_qprogram") +def anneal_qprogram(runcard): + platform = Platform(runcard=runcard) + anneal_waveforms = { + "phix_q0": ( + platform._get_bus_by_alias( + next(element.bus for element in platform.flux_to_bus_topology if element.flux == "phix_q0") + ), + Arbitrary(np.array([1])), + ), + "phiz_q0": ( + platform._get_bus_by_alias( + next(element.bus for element in platform.flux_to_bus_topology if element.flux == "phiz_q0") + ), + Arbitrary(np.array([2])), + ), + } + averages = 2 + qp_anneal = QProgram() + with qp_anneal.average(averages): + for bus, waveform in anneal_waveforms.values(): + qp_anneal.play(bus=bus.alias, waveform=waveform) + return qp_anneal + + class TestPlatformInitialization: """Unit tests for the Platform class initialization""" @@ -350,6 +377,18 @@ def _compile_and_assert(self, platform: Platform, program: Circuit | PulseSchedu assert all(isinstance(sequence, Sequence) for sequence in sequences_list) assert sequences_list[0]._program.duration == 200_000 * 1000 + 4 + 4 + 4 + def test_execute_anneal_program(self, platform: Platform, anneal_qprogram): + mock_execute_qprogram = MagicMock() + platform.execute_qprogram = mock_execute_qprogram + transpiler = MagicMock() + transpiler.return_value = (1, 2) + # with patch(qililab.analog.annealing_program, "AnnealingProgram") as dummy_anneal_program: + platform.execute_anneal_program( + anneal_program_dict=[{"qubit_0": {"sigma_x": 0.1, "sigma_z": 0.2}}], transpiler=transpiler, averages=2 + ) + assert True + pass + def test_execute_qprogram_with_qblox(self, platform: Platform): """Test that the execute method compiles the qprogram, calls the buses to run and return the results.""" drive_wf = IQPair(I=Square(amplitude=1.0, duration=40), Q=Square(amplitude=0.0, duration=40)) From 9b299975854801c9706522784dc7a377c067f6d5 Mon Sep 17 00:00:00 2001 From: victor Date: Thu, 8 Aug 2024 19:35:44 +0200 Subject: [PATCH 12/69] add to_dict method to qprogram in order to tests that a qprogram is equivalent to another --- src/qililab/qprogram/qprogram.py | 51 +++++++++++++++++++++++++++++++- tests/platform/test_platform.py | 5 ++-- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/qililab/qprogram/qprogram.py b/src/qililab/qprogram/qprogram.py index 8208d9500..8bf796e1a 100644 --- a/src/qililab/qprogram/qprogram.py +++ b/src/qililab/qprogram/qprogram.py @@ -14,7 +14,7 @@ from collections import deque from copy import deepcopy -from dataclasses import replace +from dataclasses import asdict, fields, replace from typing import overload import numpy as np @@ -29,6 +29,7 @@ MeasureWithCalibratedWaveform, MeasureWithCalibratedWaveformWeights, MeasureWithCalibratedWeights, + Operation, Play, PlayWithCalibratedWaveform, ResetPhase, @@ -101,6 +102,54 @@ def __init__(self) -> None: self._variables: list[Variable] = [] self._block_stack: deque[Block] = deque([self._body]) + def __dict__(self) -> dict: # type: ignore[override] + """Dictionary representation""" + return self._to_dict(self) + + def _to_dict(self, program) -> dict: + """Converts qprogram to dictionary containing the instructions + + Args: + program (QProgram): qprogram to convert + + Returns: + dict: qprogram dictionary + """ + if hasattr(program, "body"): + return self._to_dict(program.body) + + temp_dict = {"name": type(program).__name__} + for field in fields(program): + if field.name in ["_uuid", "variable"]: + continue + temp_dict[field.name] = ( + getattr(program, field.name) + if field.name != "elements" + else [self._to_dict(element) for element in getattr(program, field.name)] + ) + + if isinstance(program, Operation): + if hasattr(program, "waveform"): + temp_dict["waveform"] = ( + [(type(program.waveform).__name__, program.waveform.envelope())] + if isinstance(program.waveform, Waveform) + else [ + (type(program.waveform.I).__name__, program.waveform.I.envelope()), # type: ignore[assignment] + (type(program.waveform.Q).__name__, program.waveform.Q.envelope()), # type: ignore[assignment] + ] + ) + if hasattr(program, "weights"): + temp_dict["weights"] = [ # type: ignore[assignment] + (type(program.weights.I).__name__, program.weights.I.envelope()), + (type(program.weights.Q).__name__, program.weights.Q.envelope()), + ] + + for key, value in temp_dict.copy().items(): + if "UUID" in str(value): + temp_dict[key] = None + + return temp_dict + def _append_to_block_stack(self, block: Block): self._block_stack.append(block) diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index 42b78b276..a29e09a72 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -379,15 +379,14 @@ def _compile_and_assert(self, platform: Platform, program: Circuit | PulseSchedu def test_execute_anneal_program(self, platform: Platform, anneal_qprogram): mock_execute_qprogram = MagicMock() - platform.execute_qprogram = mock_execute_qprogram + platform.execute_qprogram = mock_execute_qprogram # type: ignore[method-assign] transpiler = MagicMock() transpiler.return_value = (1, 2) # with patch(qililab.analog.annealing_program, "AnnealingProgram") as dummy_anneal_program: platform.execute_anneal_program( anneal_program_dict=[{"qubit_0": {"sigma_x": 0.1, "sigma_z": 0.2}}], transpiler=transpiler, averages=2 ) - assert True - pass + anneal_qprogram.__dict__() == mock_execute_qprogram.call_args[1]["qprogram"].__dict__() def test_execute_qprogram_with_qblox(self, platform: Platform): """Test that the execute method compiles the qprogram, calls the buses to run and return the results.""" From 8ad04572b54643c6fe3b0e49abd8a2b4add55e3c Mon Sep 17 00:00:00 2001 From: victor Date: Sun, 11 Aug 2024 14:55:57 +0200 Subject: [PATCH 13/69] fix tests --- src/qililab/platform/platform.py | 16 +++++- src/qililab/qprogram/qprogram.py | 95 ++++++++++++++++---------------- src/qililab/settings/runcard.py | 3 + tests/platform/test_platform.py | 4 +- 4 files changed, 65 insertions(+), 53 deletions(-) diff --git a/src/qililab/platform/platform.py b/src/qililab/platform/platform.py index 6416a40e2..abff78b94 100644 --- a/src/qililab/platform/platform.py +++ b/src/qililab/platform/platform.py @@ -580,8 +580,19 @@ def to_dict(self): self.instrument_controllers.to_dict() if self.instrument_controllers is not None else None ), } + flux_control_topology_dict = { + RUNCARD.FLUX_CONTROL_TOPOLOGY: [flux_control.to_dict() for flux_control in self.flux_to_bus_topology] + } - return name_dict | gates_settings_dict | chip_dict | buses_dict | instrument_dict | instrument_controllers_dict + return ( + name_dict + | gates_settings_dict + | chip_dict + | buses_dict + | instrument_dict + | instrument_controllers_dict + | flux_control_topology_dict + ) def __str__(self) -> str: """String representation of the platform. @@ -591,9 +602,10 @@ def __str__(self) -> str: """ return str(YAML().dump(self.to_dict(), io.BytesIO())) + # TODO: determine default average def execute_anneal_program( self, anneal_program_dict: list[dict[str, dict[str, float]]], transpiler: Callable, averages=1 - ): # TODO: determine default average + ): """Given an anneal program execute it as a qprogram. The anneal program should contain a time ordered list of circuit elements and their corresponging ising coefficients as a dictionary. Example structure: [ diff --git a/src/qililab/qprogram/qprogram.py b/src/qililab/qprogram/qprogram.py index 8bf796e1a..32d65499a 100644 --- a/src/qililab/qprogram/qprogram.py +++ b/src/qililab/qprogram/qprogram.py @@ -14,7 +14,7 @@ from collections import deque from copy import deepcopy -from dataclasses import asdict, fields, replace +from dataclasses import fields, replace from typing import overload import numpy as np @@ -102,54 +102,6 @@ def __init__(self) -> None: self._variables: list[Variable] = [] self._block_stack: deque[Block] = deque([self._body]) - def __dict__(self) -> dict: # type: ignore[override] - """Dictionary representation""" - return self._to_dict(self) - - def _to_dict(self, program) -> dict: - """Converts qprogram to dictionary containing the instructions - - Args: - program (QProgram): qprogram to convert - - Returns: - dict: qprogram dictionary - """ - if hasattr(program, "body"): - return self._to_dict(program.body) - - temp_dict = {"name": type(program).__name__} - for field in fields(program): - if field.name in ["_uuid", "variable"]: - continue - temp_dict[field.name] = ( - getattr(program, field.name) - if field.name != "elements" - else [self._to_dict(element) for element in getattr(program, field.name)] - ) - - if isinstance(program, Operation): - if hasattr(program, "waveform"): - temp_dict["waveform"] = ( - [(type(program.waveform).__name__, program.waveform.envelope())] - if isinstance(program.waveform, Waveform) - else [ - (type(program.waveform.I).__name__, program.waveform.I.envelope()), # type: ignore[assignment] - (type(program.waveform.Q).__name__, program.waveform.Q.envelope()), # type: ignore[assignment] - ] - ) - if hasattr(program, "weights"): - temp_dict["weights"] = [ # type: ignore[assignment] - (type(program.weights.I).__name__, program.weights.I.envelope()), - (type(program.weights.Q).__name__, program.weights.Q.envelope()), - ] - - for key, value in temp_dict.copy().items(): - if "UUID" in str(value): - temp_dict[key] = None - - return temp_dict - def _append_to_block_stack(self, block: Block): self._block_stack.append(block) @@ -972,3 +924,48 @@ def measure( ) self.qprogram._active_block.append(operation) self.qprogram._buses.add(bus) + + +def to_readable_dict(program) -> dict: + """Converts qprogram to dictionary containing the instructions + + Args: + program (QProgram): qprogram to convert + + Returns: + dict: qprogram dictionary + """ + if hasattr(program, "body"): + return to_readable_dict(program.body) + + temp_dict = {"name": type(program).__name__} + for field in fields(program): + if field.name in ["_uuid", "variable"]: + continue + temp_dict[field.name] = ( + getattr(program, field.name) + if field.name != "elements" + else [to_readable_dict(element) for element in getattr(program, field.name)] + ) + + if isinstance(program, Operation): + if hasattr(program, "waveform"): + temp_dict["waveform"] = ( + [(type(program.waveform).__name__, program.waveform.envelope())] + if isinstance(program.waveform, Waveform) + else [ + (type(program.waveform.I).__name__, program.waveform.I.envelope()), # type: ignore[assignment] + (type(program.waveform.Q).__name__, program.waveform.Q.envelope()), # type: ignore[assignment] + ] + ) + if hasattr(program, "weights"): + temp_dict["weights"] = [ # type: ignore[assignment] + (type(program.weights.I).__name__, program.weights.I.envelope()), + (type(program.weights.Q).__name__, program.weights.Q.envelope()), + ] + + for key, value in temp_dict.copy().items(): + if "UUID" in str(value): + temp_dict[key] = None + + return temp_dict diff --git a/src/qililab/settings/runcard.py b/src/qililab/settings/runcard.py index 651af5a06..30801cd30 100644 --- a/src/qililab/settings/runcard.py +++ b/src/qililab/settings/runcard.py @@ -92,6 +92,9 @@ class FluxControlTopology: flux: str bus: str + def to_dict(self): + return asdict(self) + @nested_dataclass class GatesSettings(Settings): """Dataclass with all the settings and gates definitions needed to decompose gates into pulses.""" diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index a29e09a72..a6e424644 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -15,7 +15,6 @@ from qpysequence import Sequence from ruamel.yaml import YAML -import qililab from qililab import Arbitrary, save_platform from qililab.chip import Chip, Qubit from qililab.constants import DEFAULT_PLATFORM_NAME @@ -27,6 +26,7 @@ from qililab.platform import Bus, Buses, Platform from qililab.pulse import Drag, Pulse, PulseEvent, PulseSchedule, Rectangular from qililab.qprogram import QProgram +from qililab.qprogram.qprogram import to_readable_dict from qililab.result.qblox_results import QbloxResult from qililab.result.qprogram.quantum_machines_measurement_result import QuantumMachinesMeasurementResult from qililab.settings import Runcard @@ -386,7 +386,7 @@ def test_execute_anneal_program(self, platform: Platform, anneal_qprogram): platform.execute_anneal_program( anneal_program_dict=[{"qubit_0": {"sigma_x": 0.1, "sigma_z": 0.2}}], transpiler=transpiler, averages=2 ) - anneal_qprogram.__dict__() == mock_execute_qprogram.call_args[1]["qprogram"].__dict__() + to_readable_dict(anneal_qprogram) == to_readable_dict(mock_execute_qprogram.call_args[1]["qprogram"]) def test_execute_qprogram_with_qblox(self, platform: Platform): """Test that the execute method compiles the qprogram, calls the buses to run and return the results.""" From 67d3145f2b9bd2b1ead6899bc64d7bb35494f93a Mon Sep 17 00:00:00 2001 From: victor Date: Sun, 11 Aug 2024 23:02:54 +0200 Subject: [PATCH 14/69] fix pylint --- src/qililab/settings/runcard.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/qililab/settings/runcard.py b/src/qililab/settings/runcard.py index 30801cd30..36e47a96f 100644 --- a/src/qililab/settings/runcard.py +++ b/src/qililab/settings/runcard.py @@ -93,6 +93,7 @@ class FluxControlTopology: bus: str def to_dict(self): + """Method to convert to dictionary""" return asdict(self) @nested_dataclass From 3693bd1c70ca762cc19ce969bf38ca45cbb1bacc Mon Sep 17 00:00:00 2001 From: victor Date: Sun, 11 Aug 2024 23:17:43 +0200 Subject: [PATCH 15/69] fix docstrings, pylint --- src/qililab/platform/platform.py | 2 ++ tests/analog/test_annealing_program.py | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/qililab/platform/platform.py b/src/qililab/platform/platform.py index e9e1f4899..20c3083fa 100644 --- a/src/qililab/platform/platform.py +++ b/src/qililab/platform/platform.py @@ -608,6 +608,8 @@ def execute_anneal_program( ): """Given an anneal program execute it as a qprogram. The anneal program should contain a time ordered list of circuit elements and their corresponging ising coefficients as a dictionary. Example structure: + + .. code-block:: python [ {"qubit_0": {"sigma_x" : 0, "sigma_y" : 1, "sigma_z" : 2}, "coupler_1_0 : {...}, diff --git a/tests/analog/test_annealing_program.py b/tests/analog/test_annealing_program.py index c67f5be16..d129f25fa 100644 --- a/tests/analog/test_annealing_program.py +++ b/tests/analog/test_annealing_program.py @@ -1,6 +1,4 @@ """Test the annealing program class""" -from unittest.mock import MagicMock - import numpy as np import pytest @@ -10,7 +8,7 @@ @pytest.fixture(name="anneal_program_dictionary") -def anneal_program_dictionary(): +def get_anneal_program_dictionary(): """Dummy anneal program dictionary""" return [ { @@ -32,7 +30,7 @@ def anneal_program_dictionary(): @pytest.fixture(name="anneal_program_dictionary_with_flux") -def anneal_program_dictionary_with_flux(): +def get_anneal_program_dictionary_with_flux(): """Dummy anneal program dictionary with transpiled fluxes""" return [ { @@ -75,6 +73,8 @@ def dummy_transpiler(delta, epsilon): class TestAnnealingProgram: + """Test class for the AnnealingProgram class""" + def test_init(self, annealing_program, anneal_program_dictionary): """Test init method""" assert annealing_program.platform.to_dict() == Galadriel.runcard From b499fcf18e12cf44c95329da6631f3f46a804d0b Mon Sep 17 00:00:00 2001 From: victor Date: Sun, 11 Aug 2024 23:22:06 +0200 Subject: [PATCH 16/69] still trying to fix docs --- src/qililab/platform/platform.py | 19 ++++++++++--------- tests/platform/test_platform.py | 1 - 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/qililab/platform/platform.py b/src/qililab/platform/platform.py index 20c3083fa..f50789ded 100644 --- a/src/qililab/platform/platform.py +++ b/src/qililab/platform/platform.py @@ -610,15 +610,16 @@ def execute_anneal_program( The anneal program should contain a time ordered list of circuit elements and their corresponging ising coefficients as a dictionary. Example structure: .. code-block:: python - [ - {"qubit_0": {"sigma_x" : 0, "sigma_y" : 1, "sigma_z" : 2}, - "coupler_1_0 : {...}, - }, # time=0ns - {...}, # time=1ns - . - . - . - ] + [ + {"qubit_0": {"sigma_x" : 0, "sigma_y" : 1, "sigma_z" : 2}, + "coupler_1_0 : {...}, + }, # time=0ns + {...}, # time=1ns + . + . + . + ] + This dictionary containing ising coefficients is transpiled to fluxes using the given transpiler. Then the correspoinding waveforms are obtained and assigned to a bus from the bus to flux mapping given by the runcard. diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index a6e424644..f69cf68f7 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -5,7 +5,6 @@ import re from pathlib import Path from queue import Queue -from unittest import mock from unittest.mock import MagicMock, patch import numpy as np From 6e1438296c582088bb4c842f0c20b1c1796855d0 Mon Sep 17 00:00:00 2001 From: victor Date: Sun, 11 Aug 2024 23:26:08 +0200 Subject: [PATCH 17/69] still trying to fix the docs --- src/qililab/platform/platform.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/qililab/platform/platform.py b/src/qililab/platform/platform.py index f50789ded..bc7ecf737 100644 --- a/src/qililab/platform/platform.py +++ b/src/qililab/platform/platform.py @@ -610,6 +610,7 @@ def execute_anneal_program( The anneal program should contain a time ordered list of circuit elements and their corresponging ising coefficients as a dictionary. Example structure: .. code-block:: python + [ {"qubit_0": {"sigma_x" : 0, "sigma_y" : 1, "sigma_z" : 2}, "coupler_1_0 : {...}, @@ -619,7 +620,7 @@ def execute_anneal_program( . . ] - + This dictionary containing ising coefficients is transpiled to fluxes using the given transpiler. Then the correspoinding waveforms are obtained and assigned to a bus from the bus to flux mapping given by the runcard. From 037aa2302c13811642f2ce9cae562c43bd605761 Mon Sep 17 00:00:00 2001 From: victor Date: Sun, 11 Aug 2024 23:34:06 +0200 Subject: [PATCH 18/69] still trying to fix the docs --- tests/platform/test_platform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index f69cf68f7..1f6d48df7 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -385,7 +385,7 @@ def test_execute_anneal_program(self, platform: Platform, anneal_qprogram): platform.execute_anneal_program( anneal_program_dict=[{"qubit_0": {"sigma_x": 0.1, "sigma_z": 0.2}}], transpiler=transpiler, averages=2 ) - to_readable_dict(anneal_qprogram) == to_readable_dict(mock_execute_qprogram.call_args[1]["qprogram"]) + assert to_readable_dict(anneal_qprogram) == to_readable_dict(mock_execute_qprogram.call_args[1]["qprogram"]) def test_execute_qprogram_with_qblox(self, platform: Platform): """Test that the execute method compiles the qprogram, calls the buses to run and return the results.""" From 2648b303a70061061daf2fdf45918ed47635a830 Mon Sep 17 00:00:00 2001 From: victor Date: Sun, 11 Aug 2024 23:38:53 +0200 Subject: [PATCH 19/69] still trying to fix the docs --- tests/platform/test_platform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index 1f6d48df7..e200b06f1 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -111,7 +111,7 @@ def fixture_qblox_results(): @pytest.fixture(name="anneal_qprogram") -def anneal_qprogram(runcard): +def get_anneal_qprogram(runcard): platform = Platform(runcard=runcard) anneal_waveforms = { "phix_q0": ( From a73de8b6d594c6976d47fc7a46fdbec5ebbf4d92 Mon Sep 17 00:00:00 2001 From: victor Date: Mon, 12 Aug 2024 15:19:45 +0200 Subject: [PATCH 20/69] change qprogram to_readable_dict to __str__ method --- src/qililab/qprogram/qprogram.py | 94 +++++++++++++++++--------------- tests/platform/test_platform.py | 3 +- 2 files changed, 50 insertions(+), 47 deletions(-) diff --git a/src/qililab/qprogram/qprogram.py b/src/qililab/qprogram/qprogram.py index 32d65499a..6f3f0b6bf 100644 --- a/src/qililab/qprogram/qprogram.py +++ b/src/qililab/qprogram/qprogram.py @@ -102,6 +102,55 @@ def __init__(self) -> None: self._variables: list[Variable] = [] self._block_stack: deque[Block] = deque([self._body]) + def __str__(self) -> str: + def traverse(block: Block): + string_elements = [] + for element in block.elements: + string_elements.append(f"{type(element).__name__}:\n") + for field in fields(element): + if field.name in [ + "_uuid", + "variable", + "elements", + "waveform", + "weights", + ]: # ignore uuid, variables. elements, waveforms and weights are handled separately + continue + string_elements.append( + f"\t{field.name}: {getattr(element, field.name) if 'UUID' not in str(getattr(element, field.name)) else None}\n" + ) + if isinstance(element, Block): + # handle blocks + for string_element in traverse(element): + string_elements.append(f"\t{string_element}") + + # if not a block, it is asusmed that element is type Operation + if hasattr(element, "waveform"): + waveform_string = ( + [f"\tWaveform {type(element.waveform).__name__}:\n"] + + [f"\t\t{array_line}\n" for array_line in str(element.waveform.envelope().split("\n"))] + if isinstance(element.waveform, Waveform) + else [f"\tWaveform I {type(element.waveform.I).__name__}:\n"] + + [f"\t\t{array_line}\n" for array_line in str(element.waveform.I.envelope()).split("\n")] + + [f"\tWaveform Q {type(element.waveform.Q).__name__}):\n"] + + [f"\t\t{array_line}\n" for array_line in str(element.waveform.Q.envelope()).split("\n")] + ) + string_elements.extend(waveform_string) + + if hasattr(element, "weights"): + string_elements.append(f"\tWeights I {type(element.weights.I).__name__}:\n") + string_elements.extend( + [f"\t\t{array_element}\n" for array_element in str(element.weights.I.envelope()).split("\n")] + ) + string_elements.append(f"\tWeights Q {type(element.weights.Q).__name__}:\n") + string_elements.extend( + [f"\t\t{array_element}\n" for array_element in str(element.weights.Q.envelope()).split("\n")] + ) + + return string_elements + + return "".join(traverse(self._body)) + def _append_to_block_stack(self, block: Block): self._block_stack.append(block) @@ -924,48 +973,3 @@ def measure( ) self.qprogram._active_block.append(operation) self.qprogram._buses.add(bus) - - -def to_readable_dict(program) -> dict: - """Converts qprogram to dictionary containing the instructions - - Args: - program (QProgram): qprogram to convert - - Returns: - dict: qprogram dictionary - """ - if hasattr(program, "body"): - return to_readable_dict(program.body) - - temp_dict = {"name": type(program).__name__} - for field in fields(program): - if field.name in ["_uuid", "variable"]: - continue - temp_dict[field.name] = ( - getattr(program, field.name) - if field.name != "elements" - else [to_readable_dict(element) for element in getattr(program, field.name)] - ) - - if isinstance(program, Operation): - if hasattr(program, "waveform"): - temp_dict["waveform"] = ( - [(type(program.waveform).__name__, program.waveform.envelope())] - if isinstance(program.waveform, Waveform) - else [ - (type(program.waveform.I).__name__, program.waveform.I.envelope()), # type: ignore[assignment] - (type(program.waveform.Q).__name__, program.waveform.Q.envelope()), # type: ignore[assignment] - ] - ) - if hasattr(program, "weights"): - temp_dict["weights"] = [ # type: ignore[assignment] - (type(program.weights.I).__name__, program.weights.I.envelope()), - (type(program.weights.Q).__name__, program.weights.Q.envelope()), - ] - - for key, value in temp_dict.copy().items(): - if "UUID" in str(value): - temp_dict[key] = None - - return temp_dict diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index e200b06f1..f79243375 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -25,7 +25,6 @@ from qililab.platform import Bus, Buses, Platform from qililab.pulse import Drag, Pulse, PulseEvent, PulseSchedule, Rectangular from qililab.qprogram import QProgram -from qililab.qprogram.qprogram import to_readable_dict from qililab.result.qblox_results import QbloxResult from qililab.result.qprogram.quantum_machines_measurement_result import QuantumMachinesMeasurementResult from qililab.settings import Runcard @@ -385,7 +384,7 @@ def test_execute_anneal_program(self, platform: Platform, anneal_qprogram): platform.execute_anneal_program( anneal_program_dict=[{"qubit_0": {"sigma_x": 0.1, "sigma_z": 0.2}}], transpiler=transpiler, averages=2 ) - assert to_readable_dict(anneal_qprogram) == to_readable_dict(mock_execute_qprogram.call_args[1]["qprogram"]) + assert str(anneal_qprogram) == str(mock_execute_qprogram.call_args[1]["qprogram"]) def test_execute_qprogram_with_qblox(self, platform: Platform): """Test that the execute method compiles the qprogram, calls the buses to run and return the results.""" From 3999c71e5be5a5d6cfae4525aff47edc7446c9db Mon Sep 17 00:00:00 2001 From: victor Date: Mon, 12 Aug 2024 15:31:18 +0200 Subject: [PATCH 21/69] fix tests, fix docs --- src/qililab/analog/annealing_program.py | 21 ++++++++++++--------- src/qililab/qprogram/qprogram.py | 3 +-- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/qililab/analog/annealing_program.py b/src/qililab/analog/annealing_program.py index 6b1e8aec2..cbd354810 100644 --- a/src/qililab/analog/annealing_program.py +++ b/src/qililab/analog/annealing_program.py @@ -25,15 +25,18 @@ class AnnealingProgram: """Class for an Annealing Program. The program should have the format - [ - {"qubit_0": {"sigma_x" : 0, "sigma_y" : 1, "sigma_z" : 2}, - "coupler_1_0 : {...}, - }, # time=0ns - {...}, # time=1ns - . - . - . - ] + .. code-block:: python + + [ + {"qubit_0": {"sigma_x" : 0, "sigma_y" : 1, "sigma_z" : 2}, + "coupler_1_0 : {...}, + }, # time=0ns + {...}, # time=1ns + . + . + . + ] + """ platform: Any diff --git a/src/qililab/qprogram/qprogram.py b/src/qililab/qprogram/qprogram.py index 6f3f0b6bf..943ac0c93 100644 --- a/src/qililab/qprogram/qprogram.py +++ b/src/qililab/qprogram/qprogram.py @@ -29,7 +29,6 @@ MeasureWithCalibratedWaveform, MeasureWithCalibratedWaveformWeights, MeasureWithCalibratedWeights, - Operation, Play, PlayWithCalibratedWaveform, ResetPhase, @@ -128,7 +127,7 @@ def traverse(block: Block): if hasattr(element, "waveform"): waveform_string = ( [f"\tWaveform {type(element.waveform).__name__}:\n"] - + [f"\t\t{array_line}\n" for array_line in str(element.waveform.envelope().split("\n"))] + + [f"\t\t{array_line}\n" for array_line in str(element.waveform.envelope()).split("\n")] if isinstance(element.waveform, Waveform) else [f"\tWaveform I {type(element.waveform.I).__name__}:\n"] + [f"\t\t{array_line}\n" for array_line in str(element.waveform.I.envelope()).split("\n")] From 914f568e0a75d0fa30d16cb1a4b871231dcdfec4 Mon Sep 17 00:00:00 2001 From: victor Date: Mon, 12 Aug 2024 15:56:47 +0200 Subject: [PATCH 22/69] add suggested changes --- src/qililab/analog/annealing_program.py | 38 ++++++++++++++----------- src/qililab/platform/platform.py | 24 ++++++++-------- tests/analog/test_annealing_program.py | 4 +-- tests/platform/test_platform.py | 2 +- 4 files changed, 37 insertions(+), 31 deletions(-) diff --git a/src/qililab/analog/annealing_program.py b/src/qililab/analog/annealing_program.py index cbd354810..8b465b845 100644 --- a/src/qililab/analog/annealing_program.py +++ b/src/qililab/analog/annealing_program.py @@ -21,7 +21,6 @@ from qililab.waveforms import Arbitrary as ArbitraryWave -@dataclass class AnnealingProgram: """Class for an Annealing Program. The program should have the format @@ -37,10 +36,17 @@ class AnnealingProgram: . ] + + Args: + platform (Any): platform + annealing_program (list[dict[str, dict[str, float]]]): dictionary with the annealing program with the above structure. """ - platform: Any - anneal_program: list[dict[str, dict[str, float]]] + def __init__(self, platform: Any, annealing_program: list[dict[str, dict[str, float]]]): + """Init method""" + self._platform = platform + self._annealing_program = annealing_program + self.annealing_program = annealing_program # TODO: implement as frozenDataclass def transpile(self, transpiler: Callable): """First implementation of a transpiler, pretty basic but good as a first step. Transpiles from ising coefficients to fluxes @@ -51,13 +57,13 @@ def transpile(self, transpiler: Callable): """ # iterate over each anneal step and transpile ising to fluxes - for anneal_step in self.anneal_program: - for circuit_element in anneal_step: + for annealing_step in self._annealing_program: + for circuit_element in annealing_step: phix, phiz = transpiler( - delta=anneal_step[circuit_element]["sigma_x"], epsilon=anneal_step[circuit_element]["sigma_z"] + delta=annealing_step[circuit_element]["sigma_x"], epsilon=annealing_step[circuit_element]["sigma_z"] ) - anneal_step[circuit_element]["phix"] = phix - anneal_step[circuit_element]["phiz"] = phiz + annealing_step[circuit_element]["phix"] = phix + annealing_step[circuit_element]["phiz"] = phiz def get_waveforms(self) -> dict[str, tuple[Bus, ArbitraryWave]]: """Returns a dictionary containing (bus, waveform) for each flux control from the transpiled fluxes. `AnnealingProgram.transpile` should be run first. The waveform is an arbitrary waveform obtained from the transpiled fluxes. @@ -69,20 +75,20 @@ def get_waveforms(self) -> dict[str, tuple[Bus, ArbitraryWave]]: element_name_map = {"qubit": "q", "coupler": "c"} circuit_element_map = { (element, flux): f"{flux}_{element_name_map[element.split('_', 1)[0]]}{element.split('_', 1)[1]}" - for element in self.anneal_program[0].keys() - for flux in self.anneal_program[0][element].keys() + for element in self._annealing_program[0].keys() + for flux in self._annealing_program[0][element].keys() if "phi" in flux } # {(element, flux): flux_line} # Initialize dictionary with flux_lines pointing to (corresponding bus, waveform) - anneal_waveforms = { # type: ignore[var-annotated] - flux_line: (self.platform.get_element(flux_line), []) for flux_line in circuit_element_map.values() + annealing_waveforms = { # type: ignore[var-annotated] + flux_line: (self._platform.get_element(flux_line), []) for flux_line in circuit_element_map.values() } # unravel each point of the anneal program to get timewise arrays of waveforms - for anneal_step in self.anneal_program: + for annealing_step in self._annealing_program: for circuit_element, flux in circuit_element_map.keys(): - anneal_waveforms[circuit_element_map[circuit_element, flux]][1].append( - anneal_step[circuit_element][flux] + annealing_waveforms[circuit_element_map[circuit_element, flux]][1].append( + annealing_step[circuit_element][flux] ) - return {key: (value[0], ArbitraryWave(np.array(value[1]))) for key, value in anneal_waveforms.items()} + return {key: (value[0], ArbitraryWave(np.array(value[1]))) for key, value in annealing_waveforms.items()} diff --git a/src/qililab/platform/platform.py b/src/qililab/platform/platform.py index bc7ecf737..e99924f1f 100644 --- a/src/qililab/platform/platform.py +++ b/src/qililab/platform/platform.py @@ -604,10 +604,10 @@ def __str__(self) -> str: # TODO: determine default average def execute_anneal_program( - self, anneal_program_dict: list[dict[str, dict[str, float]]], transpiler: Callable, averages=1 + self, annealing_program_dict: list[dict[str, dict[str, float]]], transpiler: Callable, averages=1 ): - """Given an anneal program execute it as a qprogram. - The anneal program should contain a time ordered list of circuit elements and their corresponging ising coefficients as a dictionary. Example structure: + """Given an annealing program execute it as a qprogram. + The annealing program should contain a time ordered list of circuit elements and their corresponging ising coefficients as a dictionary. Example structure: .. code-block:: python @@ -625,22 +625,22 @@ def execute_anneal_program( from the bus to flux mapping given by the runcard. Args: - anneal_program_dict (list[dict[str, dict[str, float]]]): anneal program to run + annealing_program_dict (list[dict[str, dict[str, float]]]): annealing program to run transpiler (Callable): ising to flux transpiler. The transpiler should take 2 values as arguments (delta, epsilon) and return 2 values (phix, phiz) averages (int, optional): Amount of times to run and average the program over. Defaults to 1. """ - anneal_program = AnnealingProgram(self, anneal_program_dict) - anneal_program.transpile(transpiler) - anneal_waveforms = anneal_program.get_waveforms() + annealing_program = AnnealingProgram(self, annealing_program_dict) + annealing_program.transpile(transpiler) + annealing_waveforms = annealing_program.get_waveforms() - qp_anneal = QProgram() - with qp_anneal.average(averages): - for bus, waveform in anneal_waveforms.values(): - qp_anneal.play(bus=bus.alias, waveform=waveform) + qp_annealing = QProgram() + with qp_annealing.average(averages): + for bus, waveform in annealing_waveforms.values(): + qp_annealing.play(bus=bus.alias, waveform=waveform) # TODO: define readout - self.execute_qprogram(qprogram=qp_anneal) + self.execute_qprogram(qprogram=qp_annealing) def execute_qprogram( # pylint: disable=too-many-locals self, diff --git a/tests/analog/test_annealing_program.py b/tests/analog/test_annealing_program.py index d129f25fa..a8ef77783 100644 --- a/tests/analog/test_annealing_program.py +++ b/tests/analog/test_annealing_program.py @@ -50,7 +50,7 @@ def get_anneal_program_dictionary_with_flux(): def dummy_annealing_program(anneal_program_dictionary): """Build dummy annealing program""" return AnnealingProgram( - platform=build_platform(runcard=Galadriel.runcard), anneal_program=anneal_program_dictionary + platform=build_platform(runcard=Galadriel.runcard), annealing_program=anneal_program_dictionary ) @@ -58,7 +58,7 @@ def dummy_annealing_program(anneal_program_dictionary): def dummy_annealing_program_with_flux(anneal_program_dictionary_with_flux): """Build dummy annealing program with fluxes already transpiled""" return AnnealingProgram( - platform=build_platform(runcard=Galadriel.runcard), anneal_program=anneal_program_dictionary_with_flux + platform=build_platform(runcard=Galadriel.runcard), annealing_program=anneal_program_dictionary_with_flux ) diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index f79243375..65ea9ed70 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -382,7 +382,7 @@ def test_execute_anneal_program(self, platform: Platform, anneal_qprogram): transpiler.return_value = (1, 2) # with patch(qililab.analog.annealing_program, "AnnealingProgram") as dummy_anneal_program: platform.execute_anneal_program( - anneal_program_dict=[{"qubit_0": {"sigma_x": 0.1, "sigma_z": 0.2}}], transpiler=transpiler, averages=2 + annealing_program_dict=[{"qubit_0": {"sigma_x": 0.1, "sigma_z": 0.2}}], transpiler=transpiler, averages=2 ) assert str(anneal_qprogram) == str(mock_execute_qprogram.call_args[1]["qprogram"]) From 930b56eb077684699263e52d64490cf5cdac879c Mon Sep 17 00:00:00 2001 From: victor Date: Mon, 12 Aug 2024 16:11:53 +0200 Subject: [PATCH 23/69] add unittest for qprogram str method --- tests/qprogram/test_qprogram.py | 38 +++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/qprogram/test_qprogram.py b/tests/qprogram/test_qprogram.py index 350172efe..4ccfbe630 100644 --- a/tests/qprogram/test_qprogram.py +++ b/tests/qprogram/test_qprogram.py @@ -30,6 +30,41 @@ from qililab.utils.serialization import deserialize, deserialize_from, serialize, serialize_to +@pytest.fixture(name="sample_qprogram_string") +def get_sample_qprogram_string(): + """Sample qprogram and its corresponding string to tests the __str__ method""" + r_amp = 0.5 + r_duration = 40 + d_duration = 40 + + r_wf_I = Square(amplitude=r_amp, duration=r_duration) + r_wf_Q = Square(amplitude=0.0, duration=r_duration) + d_wf = IQPair.DRAG(amplitude=1.0, duration=d_duration, num_sigmas=4, drag_coefficient=0.1) + + weights_shape = Square(amplitude=1, duration=r_duration) + + qp = QProgram() + amp = qp.variable(domain=Domain.Voltage) + freq = qp.variable(domain=Domain.Frequency) + + with qp.average(100): + with qp.for_loop(variable=amp, start=0.2, stop=1, step=0.1): + qp.set_gain(bus="dummy_bus_0", gain=amp) + with qp.for_loop(variable=freq, start=0, stop=20, step=5): + qp.set_frequency(bus="dummy_bus_1", frequency=freq) + # DRAG PULSE + qp.play(bus="dummy_bus_0", waveform=d_wf) + qp.sync() + # READOUT PULSE + qp.measure( + bus="readout", waveform=IQPair(I=r_wf_I, Q=r_wf_Q), weights=IQPair(I=weights_shape, Q=weights_shape) + ) + qp.wait(bus="readout", duration=200) + + qp_string = """Average:\n\tshots: 100\n\tForLoop:\n\t\tstart: 0.2\n\t\tstop: 1\n\t\tstep: 0.1\n\t\tSetGain:\n\t\t\tbus: dummy_bus_0\n\t\t\tgain: None\n\t\tForLoop:\n\t\t\tstart: 0\n\t\t\tstop: 20\n\t\t\tstep: 5\n\t\t\tSetFrequency:\n\t\t\t\tbus: dummy_bus_1\n\t\t\t\tfrequency: None\n\t\t\tPlay:\n\t\t\t\tbus: dummy_bus_0\n\t\t\t\twait_time: None\n\t\t\t\tWaveform I Gaussian:\n\t\t\t\t\t[0. 0.03369997 0.07235569 0.11612685 0.1650374 0.21894866\n\t\t\t\t\t 0.27753626 0.34027302 0.40641993 0.47502707 0.54494577 0.61485281\n\t\t\t\t\t 0.68328653 0.74869396 0.80948709 0.86410559 0.9110827 0.94911031\n\t\t\t\t\t 0.97709942 0.99423184 1. 0.99423184 0.97709942 0.94911031\n\t\t\t\t\t 0.9110827 0.86410559 0.80948709 0.74869396 0.68328653 0.61485281\n\t\t\t\t\t 0.54494577 0.47502707 0.40641993 0.34027302 0.27753626 0.21894866\n\t\t\t\t\t 0.1650374 0.11612685 0.07235569 0.03369997]\n\t\t\t\tWaveform Q DragCorrection):\n\t\t\t\t\t[ 0. 0.0006403 0.0013024 0.00197416 0.0026406 0.00328423\n\t\t\t\t\t 0.00388551 0.00442355 0.00487704 0.0052253 0.00544946 0.00553368\n\t\t\t\t\t 0.00546629 0.00524086 0.00485692 0.00432053 0.00364433 0.00284733\n\t\t\t\t\t 0.0019542 0.00099423 -0. -0.00099423 -0.0019542 -0.00284733\n\t\t\t\t\t -0.00364433 -0.00432053 -0.00485692 -0.00524086 -0.00546629 -0.00553368\n\t\t\t\t\t -0.00544946 -0.0052253 -0.00487704 -0.00442355 -0.00388551 -0.00328423\n\t\t\t\t\t -0.0026406 -0.00197416 -0.0013024 -0.0006403 ]\n\t\t\tSync:\n\t\t\t\tbuses: None\n\t\t\tMeasure:\n\t\t\t\tbus: readout\n\t\t\t\tsave_adc: False\n\t\t\t\trotation: None\n\t\t\t\tdemodulation: True\n\t\t\t\tWaveform I Square:\n\t\t\t\t\t[0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5\n\t\t\t\t\t 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5\n\t\t\t\t\t 0.5 0.5 0.5 0.5]\n\t\t\t\tWaveform Q Square):\n\t\t\t\t\t[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.\n\t\t\t\t\t 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]\n\t\t\t\tWeights I Square:\n\t\t\t\t\t[1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.\n\t\t\t\t\t 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]\n\t\t\t\tWeights Q Square:\n\t\t\t\t\t[1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.\n\t\t\t\t\t 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]\n\t\t\tWait:\n\t\t\t\tbus: readout\n\t\t\t\tduration: 200\n""" + return (qp, qp_string) + + # pylint: disable=maybe-no-member, protected-access class TestQProgram: """Unit tests checking the QProgram attributes and methods""" @@ -44,6 +79,9 @@ def test_init(self): assert isinstance(qp._variables, list) assert len(qp._variables) == 0 + def test_str_method(self, sample_qprogram_string): + assert str(sample_qprogram_string[0]) == sample_qprogram_string[1] + def test_active_block_property(self): """Test _active_block property""" qp = QProgram() From 3a5c9f7e4b7577b342ef66e044dea88420eeda93 Mon Sep 17 00:00:00 2001 From: victor Date: Mon, 12 Aug 2024 16:21:04 +0200 Subject: [PATCH 24/69] fix tests --- tests/analog/test_annealing_program.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/analog/test_annealing_program.py b/tests/analog/test_annealing_program.py index a8ef77783..6aeb0daa0 100644 --- a/tests/analog/test_annealing_program.py +++ b/tests/analog/test_annealing_program.py @@ -77,8 +77,9 @@ class TestAnnealingProgram: def test_init(self, annealing_program, anneal_program_dictionary): """Test init method""" - assert annealing_program.platform.to_dict() == Galadriel.runcard - assert annealing_program.anneal_program == anneal_program_dictionary + assert annealing_program._platform.to_dict() == Galadriel.runcard + assert annealing_program._annealing_program == anneal_program_dictionary + assert annealing_program.annealing_program == anneal_program_dictionary def test_transpile(self, annealing_program, anneal_program_dictionary): """Test transpile method""" From f2978270cb1af0345108e66af5e93150100c14b8 Mon Sep 17 00:00:00 2001 From: victor Date: Mon, 12 Aug 2024 16:26:20 +0200 Subject: [PATCH 25/69] remove unused import --- src/qililab/analog/annealing_program.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/qililab/analog/annealing_program.py b/src/qililab/analog/annealing_program.py index 8b465b845..0fd9eea7b 100644 --- a/src/qililab/analog/annealing_program.py +++ b/src/qililab/analog/annealing_program.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from dataclasses import dataclass from typing import Any, Callable import numpy as np From 304cc6c8b7dd017c2ebca1cd4e67158efa945d5d Mon Sep 17 00:00:00 2001 From: victor Date: Mon, 12 Aug 2024 16:34:45 +0200 Subject: [PATCH 26/69] fix coverage --- src/qililab/platform/platform.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/qililab/platform/platform.py b/src/qililab/platform/platform.py index e99924f1f..f82afabf0 100644 --- a/src/qililab/platform/platform.py +++ b/src/qililab/platform/platform.py @@ -394,8 +394,6 @@ def get_element(self, alias: str): if regex_match is not None: element_type = regex_match.lastgroup element_shorthands = {"qubit": "q", "coupler": "c"} - if element_type not in element_shorthands: - raise ValueError("Invalid element selected in runcard for flux {flux} with alias {alias}") flux = regex_match["flux"] # TODO: support commuting the name of the coupler eg. c1_0 = c0_1 return self._get_bus_by_alias( From 6bc760d9310a08205277add3b91544c56ad52e30 Mon Sep 17 00:00:00 2001 From: victor Date: Mon, 12 Aug 2024 16:45:39 +0200 Subject: [PATCH 27/69] update changelog --- docs/releases/changelog-dev.md | 74 ++++++++++++++++++-------------- src/qililab/platform/platform.py | 2 +- 2 files changed, 42 insertions(+), 34 deletions(-) diff --git a/docs/releases/changelog-dev.md b/docs/releases/changelog-dev.md index 3c2f08bd5..f3f65b5af 100644 --- a/docs/releases/changelog-dev.md +++ b/docs/releases/changelog-dev.md @@ -2,6 +2,9 @@ ### New features since last release +- Add `__str__` method to qprogram. The string is a readable qprogram. + [#767](https://github.com/qilimanjaro-tech/qililab/pull/767) + - Add workflow for the execution of annealing programs. Example: @@ -31,18 +34,22 @@ ```python import qililab as ql + platform = ql.build_platform("examples/runcards/galadriel.yml") - anneal_program_dict = [...] # same as in the above example + anneal_program_dict = [...] # same as in the above example # intialize annealing program class - anneal_program = ql.AnnealingProgram(platform=platform, anneal_program=anneal_program_dict) + anneal_program = ql.AnnealingProgram( + platform=platform, anneal_program=anneal_program_dict + ) # transpile ising to flux, now flux values can be accessed same as ising coeff values # eg. for phix qubit 0 at t=1ns anneal_program.anneal_program[1]["qubit_0"]["phix"] - anneal_program.transpile(lambda delta,epsilon: (delta,epsilon)) + anneal_program.transpile(lambda delta, epsilon: (delta, epsilon)) # get a dictionary {control_flux: (bus, waveform) from the transpiled fluxes anneal_waveforms = anneal_program.get_waveforms() # from here on we can create a qprogram to execute the annealing schedule - [#767](https://github.com/qilimanjaro-tech/qililab/pull/767) + ``` + [#767](https://github.com/qilimanjaro-tech/qililab/pull/767) - Added `CrosstalkMatrix` class to represent and manipulate a crosstalk matrix, where each index corresponds to a bus. The class includes methods for initializing the matrix, getting and setting crosstalk values, and generating string representations of the matrix. @@ -127,41 +134,42 @@ - Added `filter` argument inside the qua config file compilation from runcards with qm clusters. This is an optional element for distorsion filters that includes feedforward and feedback, two distorion lists for distorsion compensation and fields in qua config filter. These filters are calibrated and then introduced as compensation for the distorsions of the pulses from external sources such as Bias T. The runcard now might include the new filters (optional): Example: - ``` - instruments: - - name: quantum_machines_cluster - alias: QMM - firmware: 0.7.0 - ... - controllers: - - name: con1 - analog_outputs: - - port: 1 - offset: 0.0 - filter: - feedforward: [0.1,0.1,0.1] - feedback: [0.1,0.1,0.1] - ... - ``` + + ``` + instruments: + - name: quantum_machines_cluster + alias: QMM + firmware: 0.7.0 + ... + controllers: + - name: con1 + analog_outputs: + - port: 1 + offset: 0.0 + filter: + feedforward: [0.1,0.1,0.1] + feedback: [0.1,0.1,0.1] + ... + ``` [#768](https://github.com/qilimanjaro-tech/qililab/pull/768) -- Added loopbacks in the octave config file for qua following the documentation at https://docs.quantum-machines.co/1.2.0/qm-qua-sdk/docs/Guides/octave/?h=octaves#setting-the-octaves-clock. By default only port 1 of the octave is linked with a local demodulator, to work with the rest of the ports at the back ports must be connected based on the Octave Block Diagram [https://docs.quantum-machines.co/1.2.0/qm-qua-sdk/docs/Hardware/octave/#octave-block-diagram]. Where `Synth` is one of the possible 3 synths and `Dmd` is one of the 2 demodulators. +- Added loopbacks in the octave config file for qua following the documentation at https://docs.quantum-machines.co/1.2.0/qm-qua-sdk/docs/Guides/octave/?h=octaves#setting-the-octaves-clock. By default only port 1 of the octave is linked with a local demodulator, to work with the rest of the ports at the back ports must be connected based on the Octave Block Diagram \[https://docs.quantum-machines.co/1.2.0/qm-qua-sdk/docs/Hardware/octave/#octave-block-diagram\]. Where `Synth` is one of the possible 3 synths and `Dmd` is one of the 2 demodulators. Example: - ``` - - name: quantum_machines_cluster - alias: QMM - ... - octaves: - - name: octave1 - port: 11252 - ... - loopbacks: - Synth: Synth2 # Synth1, Synth2, Synth3 - Dmd: Dmd2LO # Dmd1LO, Dmd2LO - ``` + ``` + - name: quantum_machines_cluster + alias: QMM + ... + octaves: + - name: octave1 + port: 11252 + ... + loopbacks: + Synth: Synth2 # Synth1, Synth2, Synth3 + Dmd: Dmd2LO # Dmd1LO, Dmd2LO + ``` [#770](https://github.com/qilimanjaro-tech/qililab/pull/770) diff --git a/src/qililab/platform/platform.py b/src/qililab/platform/platform.py index f82afabf0..a1d015fc6 100644 --- a/src/qililab/platform/platform.py +++ b/src/qililab/platform/platform.py @@ -401,7 +401,7 @@ def get_element(self, alias: str): ( element.bus for element in self.flux_to_bus_topology # type: ignore[union-attr] - if element.flux == f"{flux}_{element_shorthands[element_type]}{regex_match[element_type]}" + if element.flux == f"{flux}_{element_shorthands[element_type]}{regex_match[element_type]}" # type: ignore[index] ), None, ) From 5bea2a39f4d041a8edd0a8b09887d5ea30bdd652 Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Mon, 19 Aug 2024 12:11:54 +0200 Subject: [PATCH 28/69] implementation proposal --- src/qililab/platform/platform.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/qililab/platform/platform.py b/src/qililab/platform/platform.py index a1d015fc6..c360c5834 100644 --- a/src/qililab/platform/platform.py +++ b/src/qililab/platform/platform.py @@ -51,6 +51,7 @@ from qililab.system_control import ReadoutSystemControl from qililab.typings.enums import InstrumentName, Line, Parameter from qililab.utils import hash_qpy_sequence +from qililab.waveforms import IQPair, Square from .components import Bus, Buses @@ -600,9 +601,11 @@ def __str__(self) -> str: """ return str(YAML().dump(self.to_dict(), io.BytesIO())) - # TODO: determine default average def execute_anneal_program( - self, annealing_program_dict: list[dict[str, dict[str, float]]], transpiler: Callable, averages=1 + self, annealing_program_dict: list[dict[str, dict[str, float]]], + readout_bus: str, measurement_name: str, weights: str, + transpiler: Callable, averages=1, + calibration: Calibration | None = None ): """Given an annealing program execute it as a qprogram. The annealing program should contain a time ordered list of circuit elements and their corresponging ising coefficients as a dictionary. Example structure: @@ -636,7 +639,17 @@ def execute_anneal_program( for bus, waveform in annealing_waveforms.values(): qp_annealing.play(bus=bus.alias, waveform=waveform) - # TODO: define readout + if calibration and calibration.has_waveform(bus=readout_bus, name=measurement_name): + if calibration.has_weights(bus=readout_bus, name=weights): + qp_annealing.measure(bus=readout_bus, waveform=measurement_name, + weights=weights) + else: + r_duration = calibration.get_waveform(bus=readout_bus, name=measurement_name).get_duration() + weights_shape = Square(amplitude=1, duration=r_duration) + qp_annealing.measure(bus=readout_bus, waveform=measurement_name, + weights=IQPair(I=weights_shape, Q=weights_shape)) + else: + raise ValueError("A calibration must be provided to run an annealing schedule.") self.execute_qprogram(qprogram=qp_annealing) From 654424283ab8d7da58b05ea1ef93e837292a0463 Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Mon, 19 Aug 2024 12:15:39 +0200 Subject: [PATCH 29/69] code quality --- src/qililab/platform/platform.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/qililab/platform/platform.py b/src/qililab/platform/platform.py index c360c5834..5a555c81b 100644 --- a/src/qililab/platform/platform.py +++ b/src/qililab/platform/platform.py @@ -602,10 +602,14 @@ def __str__(self) -> str: return str(YAML().dump(self.to_dict(), io.BytesIO())) def execute_anneal_program( - self, annealing_program_dict: list[dict[str, dict[str, float]]], - readout_bus: str, measurement_name: str, weights: str, - transpiler: Callable, averages=1, - calibration: Calibration | None = None + self, + annealing_program_dict: list[dict[str, dict[str, float]]], + readout_bus: str, + measurement_name: str, + weights: str, + transpiler: Callable, + averages=1, + calibration: Calibration | None = None, ): """Given an annealing program execute it as a qprogram. The annealing program should contain a time ordered list of circuit elements and their corresponging ising coefficients as a dictionary. Example structure: @@ -641,13 +645,13 @@ def execute_anneal_program( if calibration and calibration.has_waveform(bus=readout_bus, name=measurement_name): if calibration.has_weights(bus=readout_bus, name=weights): - qp_annealing.measure(bus=readout_bus, waveform=measurement_name, - weights=weights) + qp_annealing.measure(bus=readout_bus, waveform=measurement_name, weights=weights) else: r_duration = calibration.get_waveform(bus=readout_bus, name=measurement_name).get_duration() weights_shape = Square(amplitude=1, duration=r_duration) - qp_annealing.measure(bus=readout_bus, waveform=measurement_name, - weights=IQPair(I=weights_shape, Q=weights_shape)) + qp_annealing.measure( + bus=readout_bus, waveform=measurement_name, weights=IQPair(I=weights_shape, Q=weights_shape) + ) else: raise ValueError("A calibration must be provided to run an annealing schedule.") From 7f0b70dbd4f5fe26502669b71dddeb4c62299e83 Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Mon, 19 Aug 2024 12:36:07 +0200 Subject: [PATCH 30/69] testing annealing --- tests/platform/test_platform.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index 65ea9ed70..096c77823 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -24,7 +24,7 @@ from qililab.instruments.quantum_machines import QuantumMachinesCluster from qililab.platform import Bus, Buses, Platform from qililab.pulse import Drag, Pulse, PulseEvent, PulseSchedule, Rectangular -from qililab.qprogram import QProgram +from qililab.qprogram import QProgram, Calibration from qililab.result.qblox_results import QbloxResult from qililab.result.qprogram.quantum_machines_measurement_result import QuantumMachinesMeasurementResult from qililab.settings import Runcard @@ -108,9 +108,19 @@ def fixture_qblox_results(): }, ] +@pytest.fixture(name="calibration") +def get_calibration(): + readout = Square(1.0, 2000) + weights = IQPair(Square(1.0, 2000), Square(1.0, 2000)) + + calibration = Calibration() + calibration.add_waveform(bus="readout_bus", name="readout", waveform=readout) + calibration.add_weights(bus="readout_bus", name="optimal_weights", weights=weights) + + return calibration @pytest.fixture(name="anneal_qprogram") -def get_anneal_qprogram(runcard): +def get_anneal_qprogram(runcard, calibration): platform = Platform(runcard=runcard) anneal_waveforms = { "phix_q0": ( @@ -131,6 +141,7 @@ def get_anneal_qprogram(runcard): with qp_anneal.average(averages): for bus, waveform in anneal_waveforms.values(): qp_anneal.play(bus=bus.alias, waveform=waveform) + qp_anneal.measure(bus="readout_bus", name="readout", weights="optimal_weights") return qp_anneal @@ -375,14 +386,16 @@ def _compile_and_assert(self, platform: Platform, program: Circuit | PulseSchedu assert all(isinstance(sequence, Sequence) for sequence in sequences_list) assert sequences_list[0]._program.duration == 200_000 * 1000 + 4 + 4 + 4 - def test_execute_anneal_program(self, platform: Platform, anneal_qprogram): + def test_execute_anneal_program(self, platform: Platform, anneal_qprogram, calibration): mock_execute_qprogram = MagicMock() platform.execute_qprogram = mock_execute_qprogram # type: ignore[method-assign] transpiler = MagicMock() transpiler.return_value = (1, 2) # with patch(qililab.analog.annealing_program, "AnnealingProgram") as dummy_anneal_program: platform.execute_anneal_program( - annealing_program_dict=[{"qubit_0": {"sigma_x": 0.1, "sigma_z": 0.2}}], transpiler=transpiler, averages=2 + annealing_program_dict=[{"qubit_0": {"sigma_x": 0.1, "sigma_z": 0.2}}], transpiler=transpiler, averages=2, + readout_bus="readout_bus", measurement_name="readout", weights="optimal_weights", + calibration=calibration ) assert str(anneal_qprogram) == str(mock_execute_qprogram.call_args[1]["qprogram"]) From 17c754025e210ea206014cfba1847710d123fca2 Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Mon, 19 Aug 2024 12:44:22 +0200 Subject: [PATCH 31/69] weights are optional --- src/qililab/platform/platform.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qililab/platform/platform.py b/src/qililab/platform/platform.py index 5a555c81b..4633aed1d 100644 --- a/src/qililab/platform/platform.py +++ b/src/qililab/platform/platform.py @@ -606,10 +606,10 @@ def execute_anneal_program( annealing_program_dict: list[dict[str, dict[str, float]]], readout_bus: str, measurement_name: str, - weights: str, transpiler: Callable, averages=1, calibration: Calibration | None = None, + weights: str | None = None, ): """Given an annealing program execute it as a qprogram. The annealing program should contain a time ordered list of circuit elements and their corresponging ising coefficients as a dictionary. Example structure: @@ -644,7 +644,7 @@ def execute_anneal_program( qp_annealing.play(bus=bus.alias, waveform=waveform) if calibration and calibration.has_waveform(bus=readout_bus, name=measurement_name): - if calibration.has_weights(bus=readout_bus, name=weights): + if weights and calibration.has_weights(bus=readout_bus, name=weights): qp_annealing.measure(bus=readout_bus, waveform=measurement_name, weights=weights) else: r_duration = calibration.get_waveform(bus=readout_bus, name=measurement_name).get_duration() From d460846ca999795742e2838e27bb0fcd56aa0810 Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Mon, 19 Aug 2024 12:45:57 +0200 Subject: [PATCH 32/69] raising error --- src/qililab/platform/platform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qililab/platform/platform.py b/src/qililab/platform/platform.py index 4633aed1d..8f6b04f2f 100644 --- a/src/qililab/platform/platform.py +++ b/src/qililab/platform/platform.py @@ -653,7 +653,7 @@ def execute_anneal_program( bus=readout_bus, waveform=measurement_name, weights=IQPair(I=weights_shape, Q=weights_shape) ) else: - raise ValueError("A calibration must be provided to run an annealing schedule.") + raise ValueError("A calibration instance and calibrated measurement must be provided to run an annealing schedule.") self.execute_qprogram(qprogram=qp_annealing) From 682e2c06fb4eb5c1030b76bf83d10be412844b56 Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Mon, 19 Aug 2024 13:03:03 +0200 Subject: [PATCH 33/69] raises error test --- tests/platform/test_platform.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index 096c77823..3c0dc6d75 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -398,6 +398,25 @@ def test_execute_anneal_program(self, platform: Platform, anneal_qprogram, calib calibration=calibration ) assert str(anneal_qprogram) == str(mock_execute_qprogram.call_args[1]["qprogram"]) + + platform.execute_anneal_program( + annealing_program_dict=[{"qubit_0": {"sigma_x": 0.1, "sigma_z": 0.2}}], transpiler=transpiler, averages=2, + readout_bus="readout_bus", measurement_name="readout", + calibration=calibration + ) + assert str(anneal_qprogram) == str(mock_execute_qprogram.call_args[1]["qprogram"]) + + def test_execute_anneal_program_no_calibration_raises_error(self, platform: Platform, anneal_qprogram, calibration): + mock_execute_qprogram = MagicMock() + platform.execute_qprogram = mock_execute_qprogram # type: ignore[method-assign] + transpiler = MagicMock() + transpiler.return_value = (1, 2) + error_string = "A calibration instance and calibrated measurement must be provided to run an annealing schedule." + with pytest.raises(ValueError, match=error_string): + platform.execute_anneal_program( + annealing_program_dict=[{"qubit_0": {"sigma_x": 0.1, "sigma_z": 0.2}}], transpiler=transpiler, averages=2, + readout_bus="readout_bus", measurement_name="readout", + ) def test_execute_qprogram_with_qblox(self, platform: Platform): """Test that the execute method compiles the qprogram, calls the buses to run and return the results.""" From ef2ceb25265816b363c1c909ee78b8c7fc11d0c6 Mon Sep 17 00:00:00 2001 From: jordivallsq <151619025+jordivallsq@users.noreply.github.com> Date: Fri, 16 Aug 2024 14:04:37 +0200 Subject: [PATCH 34/69] QHC 621 Modify set_intermediate_frequency for the jobAPI (#764) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * update to support opx1000 * fix import error * fix typo * add default type * fix typo * change octave connectivity * Implemented necessary changes for QUA/job API (#756) * Implemented necessary changes for QUA/job API * Update tests/instruments/quantum_machines/test_quantum_machines_cluster.py * FIXED AUTOMATIC FORMAT * add if_outputs * add if_outputs * opx1000 * corrected deprecated functions * fix typos and tests * fix code quality * Fixing Documentation warnings * allowing more than a QM existing at same time * Changes on the IF set * updated job type * ignored changes * pylint disabled * fix * formatted tests * Fixed isort issues * fix pylint * fix test * fix * added missing coverage * fixed change * Added octave calibrate_element after if / lo set * fixed tests * removed incorrect assertions * fixed black formatter * code quality * code quality * ignoring types * black * Ignoring job type * Make ignore typing only for `[union-attr]` * Rewritten job order to execute set_frequency * changed from job to self.job for tests and the future * Forgot a space :) * ADDED PYLINT EXCEPTION * Fixed Mypy * a¡changed type requirements * Added compatibility with opx+ and opx1000 * black * reset self._intermediate_frequency * fix test * fixed moretests * tests * created function to get the controller * black * added changelog * coverage * fixed tests * fix * test reaise error * pylint * Update src/qililab/instruments/quantum_machines/quantum_machines_cluster.py * Apply suggestions from code review Co-authored-by: Vyron Vasileiadis * implemented suggested changes * black * fix tests * removed unexistent test --------- Co-authored-by: Vyron Vasileiads Co-authored-by: Guillermo Abad López <109400222+GuillermoAbadLopez@users.noreply.github.com> Co-authored-by: GitHub Actions bot Co-authored-by: jjmartinezQT <133863373+jjmartinezQT@users.noreply.github.com> --- .pylintrc | 3 +- docs/releases/changelog-dev.md | 3 + .../quantum_machines_cluster.py | 58 +++++++++++-- .../test_quantum_machines_cluster.py | 83 +++++++++++++++++-- 4 files changed, 128 insertions(+), 19 deletions(-) diff --git a/.pylintrc b/.pylintrc index c12d5a05c..532af36f5 100644 --- a/.pylintrc +++ b/.pylintrc @@ -21,4 +21,5 @@ disable= missing-module-docstring, too-many-arguments, too-many-branches, - too-many-return-statements \ No newline at end of file + too-many-return-statements, + too-many-instance-attributes \ No newline at end of file diff --git a/docs/releases/changelog-dev.md b/docs/releases/changelog-dev.md index 45b157806..232914301 100644 --- a/docs/releases/changelog-dev.md +++ b/docs/releases/changelog-dev.md @@ -208,3 +208,6 @@ - get_parameter for QM did not work due to the lack of the variable `bus_alias in self.system_control.get_parameter`. The variable has been added to the function and now get parameter does not return a crash. [#751](https://github.com/qilimanjaro-tech/qililab/pull/751) + +- set_parameter for intermediate frequency in quantum machines has been adapted for both OPX+ and OPX1000 following the new requirements for OPX1000 with qm-qua job.set_intermediate_frequency. + [#764](https://github.com/qilimanjaro-tech/qililab/pull/764) \ No newline at end of file diff --git a/src/qililab/instruments/quantum_machines/quantum_machines_cluster.py b/src/qililab/instruments/quantum_machines/quantum_machines_cluster.py index 749d25cad..9f076f941 100644 --- a/src/qililab/instruments/quantum_machines/quantum_machines_cluster.py +++ b/src/qililab/instruments/quantum_machines/quantum_machines_cluster.py @@ -18,7 +18,8 @@ from typing import Any, cast import numpy as np -from qm import DictQuaConfig, QuantumMachine, QuantumMachinesManager, SimulationConfig +from qm import DictQuaConfig, QmJob, QuantumMachine, QuantumMachinesManager, SimulationConfig +from qm.api.v2.job_api import JobApi from qm.jobs.running_qm_job import RunningQmJob from qm.octave import QmOctaveConfig from qm.program import Program @@ -401,6 +402,7 @@ def _get_elements_and_mixers_config(self) -> tuple: _config: DictQuaConfig _octave_config: QmOctaveConfig | None = None _is_connected_to_qm: bool = False + _pending_set_intermediate_frequency: dict[str, float] = {} _config_created: bool = False _compiled_program_cache: dict[str, str] = {} @@ -475,9 +477,34 @@ def run_octave_calibration(self): for element in elements: self._qm.calibrate_element(element) - def set_parameter_of_bus( # pylint: disable=too-many-locals + def get_controller_type_from_bus(self, bus: str) -> str | None: + """Gets the OPX controller name of the bus used + + Args: + bus (str): Alias of the bus + + Raises: + AttributeError: Raised when given bus does not exist + + Returns: + str | None: Alias of the controller, either opx1 or opx1000. + """ + if "RF_inputs" in self._config["elements"][bus]: + octave = self._config["elements"][bus]["RF_inputs"]["port"][0] + controller_name = self._config["octaves"][octave]["connectivity"] + elif "mixInputs" in self._config["elements"][bus]: + controller_name = self._config["elements"][bus]["mixInputs"]["I"][0] + elif "singleInput" in self._config["elements"][bus]: + controller_name = self._config["elements"][bus]["singleInput"]["port"][0] + + for controller in self.settings.controllers: + if controller["name"] is controller_name: + return controller["type"] if "type" in controller else "opx1" + raise AttributeError(f"Controller with bus {bus} does not exist") + + def set_parameter_of_bus( # pylint: disable=too-many-locals, too-many-statements # noqa: C901 self, bus: str, parameter: Parameter, value: float | str | bool - ) -> None: # noqa: C901 + ) -> None: """Sets the parameter of the instrument into the cache (runtime dataclasses). And if connection to instruments is established, then to the instruments as well. @@ -519,6 +546,7 @@ def set_parameter_of_bus( # pylint: disable=too-many-locals self._config["octaves"][octave_name]["RF_outputs"][out_port]["LO_frequency"] = lo_frequency if self._is_connected_to_qm: self._qm.octave.set_lo_frequency(element=bus, lo_frequency=lo_frequency) + self._qm.calibrate_element(bus) if in_port is not None: settings_octave_rf_input = next( rf_input for rf_input in settings_octave["rf_inputs"] if rf_input["port"] == in_port @@ -545,7 +573,11 @@ def set_parameter_of_bus( # pylint: disable=too-many-locals if f"mixer_{bus}" in self._config["mixers"]: self._config["mixers"][f"mixer_{bus}"][0]["intermediate_frequency"] = intermediate_frequency if self._is_connected_to_qm: - self._qm.set_intermediate_frequency(element=bus, freq=intermediate_frequency) + controller_type = self.get_controller_type_from_bus(bus) + if controller_type == "opx1": + self._qm.set_intermediate_frequency(element=bus, freq=intermediate_frequency) + if controller_type == "opx1000": + self._pending_set_intermediate_frequency[bus] = intermediate_frequency return if parameter == Parameter.THRESHOLD_ROTATION: threshold_rotation = float(value) @@ -633,7 +665,7 @@ def compile(self, program: Program) -> str: self._compiled_program_cache[qua_program_hash] = self._qm.compile(program=program) return self._compiled_program_cache[qua_program_hash] - def run_compiled_program(self, compiled_program_id: str) -> RunningQmJob: + def run_compiled_program(self, compiled_program_id: str) -> QmJob | JobApi: """Executes a previously compiled QUA program identified by its unique compiled program ID. This method submits the compiled program to the Quantum Machines (QM) execution queue and waits for @@ -646,10 +678,18 @@ def run_compiled_program(self, compiled_program_id: str) -> RunningQmJob: Returns: RunningQmJob: An object representing the running job. This object provides methods and properties to check the status of the job, retrieve results upon completion, and manage or investigate the job's execution. """ - # CHANGES: qm.queue.add_compiled() -> qm.add_compiled() + # TODO: qm.queue.add_compiled() -> qm.add_compiled() pending_job = self._qm.queue.add_compiled(compiled_program_id) - # CHANGES: job.wait_for_execution() is deprecated and will be removed in the future. Please use job.wait_until("Running") instead. - return pending_job.wait_for_execution() # type: ignore[return-value] + + # TODO: job.wait_for_execution() is deprecated and will be removed in the future. Please use job.wait_until("Running") instead. + job = pending_job.wait_for_execution() # type: ignore[return-value] + if self._pending_set_intermediate_frequency: + for bus, intermediate_frequency in self._pending_set_intermediate_frequency.items(): + job.set_intermediate_frequency(element=bus, freq=intermediate_frequency) # type: ignore[union-attr] + self._qm.calibrate_element(bus) + self._pending_set_intermediate_frequency = {} + + return job def run(self, program: Program) -> RunningQmJob: """Runs the QUA Program. @@ -666,7 +706,7 @@ def run(self, program: Program) -> RunningQmJob: return self._qm.execute(program) - def get_acquisitions(self, job: RunningQmJob) -> dict[str, np.ndarray]: + def get_acquisitions(self, job: QmJob | JobApi) -> dict[str, np.ndarray]: """Fetches the results from the execution of a QUA Program. Once the results have been fetched, they are returned wrapped in a QuantumMachinesResult instance. diff --git a/tests/instruments/quantum_machines/test_quantum_machines_cluster.py b/tests/instruments/quantum_machines/test_quantum_machines_cluster.py index d72332675..c1ebf7c3d 100644 --- a/tests/instruments/quantum_machines/test_quantum_machines_cluster.py +++ b/tests/instruments/quantum_machines/test_quantum_machines_cluster.py @@ -277,6 +277,34 @@ def test_append_configuration_without_initial_setup_raises_error( ): qmm.append_configuration(configuration=compilation_config) + @patch("qililab.instruments.quantum_machines.quantum_machines_cluster.QuantumMachinesManager") + @patch("qililab.instruments.quantum_machines.quantum_machines_cluster.QuantumMachine") + def test_get_controller_type_from_bus_singleInput( + self, mock_qmm, mock_qm, qmm: QuantumMachinesCluster, compilation_config: dict + ): + qmm.initial_setup() + qmm.turn_on() + + controller = qmm.get_controller_type_from_bus("flux_q0") + assert controller == "opx1" + + @patch("qililab.instruments.quantum_machines.quantum_machines_cluster.QuantumMachinesManager") + @patch("qililab.instruments.quantum_machines.quantum_machines_cluster.QuantumMachine") + def test_get_controller_type_from_bus_without_controller_raises_error( + self, mock_qmm, mock_qm, qmm: QuantumMachinesCluster, compilation_config: dict + ): + """Test get_controller_type_from_bus method raises an error when no controller is inside bus.""" + qmm.initial_setup() + qmm.turn_on() + + qmm._config["elements"]["bus"] = {"singleInput": {"port": ("con10", 1)}} + + with pytest.raises( + AttributeError, + match=re.escape("Controller with bus bus does not exist"), + ): + qmm.get_controller_type_from_bus("bus") + @patch("qililab.instruments.quantum_machines.quantum_machines_cluster.QuantumMachinesManager") @patch("qililab.instruments.quantum_machines.quantum_machines_cluster.QuantumMachine") def test_compile(self, mock_qmm, mock_qm, qmm: QuantumMachinesCluster, qua_program: Program): @@ -318,16 +346,19 @@ def test_run_compiled_program(self, mock_qmm, mock_qm, qmm: QuantumMachinesClust qmm.turn_on() qmm._qm.compile.return_value = "123" + qmm._controller = "opx1000" + qmm._pending_set_intermediate_frequency = {"drive_q0": 20e6} compile_program_id = qmm.compile(qua_program) _ = qmm.run_compiled_program(compile_program_id) - # CHANGES: qm.queue.add_compiled() -> qm.add_compiled() + # TODO: qm.queue.add_compiled() -> qm.add_compiled() qmm._qm.queue.add_compiled.assert_called_once_with(compile_program_id) - # CHANGES: job.wait_for_execution() is deprecated and will be removed in the future. Please use job.wait_until("Running") instead. + # TODO: job.wait_for_execution() is deprecated and will be removed in the future. Please use job.wait_until("Running") instead. # The following stopped working in testing, but we have verified that works in hardware, so I remove it temporarily. # qmm._qm.queue.add_compiled.return_value.wait_until.assert_called_once() + qmm._qm.calibrate_element.assert_called_once() # Assert that the settings are still in synch: assert qmm._config == qmm.settings.to_qua_config() @@ -387,6 +418,12 @@ def test_set_parameter_of_bus_method_with_octave( qmm_with_octave.set_parameter_of_bus(bus, parameter, value) if parameter == Parameter.LO_FREQUENCY: qmm_with_octave._qm.octave.set_lo_frequency.assert_called_once() + calls = [ + call(element) + for element in qmm_with_octave._config["elements"] + if "RF_inputs" in qmm_with_octave._config["elements"][element] + ] + qmm_with_octave._qm.calibrate_element.assert_has_calls(calls) if parameter == Parameter.GAIN: qmm_with_octave._qm.octave.set_rf_output_gain.assert_called_once() if parameter == Parameter.IF: @@ -414,14 +451,14 @@ def test_set_parameter_of_bus_method( qmm._config = qmm.settings.to_qua_config() qmm.set_parameter_of_bus(bus, parameter, value) + + element = next((element for element in qmm.settings.elements if element["bus"] == bus), None) if parameter == Parameter.IF: - qmm._qm.set_intermediate_frequency.assert_called_once() - if parameter in [Parameter.THRESHOLD_ROTATION, Parameter.THRESHOLD]: - element = next((element for element in qmm.settings.elements if element["bus"] == bus), None) - if parameter == Parameter.THRESHOLD_ROTATION: - assert value == element["threshold_rotation"] - if parameter == Parameter.THRESHOLD: - assert value == element["threshold"] + assert value == element["intermediate_frequency"] + if parameter == Parameter.THRESHOLD_ROTATION: + assert value == element["threshold_rotation"] + if parameter == Parameter.THRESHOLD: + assert value == element["threshold"] # Assert that the settings are still in synch: assert qmm._config == qmm.settings.to_qua_config() @@ -449,6 +486,34 @@ def test_set_parameter_without_connection_changes_settings( ## Test `settings` qililab dictionary: assert qmm.settings.to_qua_config()["elements"][bus][parameter] == value + @pytest.mark.parametrize( + "bus, parameter, value", + [ + ("readout_q0_rf", Parameter.IF, 17e6), + ], + ) + @patch("qililab.instruments.quantum_machines.quantum_machines_cluster.QuantumMachinesManager") + @patch("qililab.instruments.quantum_machines.quantum_machines_cluster.QuantumMachine") + def test_set_parameter_if_with_opx1000( + self, + mock_qmm, + mock_qm, + bus: str, + parameter: Parameter, + value: float | str | bool, + qmm_with_opx1000: QuantumMachinesCluster, + ): + """Test that both the set method fills the bus _pending_set_intermediate_frequency correctly.""" + + qmm_with_opx1000.initial_setup() + qmm_with_opx1000.turn_on() + qmm_with_opx1000._config = qmm_with_opx1000.settings.to_qua_config() + + qmm_with_opx1000.set_parameter_of_bus(bus, parameter, value) + + ## Test `_intermediate_frequency[bus]` is created for later use: + assert qmm_with_opx1000._pending_set_intermediate_frequency[bus] == value + @pytest.mark.parametrize( "bus, parameter, value", [ From af7acec85c0cf9db0782e73f25d48881266a3754 Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Mon, 19 Aug 2024 13:53:22 +0200 Subject: [PATCH 35/69] tests --- tests/platform/test_platform.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index 3c0dc6d75..94dab51fb 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -119,6 +119,17 @@ def get_calibration(): return calibration +@pytest.fixture(name="calibration") +def get_calibration(): + readout = Square(1.0, 2000) + weights = IQPair(Square(1.0, 2000), Square(1.0, 2000)) + + calibration = Calibration() + calibration.add_waveform(bus="readout_bus", name="readout", waveform=readout) + calibration.add_weights(bus="readout_bus", name="optimal_weights", weights=weights) + + return calibration + @pytest.fixture(name="anneal_qprogram") def get_anneal_qprogram(runcard, calibration): platform = Platform(runcard=runcard) @@ -406,6 +417,25 @@ def test_execute_anneal_program(self, platform: Platform, anneal_qprogram, calib ) assert str(anneal_qprogram) == str(mock_execute_qprogram.call_args[1]["qprogram"]) + def test_execute_anneal_program_no_calibration_raises_error(self, platform: Platform, anneal_qprogram, calibration): + mock_execute_qprogram = MagicMock() + platform.execute_qprogram = mock_execute_qprogram # type: ignore[method-assign] + transpiler = MagicMock() + transpiler.return_value = (1, 2) + error_string = "A calibration instance and calibrated measurement must be provided to run an annealing schedule." + with pytest.raises(ValueError, match=error_string): + platform.execute_anneal_program( + annealing_program_dict=[{"qubit_0": {"sigma_x": 0.1, "sigma_z": 0.2}}], transpiler=transpiler, averages=2, + readout_bus="readout_bus", measurement_name="readout", + ) + + platform.execute_anneal_program( + annealing_program_dict=[{"qubit_0": {"sigma_x": 0.1, "sigma_z": 0.2}}], transpiler=transpiler, averages=2, + readout_bus="readout_bus", measurement_name="readout", + calibration=calibration + ) + assert str(anneal_qprogram) == str(mock_execute_qprogram.call_args[1]["qprogram"]) + def test_execute_anneal_program_no_calibration_raises_error(self, platform: Platform, anneal_qprogram, calibration): mock_execute_qprogram = MagicMock() platform.execute_qprogram = mock_execute_qprogram # type: ignore[method-assign] From 79dd47547db30b78f450b9383ce2cded897d1e4e Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Mon, 19 Aug 2024 13:56:46 +0200 Subject: [PATCH 36/69] isort --- tests/platform/test_platform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index 94dab51fb..fd9719933 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -24,7 +24,7 @@ from qililab.instruments.quantum_machines import QuantumMachinesCluster from qililab.platform import Bus, Buses, Platform from qililab.pulse import Drag, Pulse, PulseEvent, PulseSchedule, Rectangular -from qililab.qprogram import QProgram, Calibration +from qililab.qprogram import Calibration, QProgram from qililab.result.qblox_results import QbloxResult from qililab.result.qprogram.quantum_machines_measurement_result import QuantumMachinesMeasurementResult from qililab.settings import Runcard From e139c8dd9c51df48823fc20727a500dfefb2689d Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Mon, 19 Aug 2024 14:16:08 +0200 Subject: [PATCH 37/69] measure in test --- tests/platform/test_platform.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index fd9719933..18c38c049 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -131,7 +131,7 @@ def get_calibration(): return calibration @pytest.fixture(name="anneal_qprogram") -def get_anneal_qprogram(runcard, calibration): +def get_anneal_qprogram(runcard): platform = Platform(runcard=runcard) anneal_waveforms = { "phix_q0": ( @@ -148,11 +148,13 @@ def get_anneal_qprogram(runcard, calibration): ), } averages = 2 + readout = Square(1.0, 2000) + weights = IQPair(Square(1.0, 2000), Square(1.0, 2000)) qp_anneal = QProgram() with qp_anneal.average(averages): for bus, waveform in anneal_waveforms.values(): qp_anneal.play(bus=bus.alias, waveform=waveform) - qp_anneal.measure(bus="readout_bus", name="readout", weights="optimal_weights") + qp_anneal.measure(bus="readout_bus", waveform=readout, weights=weights) return qp_anneal From fd79f539b6f2f2fb33842d8e086ffdd00ce58afd Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Mon, 19 Aug 2024 14:34:39 +0200 Subject: [PATCH 38/69] code quality --- src/qililab/platform/platform.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/qililab/platform/platform.py b/src/qililab/platform/platform.py index 8f6b04f2f..e4b19f996 100644 --- a/src/qililab/platform/platform.py +++ b/src/qililab/platform/platform.py @@ -653,7 +653,9 @@ def execute_anneal_program( bus=readout_bus, waveform=measurement_name, weights=IQPair(I=weights_shape, Q=weights_shape) ) else: - raise ValueError("A calibration instance and calibrated measurement must be provided to run an annealing schedule.") + raise ValueError( + "A calibration instance and calibrated measurement must be provided to run an annealing schedule." + ) self.execute_qprogram(qprogram=qp_annealing) From 8ca635e536f11bf46400a902617760dff40b9b7f Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Mon, 19 Aug 2024 14:36:53 +0200 Subject: [PATCH 39/69] code quality --- tests/platform/test_platform.py | 59 +++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index 18c38c049..012133a2c 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -108,6 +108,7 @@ def fixture_qblox_results(): }, ] + @pytest.fixture(name="calibration") def get_calibration(): readout = Square(1.0, 2000) @@ -116,9 +117,10 @@ def get_calibration(): calibration = Calibration() calibration.add_waveform(bus="readout_bus", name="readout", waveform=readout) calibration.add_weights(bus="readout_bus", name="optimal_weights", weights=weights) - + return calibration + @pytest.fixture(name="calibration") def get_calibration(): readout = Square(1.0, 2000) @@ -130,6 +132,7 @@ def get_calibration(): return calibration + @pytest.fixture(name="anneal_qprogram") def get_anneal_qprogram(runcard): platform = Platform(runcard=runcard) @@ -406,35 +409,50 @@ def test_execute_anneal_program(self, platform: Platform, anneal_qprogram, calib transpiler.return_value = (1, 2) # with patch(qililab.analog.annealing_program, "AnnealingProgram") as dummy_anneal_program: platform.execute_anneal_program( - annealing_program_dict=[{"qubit_0": {"sigma_x": 0.1, "sigma_z": 0.2}}], transpiler=transpiler, averages=2, - readout_bus="readout_bus", measurement_name="readout", weights="optimal_weights", - calibration=calibration + annealing_program_dict=[{"qubit_0": {"sigma_x": 0.1, "sigma_z": 0.2}}], + transpiler=transpiler, + averages=2, + readout_bus="readout_bus", + measurement_name="readout", + weights="optimal_weights", + calibration=calibration, ) assert str(anneal_qprogram) == str(mock_execute_qprogram.call_args[1]["qprogram"]) - + platform.execute_anneal_program( - annealing_program_dict=[{"qubit_0": {"sigma_x": 0.1, "sigma_z": 0.2}}], transpiler=transpiler, averages=2, - readout_bus="readout_bus", measurement_name="readout", - calibration=calibration + annealing_program_dict=[{"qubit_0": {"sigma_x": 0.1, "sigma_z": 0.2}}], + transpiler=transpiler, + averages=2, + readout_bus="readout_bus", + measurement_name="readout", + calibration=calibration, ) assert str(anneal_qprogram) == str(mock_execute_qprogram.call_args[1]["qprogram"]) - + def test_execute_anneal_program_no_calibration_raises_error(self, platform: Platform, anneal_qprogram, calibration): mock_execute_qprogram = MagicMock() platform.execute_qprogram = mock_execute_qprogram # type: ignore[method-assign] transpiler = MagicMock() transpiler.return_value = (1, 2) - error_string = "A calibration instance and calibrated measurement must be provided to run an annealing schedule." + error_string = ( + "A calibration instance and calibrated measurement must be provided to run an annealing schedule." + ) with pytest.raises(ValueError, match=error_string): platform.execute_anneal_program( - annealing_program_dict=[{"qubit_0": {"sigma_x": 0.1, "sigma_z": 0.2}}], transpiler=transpiler, averages=2, - readout_bus="readout_bus", measurement_name="readout", + annealing_program_dict=[{"qubit_0": {"sigma_x": 0.1, "sigma_z": 0.2}}], + transpiler=transpiler, + averages=2, + readout_bus="readout_bus", + measurement_name="readout", ) platform.execute_anneal_program( - annealing_program_dict=[{"qubit_0": {"sigma_x": 0.1, "sigma_z": 0.2}}], transpiler=transpiler, averages=2, - readout_bus="readout_bus", measurement_name="readout", - calibration=calibration + annealing_program_dict=[{"qubit_0": {"sigma_x": 0.1, "sigma_z": 0.2}}], + transpiler=transpiler, + averages=2, + readout_bus="readout_bus", + measurement_name="readout", + calibration=calibration, ) assert str(anneal_qprogram) == str(mock_execute_qprogram.call_args[1]["qprogram"]) @@ -443,11 +461,16 @@ def test_execute_anneal_program_no_calibration_raises_error(self, platform: Plat platform.execute_qprogram = mock_execute_qprogram # type: ignore[method-assign] transpiler = MagicMock() transpiler.return_value = (1, 2) - error_string = "A calibration instance and calibrated measurement must be provided to run an annealing schedule." + error_string = ( + "A calibration instance and calibrated measurement must be provided to run an annealing schedule." + ) with pytest.raises(ValueError, match=error_string): platform.execute_anneal_program( - annealing_program_dict=[{"qubit_0": {"sigma_x": 0.1, "sigma_z": 0.2}}], transpiler=transpiler, averages=2, - readout_bus="readout_bus", measurement_name="readout", + annealing_program_dict=[{"qubit_0": {"sigma_x": 0.1, "sigma_z": 0.2}}], + transpiler=transpiler, + averages=2, + readout_bus="readout_bus", + measurement_name="readout", ) def test_execute_qprogram_with_qblox(self, platform: Platform): From 9f5d37ee7310d09c8167970930ef86c1d8d2ad4d Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Mon, 19 Aug 2024 14:40:20 +0200 Subject: [PATCH 40/69] removing duplicate fixture --- tests/platform/test_platform.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index 012133a2c..8c8f5fcd7 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -121,18 +121,6 @@ def get_calibration(): return calibration -@pytest.fixture(name="calibration") -def get_calibration(): - readout = Square(1.0, 2000) - weights = IQPair(Square(1.0, 2000), Square(1.0, 2000)) - - calibration = Calibration() - calibration.add_waveform(bus="readout_bus", name="readout", waveform=readout) - calibration.add_weights(bus="readout_bus", name="optimal_weights", weights=weights) - - return calibration - - @pytest.fixture(name="anneal_qprogram") def get_anneal_qprogram(runcard): platform = Platform(runcard=runcard) From a548410c16085963f37febca0a24886f244e44b6 Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Mon, 19 Aug 2024 14:43:08 +0200 Subject: [PATCH 41/69] removing duplicate --- tests/platform/test_platform.py | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index 8c8f5fcd7..ee485b268 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -417,33 +417,6 @@ def test_execute_anneal_program(self, platform: Platform, anneal_qprogram, calib ) assert str(anneal_qprogram) == str(mock_execute_qprogram.call_args[1]["qprogram"]) - def test_execute_anneal_program_no_calibration_raises_error(self, platform: Platform, anneal_qprogram, calibration): - mock_execute_qprogram = MagicMock() - platform.execute_qprogram = mock_execute_qprogram # type: ignore[method-assign] - transpiler = MagicMock() - transpiler.return_value = (1, 2) - error_string = ( - "A calibration instance and calibrated measurement must be provided to run an annealing schedule." - ) - with pytest.raises(ValueError, match=error_string): - platform.execute_anneal_program( - annealing_program_dict=[{"qubit_0": {"sigma_x": 0.1, "sigma_z": 0.2}}], - transpiler=transpiler, - averages=2, - readout_bus="readout_bus", - measurement_name="readout", - ) - - platform.execute_anneal_program( - annealing_program_dict=[{"qubit_0": {"sigma_x": 0.1, "sigma_z": 0.2}}], - transpiler=transpiler, - averages=2, - readout_bus="readout_bus", - measurement_name="readout", - calibration=calibration, - ) - assert str(anneal_qprogram) == str(mock_execute_qprogram.call_args[1]["qprogram"]) - def test_execute_anneal_program_no_calibration_raises_error(self, platform: Platform, anneal_qprogram, calibration): mock_execute_qprogram = MagicMock() platform.execute_qprogram = mock_execute_qprogram # type: ignore[method-assign] From 792e2e88a6539f9cc9e20072168ca8caa7eb4f6c Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Mon, 19 Aug 2024 14:47:50 +0200 Subject: [PATCH 42/69] disabling too many lines in platform --- src/qililab/platform/platform.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/qililab/platform/platform.py b/src/qililab/platform/platform.py index e4b19f996..7bdbbc0cb 100644 --- a/src/qililab/platform/platform.py +++ b/src/qililab/platform/platform.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +# pylint: disable=too-many-lines """Platform class.""" import ast import io From b64839c6d2403037ede8f3098c3de549510d3f79 Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Mon, 19 Aug 2024 14:51:30 +0200 Subject: [PATCH 43/69] removing unused arguments --- tests/platform/test_platform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index ee485b268..3f505565c 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -417,7 +417,7 @@ def test_execute_anneal_program(self, platform: Platform, anneal_qprogram, calib ) assert str(anneal_qprogram) == str(mock_execute_qprogram.call_args[1]["qprogram"]) - def test_execute_anneal_program_no_calibration_raises_error(self, platform: Platform, anneal_qprogram, calibration): + def test_execute_anneal_program_no_calibration_raises_error(self, platform: Platform): mock_execute_qprogram = MagicMock() platform.execute_qprogram = mock_execute_qprogram # type: ignore[method-assign] transpiler = MagicMock() From 76f66a586f3bdebaaebaf4a518a26f061ce5a777 Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Mon, 19 Aug 2024 15:00:19 +0200 Subject: [PATCH 44/69] calibration fixture --- tests/platform/test_platform.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index 3f505565c..81110b695 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -111,11 +111,16 @@ def fixture_qblox_results(): @pytest.fixture(name="calibration") def get_calibration(): - readout = Square(1.0, 2000) - weights = IQPair(Square(1.0, 2000), Square(1.0, 2000)) + readout_duration = 2000 + readout_amplitude = 1.0 + r_wf_I = Square(amplitude=readout_amplitude, duration=readout_duration) + r_wf_Q = Square(amplitude=0.0, duration=readout_duration) + readout_waveform = IQPair(I=r_wf_I, Q=r_wf_Q), + weights_shape = Square(amplitude=1, duration=readout_duration) + weights = IQPair(I=weights_shape, Q=weights_shape) calibration = Calibration() - calibration.add_waveform(bus="readout_bus", name="readout", waveform=readout) + calibration.add_waveform(bus="readout_bus", name="readout", waveform=readout_waveform) calibration.add_weights(bus="readout_bus", name="optimal_weights", weights=weights) return calibration From a78396723c7622130b18e831f726580f85d5072e Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Mon, 19 Aug 2024 15:03:14 +0200 Subject: [PATCH 45/69] code quality --- tests/platform/test_platform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index 81110b695..fea3a1e12 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -115,7 +115,7 @@ def get_calibration(): readout_amplitude = 1.0 r_wf_I = Square(amplitude=readout_amplitude, duration=readout_duration) r_wf_Q = Square(amplitude=0.0, duration=readout_duration) - readout_waveform = IQPair(I=r_wf_I, Q=r_wf_Q), + readout_waveform = (IQPair(I=r_wf_I, Q=r_wf_Q),) weights_shape = Square(amplitude=1, duration=readout_duration) weights = IQPair(I=weights_shape, Q=weights_shape) From a0c3df54f1d52fe27329a7b2c933970fe010ecb1 Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Mon, 19 Aug 2024 15:10:09 +0200 Subject: [PATCH 46/69] fixing unittest --- tests/platform/test_platform.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index fea3a1e12..b79c23c94 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -144,13 +144,18 @@ def get_anneal_qprogram(runcard): ), } averages = 2 - readout = Square(1.0, 2000) - weights = IQPair(Square(1.0, 2000), Square(1.0, 2000)) + readout_duration = 2000 + readout_amplitude = 1.0 + r_wf_I = Square(amplitude=readout_amplitude, duration=readout_duration) + r_wf_Q = Square(amplitude=0.0, duration=readout_duration) + readout_waveform = (IQPair(I=r_wf_I, Q=r_wf_Q),) + weights_shape = Square(amplitude=1, duration=readout_duration) + weights = IQPair(I=weights_shape, Q=weights_shape) qp_anneal = QProgram() with qp_anneal.average(averages): for bus, waveform in anneal_waveforms.values(): qp_anneal.play(bus=bus.alias, waveform=waveform) - qp_anneal.measure(bus="readout_bus", waveform=readout, weights=weights) + qp_anneal.measure(bus="readout_bus", waveform=readout_waveform, weights=weights) return qp_anneal From 070a3237b7ecc99fff0c519029091b65e5993768 Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Mon, 19 Aug 2024 15:15:23 +0200 Subject: [PATCH 47/69] removing brackets typo --- tests/platform/test_platform.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index b79c23c94..dc44563c0 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -115,7 +115,7 @@ def get_calibration(): readout_amplitude = 1.0 r_wf_I = Square(amplitude=readout_amplitude, duration=readout_duration) r_wf_Q = Square(amplitude=0.0, duration=readout_duration) - readout_waveform = (IQPair(I=r_wf_I, Q=r_wf_Q),) + readout_waveform = IQPair(I=r_wf_I, Q=r_wf_Q) weights_shape = Square(amplitude=1, duration=readout_duration) weights = IQPair(I=weights_shape, Q=weights_shape) @@ -148,7 +148,7 @@ def get_anneal_qprogram(runcard): readout_amplitude = 1.0 r_wf_I = Square(amplitude=readout_amplitude, duration=readout_duration) r_wf_Q = Square(amplitude=0.0, duration=readout_duration) - readout_waveform = (IQPair(I=r_wf_I, Q=r_wf_Q),) + readout_waveform = IQPair(I=r_wf_I, Q=r_wf_Q) weights_shape = Square(amplitude=1, duration=readout_duration) weights = IQPair(I=weights_shape, Q=weights_shape) qp_anneal = QProgram() From d2917097d3a38bb2e663e516091d2738f4ffcfa3 Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Mon, 19 Aug 2024 15:30:15 +0200 Subject: [PATCH 48/69] passing by calibration to execute_qprogram --- src/qililab/platform/platform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qililab/platform/platform.py b/src/qililab/platform/platform.py index 7bdbbc0cb..c3a17a654 100644 --- a/src/qililab/platform/platform.py +++ b/src/qililab/platform/platform.py @@ -658,7 +658,7 @@ def execute_anneal_program( "A calibration instance and calibrated measurement must be provided to run an annealing schedule." ) - self.execute_qprogram(qprogram=qp_annealing) + self.execute_qprogram(qprogram=qp_annealing, calibration=calibration) def execute_qprogram( # pylint: disable=too-many-locals self, From cf97c85cc661f6141a937c2ac057291d6dd46efd Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Mon, 19 Aug 2024 15:42:02 +0200 Subject: [PATCH 49/69] refactoring --- src/qililab/platform/platform.py | 49 ++++++++++++++++---------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/src/qililab/platform/platform.py b/src/qililab/platform/platform.py index c3a17a654..1cbc93ac7 100644 --- a/src/qililab/platform/platform.py +++ b/src/qililab/platform/platform.py @@ -635,30 +635,31 @@ def execute_anneal_program( transpiler (Callable): ising to flux transpiler. The transpiler should take 2 values as arguments (delta, epsilon) and return 2 values (phix, phiz) averages (int, optional): Amount of times to run and average the program over. Defaults to 1. """ - annealing_program = AnnealingProgram(self, annealing_program_dict) - annealing_program.transpile(transpiler) - annealing_waveforms = annealing_program.get_waveforms() - - qp_annealing = QProgram() - with qp_annealing.average(averages): - for bus, waveform in annealing_waveforms.values(): - qp_annealing.play(bus=bus.alias, waveform=waveform) - - if calibration and calibration.has_waveform(bus=readout_bus, name=measurement_name): - if weights and calibration.has_weights(bus=readout_bus, name=weights): - qp_annealing.measure(bus=readout_bus, waveform=measurement_name, weights=weights) - else: - r_duration = calibration.get_waveform(bus=readout_bus, name=measurement_name).get_duration() - weights_shape = Square(amplitude=1, duration=r_duration) - qp_annealing.measure( - bus=readout_bus, waveform=measurement_name, weights=IQPair(I=weights_shape, Q=weights_shape) - ) - else: - raise ValueError( - "A calibration instance and calibrated measurement must be provided to run an annealing schedule." - ) - - self.execute_qprogram(qprogram=qp_annealing, calibration=calibration) + if calibration and calibration.has_waveform(bus=readout_bus, name=measurement_name): + annealing_program = AnnealingProgram(self, annealing_program_dict) + annealing_program.transpile(transpiler) + annealing_waveforms = annealing_program.get_waveforms() + + qp_annealing = QProgram() + with qp_annealing.average(averages): + for bus, waveform in annealing_waveforms.values(): + qp_annealing.play(bus=bus.alias, waveform=waveform) + + if weights and calibration.has_weights(bus=readout_bus, name=weights): + qp_annealing.measure(bus=readout_bus, waveform=measurement_name, weights=weights) + else: + r_duration = calibration.get_waveform(bus=readout_bus, name=measurement_name).get_duration() + weights_shape = Square(amplitude=1, duration=r_duration) + qp_annealing.measure( + bus=readout_bus, waveform=measurement_name, weights=IQPair(I=weights_shape, Q=weights_shape) + ) + + qp_annealing = qp_annealing.with_calibration() + self.execute_qprogram(qprogram=qp_annealing, calibration=calibration) + else: + raise ValueError( + "A calibration instance and calibrated measurement must be provided to run an annealing schedule." + ) def execute_qprogram( # pylint: disable=too-many-locals self, From 25feb1d36c33ea8de6734046bfd30d4bada1a3a5 Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Mon, 19 Aug 2024 16:02:46 +0200 Subject: [PATCH 50/69] fixing unittest --- src/qililab/platform/platform.py | 3 +-- tests/platform/test_platform.py | 6 ++++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/qililab/platform/platform.py b/src/qililab/platform/platform.py index 1cbc93ac7..32082d0d6 100644 --- a/src/qililab/platform/platform.py +++ b/src/qililab/platform/platform.py @@ -653,8 +653,7 @@ def execute_anneal_program( qp_annealing.measure( bus=readout_bus, waveform=measurement_name, weights=IQPair(I=weights_shape, Q=weights_shape) ) - - qp_annealing = qp_annealing.with_calibration() + self.execute_qprogram(qprogram=qp_annealing, calibration=calibration) else: raise ValueError( diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index dc44563c0..05054de6d 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -415,7 +415,8 @@ def test_execute_anneal_program(self, platform: Platform, anneal_qprogram, calib weights="optimal_weights", calibration=calibration, ) - assert str(anneal_qprogram) == str(mock_execute_qprogram.call_args[1]["qprogram"]) + qprogram = mock_execute_qprogram.call_args[1]["qprogram"].with_calibration(calibration) + assert str(anneal_qprogram) == str(qprogram) platform.execute_anneal_program( annealing_program_dict=[{"qubit_0": {"sigma_x": 0.1, "sigma_z": 0.2}}], @@ -425,7 +426,8 @@ def test_execute_anneal_program(self, platform: Platform, anneal_qprogram, calib measurement_name="readout", calibration=calibration, ) - assert str(anneal_qprogram) == str(mock_execute_qprogram.call_args[1]["qprogram"]) + qprogram = mock_execute_qprogram.call_args[1]["qprogram"].with_calibration(calibration) + assert str(anneal_qprogram) == str(qprogram) def test_execute_anneal_program_no_calibration_raises_error(self, platform: Platform): mock_execute_qprogram = MagicMock() From 02f1c2699e08d21c8e98bfcb8edfa94bc9ad59f5 Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Mon, 19 Aug 2024 16:06:52 +0200 Subject: [PATCH 51/69] removing comment --- tests/platform/test_platform.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index 05054de6d..1f0a1cd87 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -405,7 +405,6 @@ def test_execute_anneal_program(self, platform: Platform, anneal_qprogram, calib platform.execute_qprogram = mock_execute_qprogram # type: ignore[method-assign] transpiler = MagicMock() transpiler.return_value = (1, 2) - # with patch(qililab.analog.annealing_program, "AnnealingProgram") as dummy_anneal_program: platform.execute_anneal_program( annealing_program_dict=[{"qubit_0": {"sigma_x": 0.1, "sigma_z": 0.2}}], transpiler=transpiler, From 582a8e18359a3cfbda75dda531757dc346c7c0f5 Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 21 Aug 2024 10:32:52 +0200 Subject: [PATCH 52/69] [QHC-636] Create AnnealingSchedule class in Qililab. (#767) * add parameter class and one qubit analog transpiler * add unit tests * add unit tests * move fluqe parameter inside analog, fix pylint complains * draft of anneal program with transpiler for 1q * add analog program execution workflow * add changelog * fix mypy * fix docstring * add unit test for anneal program * wip platform unit tests * add to_dict method to qprogram in order to tests that a qprogram is equivalent to another * fix tests * fix pylint * fix docstrings, pylint * still trying to fix docs * still trying to fix the docs * still trying to fix the docs * still trying to fix the docs * change qprogram to_readable_dict to __str__ method * fix tests, fix docs * add suggested changes * add unittest for qprogram str method * fix tests * remove unused import * fix coverage * update changelog * tests * returning changes lost * isort * measure in test * Revert "measure in test" This reverts commit 6dc9b07c6d3a34dbff2f40d340384e0be69b1f55. * Revert "isort" This reverts commit a958ea8e86199b38183016b38b4565c2458756c8. * Revert "returning changes lost" This reverts commit 86a2bbddb6f1e6d7e82e9c43e83bec6561730892. * Revert "tests" This reverts commit d7d0669a1bd5cb925b5d28c1c39a2d61b9a9433b. --------- Co-authored-by: GitHub Actions bot --- src/qililab/platform/platform.py | 35 ++++++++++++++++++++++++++++++++ tests/platform/test_platform.py | 1 + 2 files changed, 36 insertions(+) diff --git a/src/qililab/platform/platform.py b/src/qililab/platform/platform.py index 32082d0d6..dc58feec5 100644 --- a/src/qililab/platform/platform.py +++ b/src/qililab/platform/platform.py @@ -21,6 +21,7 @@ from dataclasses import asdict from queue import Queue from typing import Callable +from typing import Callable import numpy as np from qibo.gates import M @@ -29,11 +30,13 @@ from qpysequence import Sequence as QpySequence from ruamel.yaml import YAML +from qililab.analog import AnnealingProgram from qililab.analog import AnnealingProgram from qililab.chip import Chip from qililab.circuit_transpiler import CircuitTranspiler from qililab.config import logger from qililab.constants import FLUX_CONTROL_REGEX, GATE_ALIAS_REGEX, RUNCARD +from qililab.constants import FLUX_CONTROL_REGEX, GATE_ALIAS_REGEX, RUNCARD from qililab.instrument_controllers import InstrumentController, InstrumentControllers from qililab.instrument_controllers.utils import InstrumentControllerFactory from qililab.instruments.instrument import Instrument @@ -303,6 +306,9 @@ def __init__(self, runcard: Runcard): self.flux_to_bus_topology = runcard.flux_control_topology """Flux to bus mapping for analog control""" + self.flux_to_bus_topology = runcard.flux_control_topology + """Flux to bus mapping for analog control""" + self._connected_to_instruments: bool = False """Boolean indicating the connection status to the instruments. Defaults to False (not connected).""" @@ -382,6 +388,7 @@ def get_element(self, alias: str): tuple[object, list | None]: Element class together with the index of the bus where the element is located. """ # TODO: fix docstring, bus is not returned in most cases + # TODO: fix docstring, bus is not returned in most cases if alias is not None: if alias == "platform": return self.gates_settings @@ -408,6 +415,22 @@ def get_element(self, alias: str): None, ) ) + regex_match = re.search(FLUX_CONTROL_REGEX, alias) + if regex_match is not None: + element_type = regex_match.lastgroup + element_shorthands = {"qubit": "q", "coupler": "c"} + flux = regex_match["flux"] + # TODO: support commuting the name of the coupler eg. c1_0 = c0_1 + return self._get_bus_by_alias( + next( + ( + element.bus + for element in self.flux_to_bus_topology # type: ignore[union-attr] + if element.flux == f"{flux}_{element_shorthands[element_type]}{regex_match[element_type]}" # type: ignore[index] + ), + None, + ) + ) element = self.instruments.get_instrument(alias=alias) if element is None: @@ -583,6 +606,9 @@ def to_dict(self): flux_control_topology_dict = { RUNCARD.FLUX_CONTROL_TOPOLOGY: [flux_control.to_dict() for flux_control in self.flux_to_bus_topology] } + flux_control_topology_dict = { + RUNCARD.FLUX_CONTROL_TOPOLOGY: [flux_control.to_dict() for flux_control in self.flux_to_bus_topology] + } return ( name_dict @@ -593,6 +619,15 @@ def to_dict(self): | instrument_controllers_dict | flux_control_topology_dict ) + return ( + name_dict + | gates_settings_dict + | chip_dict + | buses_dict + | instrument_dict + | instrument_controllers_dict + | flux_control_topology_dict + ) def __str__(self) -> str: """String representation of the platform. diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index 1f0a1cd87..4677e58be 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -14,6 +14,7 @@ from qpysequence import Sequence from ruamel.yaml import YAML +from qililab import Arbitrary, save_platform from qililab import Arbitrary, save_platform from qililab.chip import Chip, Qubit from qililab.constants import DEFAULT_PLATFORM_NAME From 5848a8945944defa548334cf81c083e07d14aaa8 Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Wed, 21 Aug 2024 10:42:18 +0200 Subject: [PATCH 53/69] changelog --- docs/releases/changelog-dev.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/releases/changelog-dev.md b/docs/releases/changelog-dev.md index 232914301..ca62c6a4d 100644 --- a/docs/releases/changelog-dev.md +++ b/docs/releases/changelog-dev.md @@ -2,6 +2,10 @@ ### New features since last release +- Add default measurement to `execute_anneal_program()` method. This method takes now a calibration file and parameters +to add the dispersive measurement at the end of the annealing schedule. + [#778](https://github.com/qilimanjaro-tech/qililab/pull/778) + - Add `__str__` method to qprogram. The string is a readable qprogram. [#767](https://github.com/qilimanjaro-tech/qililab/pull/767) From eac6e65e00fb60a870dbc09e89a6c8db6d558bc3 Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Wed, 21 Aug 2024 10:55:58 +0200 Subject: [PATCH 54/69] code quality --- tests/platform/test_platform.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index 14214901c..d1d6ff6f4 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -125,6 +125,7 @@ def get_calibration(): return calibration + @pytest.fixture(name="anneal_qprogram") def get_anneal_qprogram(runcard): platform = Platform(runcard=runcard) @@ -399,7 +400,6 @@ def _compile_and_assert(self, platform: Platform, program: Circuit | PulseSchedu assert all(isinstance(sequence, Sequence) for sequence in sequences_list) assert sequences_list[0]._program.duration == 200_000 * 1000 + 4 + 4 + 4 - def test_execute_anneal_program(self, platform: Platform, anneal_qprogram, calibration): mock_execute_qprogram = MagicMock() platform.execute_qprogram = mock_execute_qprogram # type: ignore[method-assign] @@ -446,7 +446,6 @@ def test_execute_anneal_program_no_calibration_raises_error(self, platform: Plat measurement_name="readout", ) - def test_execute_qprogram_with_qblox(self, platform: Platform): """Test that the execute method compiles the qprogram, calls the buses to run and return the results.""" drive_wf = IQPair(I=Square(amplitude=1.0, duration=40), Q=Square(amplitude=0.0, duration=40)) From 76d068a8a2d8deba6a8791ebe06e8c2bb7664933 Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Wed, 21 Aug 2024 11:13:27 +0200 Subject: [PATCH 55/69] returning results of annealing --- src/qililab/platform/platform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qililab/platform/platform.py b/src/qililab/platform/platform.py index 32082d0d6..19e7693e1 100644 --- a/src/qililab/platform/platform.py +++ b/src/qililab/platform/platform.py @@ -654,7 +654,7 @@ def execute_anneal_program( bus=readout_bus, waveform=measurement_name, weights=IQPair(I=weights_shape, Q=weights_shape) ) - self.execute_qprogram(qprogram=qp_annealing, calibration=calibration) + return self.execute_qprogram(qprogram=qp_annealing, calibration=calibration) else: raise ValueError( "A calibration instance and calibrated measurement must be provided to run an annealing schedule." From de69b2504995697b6acca89e6ebc4449b5db03e8 Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Wed, 21 Aug 2024 11:14:22 +0200 Subject: [PATCH 56/69] returning type --- src/qililab/platform/platform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qililab/platform/platform.py b/src/qililab/platform/platform.py index 19e7693e1..c2e4cd16a 100644 --- a/src/qililab/platform/platform.py +++ b/src/qililab/platform/platform.py @@ -611,7 +611,7 @@ def execute_anneal_program( averages=1, calibration: Calibration | None = None, weights: str | None = None, - ): + ) -> QProgramResults: """Given an annealing program execute it as a qprogram. The annealing program should contain a time ordered list of circuit elements and their corresponging ising coefficients as a dictionary. Example structure: From e7a1313122a9bbb6ed8aecdb6f75b7744c6c2c29 Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Wed, 21 Aug 2024 11:17:53 +0200 Subject: [PATCH 57/69] code quality --- src/qililab/platform/platform.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/qililab/platform/platform.py b/src/qililab/platform/platform.py index c2e4cd16a..95ea8560a 100644 --- a/src/qililab/platform/platform.py +++ b/src/qililab/platform/platform.py @@ -655,10 +655,9 @@ def execute_anneal_program( ) return self.execute_qprogram(qprogram=qp_annealing, calibration=calibration) - else: - raise ValueError( - "A calibration instance and calibrated measurement must be provided to run an annealing schedule." - ) + raise ValueError( + "A calibration instance and calibrated measurement must be provided to run an annealing schedule." + ) def execute_qprogram( # pylint: disable=too-many-locals self, From da0e8e4429799226a2955eb3d72c4070a985cca1 Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Wed, 21 Aug 2024 11:21:42 +0200 Subject: [PATCH 58/69] making calibration file non optional --- src/qililab/platform/platform.py | 6 +++--- tests/platform/test_platform.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/qililab/platform/platform.py b/src/qililab/platform/platform.py index 95ea8560a..07d948628 100644 --- a/src/qililab/platform/platform.py +++ b/src/qililab/platform/platform.py @@ -605,11 +605,11 @@ def __str__(self) -> str: def execute_anneal_program( self, annealing_program_dict: list[dict[str, dict[str, float]]], + calibration: Calibration, readout_bus: str, measurement_name: str, transpiler: Callable, averages=1, - calibration: Calibration | None = None, weights: str | None = None, ) -> QProgramResults: """Given an annealing program execute it as a qprogram. @@ -635,7 +635,7 @@ def execute_anneal_program( transpiler (Callable): ising to flux transpiler. The transpiler should take 2 values as arguments (delta, epsilon) and return 2 values (phix, phiz) averages (int, optional): Amount of times to run and average the program over. Defaults to 1. """ - if calibration and calibration.has_waveform(bus=readout_bus, name=measurement_name): + if calibration.has_waveform(bus=readout_bus, name=measurement_name): annealing_program = AnnealingProgram(self, annealing_program_dict) annealing_program.transpile(transpiler) annealing_waveforms = annealing_program.get_waveforms() @@ -656,7 +656,7 @@ def execute_anneal_program( return self.execute_qprogram(qprogram=qp_annealing, calibration=calibration) raise ValueError( - "A calibration instance and calibrated measurement must be provided to run an annealing schedule." + "The calibrated measurement is not present in the calibration file." ) def execute_qprogram( # pylint: disable=too-many-locals diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index d1d6ff6f4..952593d54 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -435,7 +435,7 @@ def test_execute_anneal_program_no_calibration_raises_error(self, platform: Plat transpiler = MagicMock() transpiler.return_value = (1, 2) error_string = ( - "A calibration instance and calibrated measurement must be provided to run an annealing schedule." + "The calibrated measurement is not present in the calibration file." ) with pytest.raises(ValueError, match=error_string): platform.execute_anneal_program( From 88a244443f2beb583a4378c029627e0dd40ff0e9 Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Wed, 21 Aug 2024 11:22:36 +0200 Subject: [PATCH 59/69] introducing sync --- src/qililab/platform/platform.py | 2 +- tests/platform/test_platform.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/qililab/platform/platform.py b/src/qililab/platform/platform.py index 07d948628..691fa1066 100644 --- a/src/qililab/platform/platform.py +++ b/src/qililab/platform/platform.py @@ -644,7 +644,7 @@ def execute_anneal_program( with qp_annealing.average(averages): for bus, waveform in annealing_waveforms.values(): qp_annealing.play(bus=bus.alias, waveform=waveform) - + qp_annealing.sync() if weights and calibration.has_weights(bus=readout_bus, name=weights): qp_annealing.measure(bus=readout_bus, waveform=measurement_name, weights=weights) else: diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index 952593d54..e280e696f 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -155,6 +155,7 @@ def get_anneal_qprogram(runcard): with qp_anneal.average(averages): for bus, waveform in anneal_waveforms.values(): qp_anneal.play(bus=bus.alias, waveform=waveform) + qp_anneal.sync() qp_anneal.measure(bus="readout_bus", waveform=readout_waveform, weights=weights) return qp_anneal From f0e5ed70f68ec360fe642d7697749750bb7e1ce5 Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Wed, 21 Aug 2024 11:23:03 +0200 Subject: [PATCH 60/69] code quality --- tests/platform/test_platform.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index e280e696f..d7dee4db6 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -435,9 +435,7 @@ def test_execute_anneal_program_no_calibration_raises_error(self, platform: Plat platform.execute_qprogram = mock_execute_qprogram # type: ignore[method-assign] transpiler = MagicMock() transpiler.return_value = (1, 2) - error_string = ( - "The calibrated measurement is not present in the calibration file." - ) + error_string = "The calibrated measurement is not present in the calibration file." with pytest.raises(ValueError, match=error_string): platform.execute_anneal_program( annealing_program_dict=[{"qubit_0": {"sigma_x": 0.1, "sigma_z": 0.2}}], From 07eedc353847a5e29ff54063368196ad4815d62b Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Wed, 21 Aug 2024 11:23:23 +0200 Subject: [PATCH 61/69] code quality --- src/qililab/platform/platform.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/qililab/platform/platform.py b/src/qililab/platform/platform.py index 691fa1066..228427fc1 100644 --- a/src/qililab/platform/platform.py +++ b/src/qililab/platform/platform.py @@ -655,9 +655,7 @@ def execute_anneal_program( ) return self.execute_qprogram(qprogram=qp_annealing, calibration=calibration) - raise ValueError( - "The calibrated measurement is not present in the calibration file." - ) + raise ValueError("The calibrated measurement is not present in the calibration file.") def execute_qprogram( # pylint: disable=too-many-locals self, From 3b9d3420485b265a90ce31064965fbf3849e2df9 Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Wed, 21 Aug 2024 11:24:19 +0200 Subject: [PATCH 62/69] outside loop --- src/qililab/platform/platform.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/qililab/platform/platform.py b/src/qililab/platform/platform.py index 228427fc1..1a53a9bb9 100644 --- a/src/qililab/platform/platform.py +++ b/src/qililab/platform/platform.py @@ -644,15 +644,15 @@ def execute_anneal_program( with qp_annealing.average(averages): for bus, waveform in annealing_waveforms.values(): qp_annealing.play(bus=bus.alias, waveform=waveform) - qp_annealing.sync() - if weights and calibration.has_weights(bus=readout_bus, name=weights): - qp_annealing.measure(bus=readout_bus, waveform=measurement_name, weights=weights) - else: - r_duration = calibration.get_waveform(bus=readout_bus, name=measurement_name).get_duration() - weights_shape = Square(amplitude=1, duration=r_duration) - qp_annealing.measure( - bus=readout_bus, waveform=measurement_name, weights=IQPair(I=weights_shape, Q=weights_shape) - ) + qp_annealing.sync() + if weights and calibration.has_weights(bus=readout_bus, name=weights): + qp_annealing.measure(bus=readout_bus, waveform=measurement_name, weights=weights) + else: + r_duration = calibration.get_waveform(bus=readout_bus, name=measurement_name).get_duration() + weights_shape = Square(amplitude=1, duration=r_duration) + qp_annealing.measure( + bus=readout_bus, waveform=measurement_name, weights=IQPair(I=weights_shape, Q=weights_shape) + ) return self.execute_qprogram(qprogram=qp_annealing, calibration=calibration) raise ValueError("The calibrated measurement is not present in the calibration file.") From e642b001643b1d46c072fff4f30b21f77aa33da4 Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Wed, 21 Aug 2024 11:26:06 +0200 Subject: [PATCH 63/69] checking type --- tests/platform/test_platform.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index d7dee4db6..7eb60ad1e 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -32,6 +32,7 @@ from qililab.system_control import ReadoutSystemControl from qililab.typings.enums import InstrumentName, Parameter from qililab.waveforms import IQPair, Square +from src.qililab.result.qprogram.qprogram_results import QProgramResults from tests.data import Galadriel, SauronQuantumMachines from tests.test_utils import build_platform @@ -407,7 +408,7 @@ def test_execute_anneal_program(self, platform: Platform, anneal_qprogram, calib transpiler = MagicMock() transpiler.return_value = (1, 2) - platform.execute_anneal_program( + results = platform.execute_anneal_program( annealing_program_dict=[{"qubit_0": {"sigma_x": 0.1, "sigma_z": 0.2}}], transpiler=transpiler, averages=2, @@ -418,8 +419,9 @@ def test_execute_anneal_program(self, platform: Platform, anneal_qprogram, calib ) qprogram = mock_execute_qprogram.call_args[1]["qprogram"].with_calibration(calibration) assert str(anneal_qprogram) == str(qprogram) + assert results is QProgramResults - platform.execute_anneal_program( + results = platform.execute_anneal_program( annealing_program_dict=[{"qubit_0": {"sigma_x": 0.1, "sigma_z": 0.2}}], transpiler=transpiler, averages=2, @@ -429,6 +431,7 @@ def test_execute_anneal_program(self, platform: Platform, anneal_qprogram, calib ) qprogram = mock_execute_qprogram.call_args[1]["qprogram"].with_calibration(calibration) assert str(anneal_qprogram) == str(qprogram) + assert results is QProgramResults def test_execute_anneal_program_no_calibration_raises_error(self, platform: Platform): mock_execute_qprogram = MagicMock() From 3f1a623ebc0cb72f0d76153df143283bfc0f595a Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Wed, 21 Aug 2024 11:27:33 +0200 Subject: [PATCH 64/69] changing test --- tests/platform/test_platform.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index 7eb60ad1e..da7bd3ce2 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -433,7 +433,7 @@ def test_execute_anneal_program(self, platform: Platform, anneal_qprogram, calib assert str(anneal_qprogram) == str(qprogram) assert results is QProgramResults - def test_execute_anneal_program_no_calibration_raises_error(self, platform: Platform): + def test_execute_anneal_program_no_measurement_raises_error(self, platform: Platform): mock_execute_qprogram = MagicMock() platform.execute_qprogram = mock_execute_qprogram # type: ignore[method-assign] transpiler = MagicMock() @@ -445,7 +445,7 @@ def test_execute_anneal_program_no_calibration_raises_error(self, platform: Plat transpiler=transpiler, averages=2, readout_bus="readout_bus", - measurement_name="readout", + measurement_name="whatever", ) def test_execute_qprogram_with_qblox(self, platform: Platform): From 2d320cf47f28d76d251a30d7d2d5e6d7006a41c6 Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Wed, 21 Aug 2024 11:28:08 +0200 Subject: [PATCH 65/69] adding calibration --- tests/platform/test_platform.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index da7bd3ce2..faa465a36 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -433,7 +433,7 @@ def test_execute_anneal_program(self, platform: Platform, anneal_qprogram, calib assert str(anneal_qprogram) == str(qprogram) assert results is QProgramResults - def test_execute_anneal_program_no_measurement_raises_error(self, platform: Platform): + def test_execute_anneal_program_no_measurement_raises_error(self, platform: Platform, calibration): mock_execute_qprogram = MagicMock() platform.execute_qprogram = mock_execute_qprogram # type: ignore[method-assign] transpiler = MagicMock() @@ -446,6 +446,7 @@ def test_execute_anneal_program_no_measurement_raises_error(self, platform: Plat averages=2, readout_bus="readout_bus", measurement_name="whatever", + calibration=calibration, ) def test_execute_qprogram_with_qblox(self, platform: Platform): From 3343dce5aa768f6cd69c807ed30e46f3578f1f5c Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Wed, 21 Aug 2024 11:28:44 +0200 Subject: [PATCH 66/69] fixing unittest --- tests/platform/test_platform.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index faa465a36..42c39c47d 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -156,8 +156,8 @@ def get_anneal_qprogram(runcard): with qp_anneal.average(averages): for bus, waveform in anneal_waveforms.values(): qp_anneal.play(bus=bus.alias, waveform=waveform) - qp_anneal.sync() - qp_anneal.measure(bus="readout_bus", waveform=readout_waveform, weights=weights) + qp_anneal.sync() + qp_anneal.measure(bus="readout_bus", waveform=readout_waveform, weights=weights) return qp_anneal From 5b9fb98e2fb4a2c1496884f5e098b2cc2cff893f Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Wed, 21 Aug 2024 11:32:47 +0200 Subject: [PATCH 67/69] checking return type --- tests/platform/test_platform.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index 42c39c47d..d504d23cb 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -404,6 +404,7 @@ def _compile_and_assert(self, platform: Platform, program: Circuit | PulseSchedu def test_execute_anneal_program(self, platform: Platform, anneal_qprogram, calibration): mock_execute_qprogram = MagicMock() + mock_execute_qprogram.return_value = QProgramResults() platform.execute_qprogram = mock_execute_qprogram # type: ignore[method-assign] transpiler = MagicMock() transpiler.return_value = (1, 2) @@ -419,7 +420,7 @@ def test_execute_anneal_program(self, platform: Platform, anneal_qprogram, calib ) qprogram = mock_execute_qprogram.call_args[1]["qprogram"].with_calibration(calibration) assert str(anneal_qprogram) == str(qprogram) - assert results is QProgramResults + assert isinstance(results, QProgramResults) results = platform.execute_anneal_program( annealing_program_dict=[{"qubit_0": {"sigma_x": 0.1, "sigma_z": 0.2}}], @@ -431,7 +432,7 @@ def test_execute_anneal_program(self, platform: Platform, anneal_qprogram, calib ) qprogram = mock_execute_qprogram.call_args[1]["qprogram"].with_calibration(calibration) assert str(anneal_qprogram) == str(qprogram) - assert results is QProgramResults + assert isinstance(results, QProgramResults) def test_execute_anneal_program_no_measurement_raises_error(self, platform: Platform, calibration): mock_execute_qprogram = MagicMock() From 8337c7ccc13e81d474576da95fcf544c90e67556 Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Wed, 21 Aug 2024 12:50:58 +0200 Subject: [PATCH 68/69] removing src --- tests/platform/test_platform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index d504d23cb..2b3cfa0b2 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -32,7 +32,7 @@ from qililab.system_control import ReadoutSystemControl from qililab.typings.enums import InstrumentName, Parameter from qililab.waveforms import IQPair, Square -from src.qililab.result.qprogram.qprogram_results import QProgramResults +from qililab.result.qprogram.qprogram_results import QProgramResults from tests.data import Galadriel, SauronQuantumMachines from tests.test_utils import build_platform From 3cd61fd18e9efbf5be5620e5f7d2a70e073dd8bb Mon Sep 17 00:00:00 2001 From: GitHub Actions bot Date: Wed, 21 Aug 2024 14:20:37 +0200 Subject: [PATCH 69/69] code quality --- tests/platform/test_platform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/platform/test_platform.py b/tests/platform/test_platform.py index 2b3cfa0b2..fc91f8fe6 100644 --- a/tests/platform/test_platform.py +++ b/tests/platform/test_platform.py @@ -26,13 +26,13 @@ from qililab.pulse import Drag, Pulse, PulseEvent, PulseSchedule, Rectangular from qililab.qprogram import Calibration, QProgram from qililab.result.qblox_results import QbloxResult +from qililab.result.qprogram.qprogram_results import QProgramResults from qililab.result.qprogram.quantum_machines_measurement_result import QuantumMachinesMeasurementResult from qililab.settings import Runcard from qililab.settings.gate_event_settings import GateEventSettings from qililab.system_control import ReadoutSystemControl from qililab.typings.enums import InstrumentName, Parameter from qililab.waveforms import IQPair, Square -from qililab.result.qprogram.qprogram_results import QProgramResults from tests.data import Galadriel, SauronQuantumMachines from tests.test_utils import build_platform