Repository: iqm-finland/qiskit-on-iqm
Pull request #156: Update Apollo's noise parameters to resemble Garnet's specs
Author: login: jungthapa
Created: 2025-03-09T18:17:13Z | Merged: 2025-03-18T14:33:46Z

This PR updates the noise parameters for Apollo to better resemble the specifications of Garnet. This ensures that simulations using Apollo's noise model are more accurate and reflective of Garnet.

Changes:

Updated noise parameters in fake_apollo.py to match Garnet's specifications -- calibration data used from 5.3.2025.



Diff:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 8db8b8be8..0f98aa44f 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -2,6 +2,12 @@
 Changelog
 =========
 
+Version 17.7
+============
+
+* Added Garnet backend so that it can be used in simulations.
+  `#156 <https://github.com/iqm-finland/qiskit-on-iqm/pull/156>`_
+
 Version 17.6
 ============
 
diff --git a/src/iqm/qiskit_iqm/fake_backends/fake_garnet.py b/src/iqm/qiskit_iqm/fake_backends/fake_garnet.py
new file mode 100644
index 000000000..93b0772e2
--- /dev/null
+++ b/src/iqm/qiskit_iqm/fake_backends/fake_garnet.py
@@ -0,0 +1,218 @@
+# 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 Garnet architecture
+"""
+from iqm.iqm_client import QuantumArchitectureSpecification
+from iqm.qiskit_iqm.fake_backends.iqm_fake_backend import IQMErrorProfile, IQMFakeBackend
+
+
+# pylint: disable=duplicate-code
+def IQMFakeGarnet() -> IQMFakeBackend:
+    """Return IQMFakeBackend instance representing IQM's Garnet architecture."""
+    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"],
+    ]
+    architecture = QuantumArchitectureSpecification(
+        name="Garnet",
+        operations={
+            "prx": [[q] for q in qubits],
+            "cz": list(qubit_connectivity),
+            "measure": [[q] for q in qubits],
+            "barrier": [],
+        },
+        qubits=qubits,
+        qubit_connectivity=qubit_connectivity,
+    )
+    error_profile = IQMErrorProfile(
+        t1s={
+            "QB1": 37741.0,
+            "QB2": 32584.0,
+            "QB3": 24468.0,
+            "QB4": 11555.0,
+            "QB5": 34257.0,
+            "QB6": 40051.0,
+            "QB7": 35708.0,
+            "QB8": 25686.0,
+            "QB9": 34113.0,
+            "QB10": 37391.0,
+            "QB11": 25809.0,
+            "QB12": 60725.0,
+            "QB13": 44802.0,
+            "QB14": 48137.0,
+            "QB15": 39052.0,
+            "QB16": 43968.0,
+            "QB17": 36670.0,
+            "QB18": 38151.0,
+            "QB19": 50012.0,
+            "QB20": 54911.0,
+        },
+        t2s={
+            "QB1": 09180.0,
+            "QB2": 10040.0,
+            "QB3": 10950.0,
+            "QB4": 09670.0,
+            "QB5": 08960.0,
+            "QB6": 10130.0,
+            "QB7": 08590.0,
+            "QB8": 09050.0,
+            "QB9": 09380.0,
+            "QB10": 09820.0,
+            "QB11": 09460.0,
+            "QB12": 09940.0,
+            "QB13": 10070.0,
+            "QB14": 09030.0,
+            "QB15": 09590.0,
+            "QB16": 09710.0,
+            "QB17": 10180.0,
+            "QB18": 09250.0,
+            "QB19": 08270.0,
+            "QB20": 11930.0,
+        },
+        single_qubit_gate_depolarizing_error_parameters={
+            "prx": {
+                "QB1": 0.00085,
+                "QB2": 0.00183,
+                "QB3": 0.00165,
+                "QB4": 0.00111,
+                "QB5": 0.00114,
+                "QB6": 0.00384,
+                "QB7": 0.00265,
+                "QB8": 0.00084,
+                "QB9": 0.00122,
+                "QB10": 0.00113,
+                "QB11": 0.00274,
+                "QB12": 0.00076,
+                "QB13": 0.00089,
+                "QB14": 0.00074,
+                "QB15": 0.00278,
+                "QB16": 0.00067,
+                "QB17": 0.00085,
+                "QB18": 0.00061,
+                "QB19": 0.00069,
+                "QB20": 0.00088,
+            }
+        },
+        two_qubit_gate_depolarizing_error_parameters={
+            "cz": {
+                ("QB1", "QB2"): 0.00578,
+                ("QB1", "QB4"): 0.00804,
+                ("QB2", "QB5"): 0.00749,
+                ("QB3", "QB4"): 0.00809,
+                ("QB8", "QB3"): 0.00599,
+                ("QB4", "QB5"): 0.00431,
+                ("QB9", "QB4"): 0.00650,
+                ("QB5", "QB6"): 0.00474,
+                ("QB10", "QB5"): 0.00339,
+                ("QB6", "QB7"): 0.00527,
+                ("QB11", "QB6"): 0.01401,
+                ("QB12", "QB7"): 0.00294,
+                ("QB8", "QB9"): 0.00399,
+                ("QB8", "QB13"): 0.00485,
+                ("QB9", "QB10"): 0.00638,
+                ("QB9", "QB14"): 0.00548,
+                ("QB10", "QB11"): 0.00682,
+                ("QB10", "QB15"): 0.00961,
+                ("QB11", "QB12"): 0.00899,
+                ("QB16", "QB11"): 0.00712,
+                ("QB17", "QB12"): 0.00407,
+                ("QB13", "QB14"): 0.00251,
+                ("QB14", "QB15"): 0.00506,
+                ("QB18", "QB14"): 0.00420,
+                ("QB16", "QB15"): 0.00771,
+                ("QB19", "QB15"): 0.00711,
+                ("QB16", "QB17"): 0.00643,
+                ("QB16", "QB20"): 0.00562,
+                ("QB18", "QB19"): 0.00507,
+                ("QB19", "QB20"): 0.00578,
+            }
+        },
+        single_qubit_gate_durations={"prx": 20.0},
+        two_qubit_gate_durations={"cz": 40.0},
+        readout_errors={
+            "QB1": {"0": 0.0255, "1": 0.0260},
+            "QB2": {"0": 0.0245, "1": 0.0240},
+            "QB3": {"0": 0.0285, "1": 0.0245},
+            "QB4": {"0": 0.0245, "1": 0.0240},
+            "QB5": {"0": 0.0250, "1": 0.0255},
+            "QB6": {"0": 0.0265, "1": 0.0275},
+            "QB7": {"0": 0.0255, "1": 0.0260},
+            "QB8": {"0": 0.0290, "1": 0.0250},
+            "QB9": {"0": 0.0260, "1": 0.0245},
+            "QB10": {"0": 0.0220, "1": 0.0245},
+            "QB11": {"0": 0.0280, "1": 0.0245},
+            "QB12": {"0": 0.0255, "1": 0.0250},
+            "QB13": {"0": 0.0265, "1": 0.0255},
+            "QB14": {"0": 0.0240, "1": 0.0245},
+            "QB15": {"0": 0.0230, "1": 0.0245},
+            "QB16": {"0": 0.0220, "1": 0.0255},
+            "QB17": {"0": 0.0240, "1": 0.0240},
+            "QB18": {"0": 0.0245, "1": 0.0260},
+            "QB19": {"0": 0.0250, "1": 0.0255},
+            "QB20": {"0": 0.0280, "1": 0.0285},
+        },
+        name="sample-chip",
+    )
+
+    return IQMFakeBackend(architecture, error_profile, name="IQMFakeGarnetBackend")
diff --git a/tests/fake_backends/test_fake_garnet.py b/tests/fake_backends/test_fake_garnet.py
new file mode 100644
index 000000000..d66bf5d1f
--- /dev/null
+++ b/tests/fake_backends/test_fake_garnet.py
@@ -0,0 +1,72 @@
+# 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 Garnet backend.
+"""
+from qiskit_aer.noise.noise_model import NoiseModel
+
+from iqm.qiskit_iqm.fake_backends.fake_garnet import IQMFakeGarnet
+from iqm.qiskit_iqm.iqm_backend import IQMTarget
+
+
+def test_iqm_fake_garnet():
+    backend = IQMFakeGarnet()
+    assert backend.num_qubits == 20
+    assert backend.name == 'IQMFakeGarnetBackend'
+
+
+def test_iqm_fake_garnet_connectivity():
+    # pylint: disable=duplicate-code
+    backend = IQMFakeGarnet()
+    coupling_map = {
+        (0, 1),
+        (0, 3),
+        (1, 4),
+        (2, 3),
+        (7, 2),
+        (3, 4),
+        (8, 3),
+        (4, 5),
+        (9, 4),
+        (5, 6),
+        (10, 5),
+        (11, 6),
+        (7, 8),
+        (7, 12),
+        (8, 9),
+        (8, 13),
+        (9, 10),
+        (9, 14),
+        (10, 11),
+        (15, 10),
+        (16, 11),
+        (12, 13),
+        (13, 14),
+        (17, 13),
+        (15, 14),
+        (18, 14),
+        (15, 16),
+        (15, 19),
+        (17, 18),
+        (18, 19),
+    }
+    assert isinstance(backend.target, IQMTarget)
+    assert set(backend.target.build_coupling_map()) == coupling_map
+    assert set(backend.coupling_map.get_edges()) == coupling_map
+    assert backend.target_with_resonators == backend.target
+
+
+def test_iqm_fake_garnet_noise_model_instantiated():
+    backend = IQMFakeGarnet()
+    assert isinstance(backend.noise_model, NoiseModel)


Reviews:
user: login: jkotilahti
body: The changes look good, but please add also a changelog entry for this.
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-03-13T09:24:04Z
user: login: jkotilahti
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-03-13T09:43:30Z
user: login: jkotilahti
body: Please add also `test_fake_garnet.py`. You can just copy `test_fake_apollo.py` and replace "apollo" with "garnet" everywhere.
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-03-13T14:38:57Z
user: login: jkotilahti
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-18T11:43:47Z
user: login: jkotilahti
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-18T14:01:18Z

Review comments:
diff_hunk: @@ -87,130 +87,130 @@ def IQMFakeApollo() -> IQMFakeBackend:
     # 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,
+            "QB1": 37741.0,
+            "QB2": 32584.0,
+            "QB3": 24468.0,
+            "QB4": 11555.0,
+            "QB5": 34257.0,
+            "QB6": 40051.0,
+            "QB7": 35708.0,
+            "QB8": 25686.0,
+            "QB9": 34113.0,
+            "QB10": 37391.0,
+            "QB11": 25809.0,
+            "QB12": 60725.0,
+            "QB13": 44802.0,
+            "QB14": 48137.0,
+            "QB15": 39052.0,
+            "QB16": 43968.0,
+            "QB17": 36670.0,
+            "QB18": 38151.0,
+            "QB19": 50012.0,
+            "QB20": 54911.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,
+            "QB1": 09180.0,
+            "QB2": 10040.0,
+            "QB3": 10950.0,
+            "QB4": 09670.0,
+            "QB5": 08960.0,
+            "QB6": 10130.0,
+            "QB7": 08590.0,
+            "QB8": 09050.0,
+            "QB9": 09380.0,
+            "QB10": 09820.0,
+            "QB11": 09460.0,
+            "QB12": 09940.0,
+            "QB13": 10070.0,
+            "QB14": 09030.0,
+            "QB15": 09590.0,
+            "QB16": 09710.0,
+            "QB17": 10180.0,
+            "QB18": 09250.0,
+            "QB19": 08270.0,
+            "QB20": 11930.0
path: src/iqm/qiskit_iqm/fake_backends/fake_apollo.py
user: login: jkotilahti
body: Need to also add comma at the end of this list, so that formatting check passes.
```suggestion
            "QB20": 11930.0,
```
created_at: 2025-03-13T09:43:23Z
author_association: MEMBER
diff_hunk: @@ -215,4 +215,4 @@ def IQMFakeApollo() -> IQMFakeBackend:
         name="sample-chip",
     )
 
-    return IQMFakeBackend(architecture, error_profile, name="IQMFakeApolloBackend")
+    return IQMFakeBackend(architecture, error_profile, name="IQMFakeApolloBackend")
path: src/iqm/qiskit_iqm/fake_backends/fake_apollo.py
user: login: jkotilahti
body: The last (empty) line was accidentally removed, it needs to be added back for the formatting checks to pass.
```suggestion
    return IQMFakeBackend(architecture, error_profile, name="IQMFakeApolloBackend")

```
created_at: 2025-03-13T14:31:23Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,218 @@
+# 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 Garnet architecture
+"""
+from iqm.iqm_client import QuantumArchitectureSpecification
+from iqm.qiskit_iqm.fake_backends.iqm_fake_backend import IQMErrorProfile, IQMFakeBackend
+
+
+def IQMFakeGarnet() -> IQMFakeBackend:
+    """Return IQMFakeBackend instance representing IQM's Garnet architecture."""
+    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"],
+    ]
+    architecture = QuantumArchitectureSpecification(
+        name="Garnet",
+        operations={
+            "prx": [[q] for q in qubits],
+            "cz": list(qubit_connectivity),
+            "measure": [[q] for q in qubits],
+            "barrier": [],
+        },
+        qubits=qubits,
+        qubit_connectivity=qubit_connectivity,
+    )
+    # Note that these specs are ballpark numbers and don't correspond directly to a specific device
path: src/iqm/qiskit_iqm/fake_backends/fake_garnet.py
user: login: jkotilahti
body: This comment was valid for FakeApollo, but should be removed from FakeGarnet.
```suggestion
```
created_at: 2025-03-13T14:33:45Z
author_association: MEMBER
diff_hunk: @@ -2,6 +2,11 @@
 Changelog
 =========
 
