Repository: iqm-finland/qiskit-on-iqm (language: python)

Issue:
Initialize repository



Discussion / hints:
Can you modify this PR so that the main branch is completely empty and this PR adds everything. Will be easier to review that way, since this is the initial setup PR and basically everything needs to be reviewed before they appear in branch main. Now there are already some files in main, which I would like to comment on, but are not modified by this PR and I cannot comment. Also it is hard to identify all those files.

Fix patch:
diff --git a/AUTHORS.rst b/AUTHORS.rst
index e10c11803..1447c7747 100644
--- a/AUTHORS.rst
+++ b/AUTHORS.rst
@@ -12,4 +12,5 @@ Contributors
 * Adrian Auer <adrian.auer@meetiqm.com>
 * Stefan Seegerer <stefan.seegerer@meetiqm.com>
 * Vicente Pina Canelles <vicente.pina@meetiqm.com>
+* Jake Muff <jake.muff@vtt.fi>
 
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index c1639fa58..6c8b1eb5c 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -2,6 +2,11 @@
 Changelog
 =========
 
+Version 11.8
+============
+
+* Add ``IQMFakeApollo`` fake backend `#66 <https://github.com/iqm-finland/qiskit-on-iqm/pull/66>`_
+
 Version 11.7
 ============
 
diff --git a/src/iqm/qiskit_iqm/__init__.py b/src/iqm/qiskit_iqm/__init__.py
index f3ad439e7..4be2ff2e3 100644
--- a/src/iqm/qiskit_iqm/__init__.py
+++ b/src/iqm/qiskit_iqm/__init__.py
@@ -13,7 +13,7 @@
 # limitations under the License.
 """Qiskit adapter for IQM's quantum computers.
 """
-from iqm.qiskit_iqm.fake_backends import IQMErrorProfile, IQMFakeAdonis
+from iqm.qiskit_iqm.fake_backends import IQMErrorProfile, IQMFakeAdonis, IQMFakeApollo
 from iqm.qiskit_iqm.fake_backends.iqm_fake_backend import IQMFakeBackend
 from iqm.qiskit_iqm.iqm_job import IQMJob
 from iqm.qiskit_iqm.iqm_provider import IQMBackend, IQMProvider, __version__
