From c8200faec861eadfb65d764fb2258a9214c3569b Mon Sep 17 00:00:00 2001 From: Akinori Machino <@amachino> Date: Fri, 7 Feb 2025 16:52:39 +0900 Subject: [PATCH] feat: Add properties to nodes and edges in LatticeGraph and implement CollisionChecker for collision detection --- src/qubex/backend/lattice_graph.py | 16 +- src/qubex/collision/__init__.py | 0 src/qubex/collision/collision_checker.py | 214 +++++++++++++++++++++++ 3 files changed, 224 insertions(+), 6 deletions(-) create mode 100644 src/qubex/collision/__init__.py create mode 100644 src/qubex/collision/collision_checker.py diff --git a/src/qubex/backend/lattice_graph.py b/src/qubex/backend/lattice_graph.py index 79ec0992..766c1d5f 100644 --- a/src/qubex/backend/lattice_graph.py +++ b/src/qubex/backend/lattice_graph.py @@ -27,13 +27,14 @@ class QubitNode(TypedDict): position: tuple[float, float] mux_id: int index_in_mux: int + properties: dict[str, float] class QubitEdge(TypedDict): id: tuple[int, int] label: str - weight: float position: tuple[tuple[float, float], tuple[float, float], tuple[float, float]] + properties: dict[str, float] class ResonatorNode(TypedDict): @@ -41,6 +42,7 @@ class ResonatorNode(TypedDict): label: str coordinates: tuple[int, int] position: tuple[float, float] + properties: dict[str, float] class MuxNode(TypedDict): @@ -48,6 +50,7 @@ class MuxNode(TypedDict): label: str coordinates: tuple[int, int] position: tuple[float, float] + properties: dict[str, float] class LatticeGraph: @@ -155,7 +158,7 @@ def qubit_undirected_graph( return self.qubit_graph.to_undirected(as_view=True) @cached_property - def qubit_undirected_edges( + def qubit_links( self, ) -> dict[tuple[int, int], QubitEdge]: return { @@ -185,6 +188,7 @@ def _init_qubit_graph(self): "position": (x, y), "mux_id": idx_m, "index_in_mux": idx_qm, + "properties": {}, } ) nx.relabel_nodes( @@ -199,7 +203,6 @@ def _init_qubit_graph(self): { "id": (id0, id1), "label": f"{node0['label']}-{node1['label']}", - "weight": 1.0, "position": ( node0["position"], ( @@ -208,6 +211,7 @@ def _init_qubit_graph(self): ), node1["position"], ), + "properties": {}, }, ) @@ -220,6 +224,7 @@ def _init_resonator_graph(self): "label": f"{PREFIX_RESONATOR}{id:0{self.resonator_max_digit}d}", "coordinates": data["coordinates"], "position": data["position"], + "properties": data["properties"], } ) @@ -235,6 +240,7 @@ def _init_mux_graph(self): "label": f"{PREFIX_MUX}{idx:0{self.mux_max_digit}d}", "coordinates": (x, y), "position": (x * 2 + 0.5, y * 2 + 0.5), + "properties": {}, } ) nx.relabel_nodes( @@ -603,9 +609,7 @@ def _create_qubit_edge_trace( hovertexts: dict | None = None, ) -> list[go.Scatter]: if values is None: - values = { - edge["label"]: edge["weight"] for edge in self.qubit_edges.values() - } + values = {edge["label"]: 1.0 for edge in self.qubit_edges.values()} values = { key: value diff --git a/src/qubex/collision/__init__.py b/src/qubex/collision/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/qubex/collision/collision_checker.py b/src/qubex/collision/collision_checker.py new file mode 100644 index 00000000..107cc1ec --- /dev/null +++ b/src/qubex/collision/collision_checker.py @@ -0,0 +1,214 @@ +from __future__ import annotations + +from typing import Literal + +import numpy as np + +from ..backend.config_loader import ConfigLoader + +CollisionType = Literal[ + "Type0A", + "Type0B", + "Type1A", + "Type1B", + "Type1C", + "Type2A", + "Type2B", + "Type3A", + "Type3B", + "Type7", + "Type8", + "Type9", +] + +CONDITIONS = { + "max_frequency": 9.5, + "min_frequency": 6.5, + "max_detuning": 1.3, + "min_t1": 3e3, + "min_t2": 3e3, + "b1": 0.2, + "b2": 0.75, +} + +DEFAULTS = { + "t1": 3e3, + "t2_echo": 3e3, + "coupling": 0.008, +} + + +class CollisionChecker: + def __init__( + self, + chip_id: str, + params_dir: str | None = None, + conditions: dict | None = None, + ): + if params_dir is None: + config_loader = ConfigLoader() + else: + config_loader = ConfigLoader(params_dir=params_dir) + + experiment_system = config_loader.get_experiment_system(chip_id) + self.graph = experiment_system.quantum_system._graph + props = config_loader._props_dict[chip_id] + + for node in self.graph.qubit_nodes.values(): + label = node["label"] + node["properties"] = { + "frequency": props["qubit_frequency"].get(label), + "anharmonicity": props["anharmonicity"].get(label), + "t1": props["t1"].get(label), + "t2_echo": props["t2_echo"].get(label), + } + + for edge in self.graph.qubit_edges.values(): + label = edge["label"] + edge["properties"] = { + "coupling": props["qubit_qubit_coupling_strength"].get(label), + } + + self.conditions = CONDITIONS + if conditions is not None: + self.conditions.update(conditions) + + def get_value( + self, + target: int | tuple[int, int], + property: str, + default: float = np.nan, + ) -> float: + if isinstance(target, int): + value = self.graph.qubit_nodes[target]["properties"][property] + if value is None or np.isnan(value): + return default + else: + return value + elif isinstance(target, tuple): + value = self.graph.qubit_edges[target]["properties"][property] + if value is None or np.isnan(value): + return default + else: + return value + else: + raise ValueError("Invalid target type.") + + def check( + self, + *collision: CollisionType, + ) -> dict: + result = {} + for type in collision: + result[type] = getattr(self, f"check_{type.lower()}")() + + self.collisions = result + return result + + def check_type0a( + self, + min_frequency: float | None = None, + max_frequency: float | None = None, + min_t1: float | None = None, + min_t2: float | None = None, + ) -> dict: + """ + conditions: + (1) qubit unmeasured + (2) qubit frequency out of range + (3) qubit t1, t2 is too short + """ + result = {} + for i, data in self.graph.qubit_nodes.items(): + flag = False + label = data["label"] + log = [] + f = self.get_value(i, "frequency", np.nan) + f_min = min_frequency or self.conditions["min_frequency"] + f_max = max_frequency or self.conditions["max_frequency"] + t1 = self.get_value(i, "t1", DEFAULTS["t1"]) + t1_min = min_t1 or self.conditions["min_t1"] + t2 = self.get_value(i, "t2_echo", DEFAULTS["t2_echo"]) + t2_min = min_t2 or self.conditions["min_t2"] + if np.isnan(f): + flag = True + log.append(f"Frequency of {label} is not defined.") + if f < f_min: + flag = True + log.append( + f"Frequency of {label} ({f:.3f} GHz) is lower than {f_min:.3f} GHz." + ) + if f > f_max: + flag = True + log.append( + f"Frequency of {label} ({f:.3f} GHz) is higher than {f_max:.3f} GHz." + ) + if t1 < t1_min: + flag = True + log.append( + f"T1 of {label} ({t1 * 1e-3:.1f} μs) is lower than {t1_min * 1e-3:.1f} μs." + ) + if t2 < t2_min: + flag = True + log.append( + f"T2 of {label} ({t2 * 1e-3:.1f} μs) is lower than {t2_min * 1e-3:.1f} μs." + ) + + result[label] = { + "collision": flag, + "log": log, + } + + result = dict(sorted(result.items())) + return result + + def check_type0b( + self, + max_detuning: float | None = None, + ): + """ + conditions: + (1) ge(i)-ge(j) is too far to implement the fast CR(i>j) or CR(j>i) + """ + result = {} + for (i, j), data in self.graph.qubit_links.items(): + flag = False + label = data["label"] + log = [] + f_i = self.get_value(i, "frequency") + f_j = self.get_value(j, "frequency") + f_diff = abs(f_i - f_j) + f_diff_max = max_detuning or self.conditions["max_detuning"] + if f_diff > f_diff_max: + flag = True + log.append( + f"Detuning of {label} ({f_diff:.3f} GHz) is higher than {f_diff_max:.3f} GHz." + ) + + result[label] = { + "collision": flag, + "log": log, + } + + result = dict(sorted(result.items())) + return result + + def check_type1a(self): ... + + def check_type1b(self): ... + + def check_type1c(self): ... + + def check_type2a(self): ... + + def check_type2b(self): ... + + def check_type3a(self): ... + + def check_type3b(self): ... + + def check_type7(self): ... + + def check_type8(self): ... + + def check_type9(self): ...