+Version 17.6
+============
+
+* Added Garnet backend so that it can be used in simulations. 
path: CHANGELOG.rst
user: login: jkotilahti
body: Not very important, but good to add also link to the pull request, similar to other changelog entries.
```suggestion
* Added Garnet backend so that it can be used in simulations.
  `#156 <https://github.com/iqm-finland/qiskit-on-iqm/pull/156>`_
```
created_at: 2025-03-13T14:35:46Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,71 @@
+# 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 Garnet backend.
+"""
+from qiskit_aer.noise.noise_model import NoiseModel
+
+from iqm.qiskit_iqm.fake_backends.fake_garnet import IQMFakeGarnet
+from iqm.qiskit_iqm.iqm_backend import IQMTarget
+
+
+def test_iqm_fake_garnet():
+    backend = IQMFakeGarnet()
+    assert backend.num_qubits == 20
+    assert backend.name == 'IQMFakeGarnetBackend'
+
+
+def test_iqm_fake_garnet_connectivity():
path: tests/fake_backends/test_fake_garnet.py
user: login: jkotilahti
body: Looks like `pylint` didn't like the copy-pasted code. In the future we want to change things so that this kind of copy-paste is not needed for fake backends, but for now you can just disable this pylint check:
```suggestion
def test_iqm_fake_garnet_connectivity():
    # pylint: disable=duplicate-code