diff --git a/src/iqm/qiskit_iqm/fake_backends/__init__.py b/src/iqm/qiskit_iqm/fake_backends/__init__.py
index 8ffef9de2..e95a0bcbe 100644
--- a/src/iqm/qiskit_iqm/fake_backends/__init__.py
+++ b/src/iqm/qiskit_iqm/fake_backends/__init__.py
@@ -15,4 +15,5 @@
 """
 
 from .fake_adonis import IQMFakeAdonis
+from .fake_apollo import IQMFakeApollo
 from .iqm_fake_backend import IQMErrorProfile, IQMFakeBackend
diff --git a/src/iqm/qiskit_iqm/fake_backends/fake_apollo.py b/src/iqm/qiskit_iqm/fake_backends/fake_apollo.py
new file mode 100644
index 000000000..216de336c
--- /dev/null
+++ b/src/iqm/qiskit_iqm/fake_backends/fake_apollo.py
@@ -0,0 +1,211 @@
+# Copyright 2022-2023 Qiskit on IQM developers
+#
+# 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.
+"""Fake (i.e. simulated) backend for IQM's 20-qubit Apollo architecture
+"""
+from iqm.iqm_client import QuantumArchitectureSpecification
+from iqm.qiskit_iqm.fake_backends.iqm_fake_backend import IQMErrorProfile, IQMFakeBackend
+
+
+def IQMFakeApollo() -> IQMFakeBackend:
+    """Return IQMFakeBackend instance representing IQM's Apollo architecture."""
+    architecture = QuantumArchitectureSpecification(
+        name="Apollo",
+        operations=["phased_rx", "cz", "measurement", "barrier"],
+        qubits=[
+            "QB1",
+            "QB2",
+            "QB3",
+            "QB4",
+            "QB5",
+            "QB6",
+            "QB7",
+            "QB8",
+            "QB9",
+            "QB10",
+            "QB11",
+            "QB12",
+            "QB13",
+            "QB14",
+            "QB15",
+            "QB16",
+            "QB17",
+            "QB18",
+            "QB19",
+            "QB20",
+        ],
+        qubit_connectivity=[
+            ["QB1", "QB2"],
+            ["QB1", "QB4"],
+            ["QB2", "QB5"],
+            ["QB3", "QB4"],
+            ["QB8", "QB3"],
+            ["QB4", "QB5"],
+            ["QB9", "QB4"],
+            ["QB5", "QB6"],
+            ["QB10", "QB5"],
+            ["QB6", "QB7"],
+            ["QB11", "QB6"],
+            ["QB12", "QB7"],
+            ["QB8", "QB9"],
+            ["QB8", "QB13"],
+            ["QB9", "QB10"],
+            ["QB9", "QB14"],
+            ["QB10", "QB11"],
+            ["QB10", "QB15"],
+            ["QB11", "QB12"],
+            ["QB16", "QB11"],
+            ["QB17", "QB12"],
+            ["QB13", "QB14"],
+            ["QB14", "QB15"],
+            ["QB18", "QB14"],
+            ["QB16", "QB15"],
+            ["QB19", "QB15"],
+            ["QB16", "QB17"],
+            ["QB16", "QB20"],
+            ["QB18", "QB19"],
+            ["QB19", "QB20"],
+        ],
+    )
+    # Note that these specs are ballpark numbers and don't correspond directly to a specific device
+    error_profile = IQMErrorProfile(
+        t1s={
+            "QB1": 49500.0,
+            "QB2": 49400.0,
+            "QB3": 41500.0,
+            "QB4": 50300.0,
+            "QB5": 49100.0,
+            "QB6": 48700.0,
+            "QB7": 48800.0,
+            "QB8": 49900.0,
+            "QB9": 48200.0,
+            "QB10": 49600.0,
+            "QB11": 42700.0,
+            "QB12": 50100.0,
+            "QB13": 47500.0,
+            "QB14": 48300.0,
+            "QB15": 39200.0,
+            "QB16": 49000.0,
+            "QB17": 49100.0,
+            "QB18": 49200.0,
+            "QB19": 41600.0,
+            "QB20": 41800.0,
+        },
+        t2s={
+            "QB1": 09100.0,
+            "QB2": 10100.0,
+            "QB3": 10900.0,
+            "QB4": 09600.0,
+            "QB5": 08900.0,
+            "QB6": 10200.0,
+            "QB7": 08500.0,
+            "QB8": 09000.0,
+            "QB9": 09300.0,
+            "QB10": 09800.0,
+            "QB11": 09400.0,
+            "QB12": 09900.0,
+            "QB13": 10000.0,
+            "QB14": 09000.0,
+            "QB15": 09500.0,
+            "QB16": 09700.0,
+            "QB17": 10100.0,
+            "QB18": 09200.0,
+            "QB19": 08200.0,
+            "QB20": 12000.0,
+        },
+        single_qubit_gate_depolarizing_error_parameters={
+            "phased_rx": {
+                "QB1": 0.00116,
+                "QB2": 0.00102,
+                "QB3": 0.00182,
+                "QB4": 0.00100,
+                "QB5": 0.00119,
+                "QB6": 0.00110,
+                "QB7": 0.00124,
+                "QB8": 0.00109,
+                "QB9": 0.00111,
+                "QB10": 0.00132,
+                "QB11": 0.00114,
+                "QB12": 0.00116,
+                "QB13": 0.00112,
+                "QB14": 0.00107,
+                "QB15": 0.00211,
+                "QB16": 0.00119,
+                "QB17": 0.00126,
+                "QB18": 0.00133,
+                "QB19": 0.00160,
+                "QB20": 0.00115,
+            }
+        },
+        two_qubit_gate_depolarizing_error_parameters={
+            "cz": {
+                ("QB1", "QB2"): 0.0120,
+                ("QB1", "QB4"): 0.0121,
+                ("QB2", "QB5"): 0.0102,
+                ("QB3", "QB4"): 0.0150,
+                ("QB8", "QB3"): 0.0103,
+                ("QB4", "QB5"): 0.0160,
+                ("QB9", "QB4"): 0.0117,
+                ("QB5", "QB6"): 0.0098,
+                ("QB10", "QB5"): 0.0210,
+                ("QB6", "QB7"): 0.0187,
+                ("QB11", "QB6"): 0.0135,
+                ("QB12", "QB7"): 0.0141,
+                ("QB8", "QB9"): 0.0192,
+                ("QB8", "QB13"): 0.0183,
+                ("QB9", "QB10"): 0.0133,
+                ("QB9", "QB14"): 0.0109,
+                ("QB10", "QB11"): 0.0134,
+                ("QB10", "QB15"): 0.0122,
+                ("QB11", "QB12"): 0.0100,
+                ("QB16", "QB11"): 0.0201,
+                ("QB17", "QB12"): 0.0180,
+                ("QB13", "QB14"): 0.0127,
+                ("QB14", "QB15"): 0.0142,
+                ("QB18", "QB14"): 0.0191,
+                ("QB16", "QB15"): 0.0146,
+                ("QB19", "QB15"): 0.0162,
+                ("QB16", "QB17"): 0.0133,
+                ("QB16", "QB20"): 0.0220,
+                ("QB18", "QB19"): 0.0190,
+                ("QB19", "QB20"): 0.0114,
+            }
+        },
+        single_qubit_gate_durations={"phased_rx": 42.0},
+        two_qubit_gate_durations={"cz": 130.0},
+        readout_errors={
+            "QB1": {"0": 0.051, "1": 0.052},
+            "QB2": {"0": 0.049, "1": 0.048},
+            "QB3": {"0": 0.057, "1": 0.049},
+            "QB4": {"0": 0.049, "1": 0.048},
+            "QB5": {"0": 0.050, "1": 0.051},
+            "QB6": {"0": 0.053, "1": 0.055},
+            "QB7": {"0": 0.051, "1": 0.052},
+            "QB8": {"0": 0.058, "1": 0.050},
+            "QB9": {"0": 0.052, "1": 0.049},
+            "QB10": {"0": 0.044, "1": 0.049},
+            "QB11": {"0": 0.056, "1": 0.049},
+            "QB12": {"0": 0.051, "1": 0.050},
+            "QB13": {"0": 0.053, "1": 0.051},
+            "QB14": {"0": 0.048, "1": 0.049},
+            "QB15": {"0": 0.046, "1": 0.049},
+            "QB16": {"0": 0.044, "1": 0.051},
+            "QB17": {"0": 0.048, "1": 0.048},
+            "QB18": {"0": 0.049, "1": 0.052},
+            "QB19": {"0": 0.050, "1": 0.051},
+            "QB20": {"0": 0.056, "1": 0.057},
+        },
+        name="sample-chip",
+    )
+
+    return IQMFakeBackend(architecture, error_profile)


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index 79c5d1623..ea29e59d5 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -50,3 +50,69 @@ def adonis_architecture_shuffled_names():
 @pytest.fixture
 def adonis_coupling_map():
     return {(0, 2), (2, 0), (1, 2), (2, 1), (2, 3), (3, 2), (2, 4), (4, 2)}