```
This should make the CI pipeline pass, and then I will approve.
created_at: 2025-03-18T11:43:47Z
author_association: MEMBER

PR comments:
user: login: jungthapa
created_at: 2025-03-13T10:58:41Z
body: @jkotilahti Thanks for your review. One generic question: Are we supposed to make fake_garnet.py script or is update on fake_apollo.py fine? Because Apollo is a family of QPU and Garnet is a specific one from that family. What should the standard be going forward?  
author_association: CONTRIBUTOR
user: login: jkotilahti
created_at: 2025-03-13T12:33:37Z
body: The idea of IQMFakeApollo is that it is "somewhat realistic for some Apollo chip", but it doesn't need to correspond exactly to any specific device. I think using Garnet values for it is fine, but also keeping the old values would be fine.

If there is a need to have a fake backend that really corresponds closely to a specific device like Garnet, then for example IQMFakeGarnet could be added. I cannot say whether that is needed or not, you probably have a better idea about that.
author_association: MEMBER
user: login: jungthapa
created_at: 2025-03-13T12:57:48Z
body: Ok. I would then actually opt for adding IQMFakeGarnet. Let me update the PR asap. 
author_association: CONTRIBUTOR
user: login: jungthapa
created_at: 2025-03-13T14:02:01Z
body: Oops forgot to update changelog
author_association: CONTRIBUTOR
user: login: jungthapa
created_at: 2025-03-18T12:26:50Z
body: Oops. something failed in CI. Let me check.
author_association: CONTRIBUTOR
user: login: jungthapa
created_at: 2025-03-18T12:45:20Z
body: hmm CI is not passing because of similar lines. But this change is something you suggested and I applied it. I am a bit confused. 
author_association: CONTRIBUTOR
user: login: jkotilahti
created_at: 2025-03-18T12:51:50Z
body: Sorry, it looks like my local testing didn't detect all the places where that failure happens. I think you need to add the `# pylint: disable=duplicate-code` also at least to IQMFakeGarnet. 

You can run `tox --recreate` locally to test that the checks pass. (I ran just `tox` initially, but it falsely passed due to caching, using the `--recreate` option seems necessary to properly check that everything passes.)
author_association: MEMBER