+
+
+@pytest.fixture
+def apollo_coupling_map():
+    return {
+        (0, 1),
+        (1, 0),
+        (0, 3),
+        (3, 0),
+        (1, 4),
+        (4, 1),
+        (2, 3),
+        (3, 2),
+        (7, 2),
+        (2, 7),
+        (3, 4),
+        (4, 3),
+        (8, 3),
+        (3, 8),
+        (4, 5),
+        (5, 4),
+        (9, 4),
+        (4, 9),
+        (5, 6),
+        (6, 5),
+        (10, 5),
+        (5, 10),
+        (11, 6),
+        (6, 11),
+        (7, 8),
+        (8, 7),
+        (7, 12),
+        (12, 7),
+        (8, 9),
+        (9, 8),
+        (8, 13),
+        (13, 8),
+        (9, 10),
+        (10, 9),
+        (9, 14),
+        (14, 9),
+        (10, 11),
+        (11, 10),
+        (15, 10),
+        (10, 15),
+        (16, 11),
+        (11, 16),
+        (12, 13),
+        (13, 12),
+        (13, 14),
+        (14, 13),
+        (17, 13),
+        (13, 17),
+        (15, 14),
+        (14, 15),
+        (18, 14),
+        (14, 18),
+        (15, 16),
+        (16, 15),
+        (15, 19),
+        (19, 15),
+        (17, 18),
+        (18, 17),
+        (18, 19),
+        (19, 18),
+    }
diff --git a/tests/fake_backends/test_fake_apollo.py b/tests/fake_backends/test_fake_apollo.py
new file mode 100644
index 000000000..06fdaacf1
--- /dev/null
+++ b/tests/fake_backends/test_fake_apollo.py
@@ -0,0 +1,34 @@
+# Copyright 2022-2023 Qiskit on IQM developers
+#
+# 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.
+
+"""Testing fake Apollo backend.
+"""
+from qiskit_aer.noise.noise_model import NoiseModel
+
+from iqm.qiskit_iqm import IQMFakeApollo
+
+
+def test_iqm_fake_apollo():
+    backend = IQMFakeApollo()
+    assert backend.num_qubits == 20
+
+
+def test_iqm_fake_apollo_connectivity(apollo_coupling_map):
+    backend = IQMFakeApollo()
+    assert set(backend.coupling_map.get_edges()) == apollo_coupling_map
+
+
+def test_iqm_fake_apollo_noise_model_instantiated():
+    backend = IQMFakeApollo()
+    assert isinstance(backend.noise_model, NoiseModel)