From f9dfa0d5b26522e6e91aece28c2714e909482682 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Tue, 29 Oct 2024 10:47:38 +0400 Subject: [PATCH 1/5] feat: add qubits connectivity natives in backends.py --- src/qibolab/_core/backends.py | 27 +++++++++++++++++++ tests/test_backends.py | 49 +++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/src/qibolab/_core/backends.py b/src/qibolab/_core/backends.py index 79fc32990..96eef1750 100644 --- a/src/qibolab/_core/backends.py +++ b/src/qibolab/_core/backends.py @@ -8,6 +8,7 @@ from qibolab._version import __version__ as qibolab_version from .compilers import Compiler +from .identifier import QubitId, QubitPairId from .platform import Platform, create_platform from .platform.load import available_platforms @@ -48,6 +49,32 @@ def __init__(self, platform): } self.compiler = Compiler.default() + @property + def qubits(self) -> list[QubitId]: + """Returns the qubits in the platform.""" + return list(self.platform.qubits) + + @property + def connectivity(self) -> list[QubitPairId]: + """Returns the list of connected qubits.""" + return self.platform.pairs + + @property + def natives(self) -> list[str]: + """Returns the list of native gates supported by the platform.""" + compiler = Compiler.default() + natives = [g.__name__ for g in list(compiler.rules)] + calibrated = self.platform.natives.two_qubit + + check_2q = ["CZ", "CNOT"] + for gate in check_2q: + if gate in natives and all( + getattr(calibrated[p], gate) is None for p in self.connectivity + ): + natives.remove(gate) + + return natives + def apply_gate(self, gate, state, nqubits): # pragma: no cover raise_error(NotImplementedError, "Qibolab cannot apply gates directly.") diff --git a/tests/test_backends.py b/tests/test_backends.py index 0165efba9..f932481ae 100644 --- a/tests/test_backends.py +++ b/tests/test_backends.py @@ -24,6 +24,55 @@ def connected_backend(connected_platform): yield QibolabBackend(connected_platform) +def test_qubits(): + backend = QibolabBackend("dummy") + assert isinstance(backend.qubits, list) + assert set(backend.qubits) == {0, 1, 2, 3, 4} + + +def test_connectivity(): + backend = QibolabBackend("dummy") + assert isinstance(backend.connectivity, list) + assert set(backend.connectivity) == {(0, 2), (1, 2), (2, 3), (2, 4)} + + +def test_natives(): + backend = QibolabBackend("dummy") + assert isinstance(backend.natives, list) + assert set(backend.natives) == { + "I", + "Z", + "RZ", + "CZ", + "CNOT", + "GPI2", + "GPI", + "M", + "Align", + } + + +def test_natives_no_cz_cnot(): + platform = create_platform("dummy") + backend = QibolabBackend(platform) + assert set(backend.natives) == { + "I", + "Z", + "RZ", + "CZ", + "CNOT", + "GPI2", + "GPI", + "M", + "Align", + } + + for gate in ["CZ", "CNOT"]: + for p in platform.pairs: + platform.natives.two_qubit[p].__dict__[gate] = None + assert gate not in set(backend.natives) + + def test_execute_circuit_initial_state(): backend = QibolabBackend("dummy") circuit = Circuit(1) From e0484f4ee46c569e98fe8279e8e2d3d90894aff6 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Tue, 29 Oct 2024 10:56:57 +0400 Subject: [PATCH 2/5] feat: add temp fix --- src/qibolab/_core/backends.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/qibolab/_core/backends.py b/src/qibolab/_core/backends.py index 96eef1750..f1729ff10 100644 --- a/src/qibolab/_core/backends.py +++ b/src/qibolab/_core/backends.py @@ -125,6 +125,10 @@ def execute_circuit(self, circuit, initial_state=None, nshots=1000): "Hardware backend only supports circuits as initial states.", ) + # Temporary fix: overwrite the wire names + if not all(q in circuit.wire_names for q in self.qubits): + circuit._wire_names = self.qubits + sequence, measurement_map = self.compiler.compile(circuit, self.platform) self.platform.connect() @@ -164,6 +168,11 @@ def execute_circuits(self, circuits, initial_states=None, nshots=1000): "Hardware backend only supports circuits as initial states.", ) + # Temporary fix: overwrite the wire names + for circuit in circuits: + if not all(q in circuit.wire_names for q in self.qubits): + circuit._wire_names = self.qubits + # TODO: Maybe these loops can be parallelized sequences, measurement_maps = zip( *(self.compiler.compile(circuit, self.platform) for circuit in circuits) From fdef376e4f8b1694707ca8432bb2269daa410390 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Tue, 29 Oct 2024 11:35:28 +0400 Subject: [PATCH 3/5] feat: use wire names --- src/qibolab/_core/compilers/compiler.py | 23 +++++++++++++-------- tests/test_compilers_default.py | 27 ++++++++++++++++++++----- 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/qibolab/_core/compilers/compiler.py b/src/qibolab/_core/compilers/compiler.py index 14cf7de6a..ffbda82c8 100644 --- a/src/qibolab/_core/compilers/compiler.py +++ b/src/qibolab/_core/compilers/compiler.py @@ -69,7 +69,9 @@ def inner(func: Rule) -> Rule: return inner - def get_sequence(self, gate: gates.Gate, platform: Platform) -> PulseSequence: + def get_sequence( + self, gate: gates.Gate, platform: Platform, wire_names: list[QubitId] + ) -> PulseSequence: """Get pulse sequence implementing the given gate. The sequence is obtained using the registered rules. @@ -82,25 +84,27 @@ def get_sequence(self, gate: gates.Gate, platform: Platform) -> PulseSequence: rule = self.rules[type(gate)] natives = platform.natives + qubits_ids = [wire_names[q] for q in gate.qubits] + target_qubits_ids = [wire_names[q] for q in gate.target_qubits] if isinstance(gate, (gates.M)): - qubits = [natives.single_qubit[platform.qubit(q)[0]] for q in gate.qubits] + qubits = [natives.single_qubit[platform.qubit(q)[0]] for q in qubits_ids] return rule(gate, qubits) if isinstance(gate, (gates.Align)): - qubits = [platform.qubit(q)[1] for q in gate.qubits] + qubits = [platform.qubit(q)[1] for q in qubits_ids] return rule(gate, qubits) if isinstance(gate, (gates.Z, gates.RZ)): - qubit = platform.qubit(gate.target_qubits[0])[1] + qubit = platform.qubit(target_qubits_ids[0])[1] return rule(gate, qubit) if len(gate.qubits) == 1: - qubit = platform.qubit(gate.target_qubits[0])[0] + qubit = platform.qubit(target_qubits_ids[0])[0] return rule(gate, natives.single_qubit[qubit]) if len(gate.qubits) == 2: - pair = tuple(platform.qubit(q)[0] for q in gate.qubits) + pair = tuple(platform.qubit(q)[0] for q in qubits_ids) assert len(pair) == 2 return rule(gate, natives.two_qubit[pair]) @@ -111,6 +115,7 @@ def _compile_gate( gate: gates.Gate, platform: Platform, channel_clock: defaultdict[ChannelId, float], + wire_names: list[QubitId], ) -> PulseSequence: def qubit_clock(el: QubitId): return max(channel_clock[ch] for ch in platform.qubits[el].channels) @@ -118,7 +123,7 @@ def qubit_clock(el: QubitId): def coupler_clock(el: QubitId): return max(channel_clock[ch] for ch in platform.couplers[el].channels) - gate_seq = self.get_sequence(gate, platform) + gate_seq = self.get_sequence(gate, platform, wire_names) # qubits receiving pulses qubits = { q @@ -183,7 +188,9 @@ def compile( # process circuit gates for moment in circuit.queue.moments: for gate in {x for x in moment if x is not None}: - gate_seq = self._compile_gate(gate, platform, channel_clock) + gate_seq = self._compile_gate( + gate, platform, channel_clock, circuit.wire_names + ) # register readout sequences to ``measurement_map`` so that we can # properly map acquisition results to measurement gates diff --git a/tests/test_compilers_default.py b/tests/test_compilers_default.py index 9332f5799..bf74f7b6e 100644 --- a/tests/test_compilers_default.py +++ b/tests/test_compilers_default.py @@ -14,8 +14,9 @@ from qibolab._core.sequence import PulseSequence -def generate_circuit_with_gate(nqubits: int, gate, *params, **kwargs): +def generate_circuit_with_gate(nqubits: int, wire_names, gate, *params, **kwargs): circuit = Circuit(nqubits) + circuit._wire_names = wire_names circuit.add(gate(q, *params, **kwargs) for q in range(nqubits)) circuit.add(gates.M(*range(nqubits))) return circuit @@ -52,7 +53,7 @@ def compile_circuit(circuit: Circuit, platform: Platform) -> PulseSequence: ) def test_compile(platform: Platform, gateargs): nqubits = platform.nqubits - circuit = generate_circuit_with_gate(nqubits, *gateargs) + circuit = generate_circuit_with_gate(nqubits, list(range(nqubits)), *gateargs) sequence = compile_circuit(circuit, platform) assert len(sequence.channels) == nqubits * int(gateargs[0] != gates.I) + nqubits * 1 @@ -62,6 +63,7 @@ def test_compile_two_gates(platform: Platform): circuit.add(gates.GPI2(0, phi=0.1)) circuit.add(gates.GPI(0, 0.2)) circuit.add(gates.M(0)) + circuit._wire_names = list(platform.qubits)[:1] sequence = compile_circuit(circuit, platform) @@ -74,8 +76,9 @@ def test_compile_two_gates(platform: Platform): def test_measurement(platform: Platform): nqubits = platform.nqubits circuit = Circuit(nqubits) - qubits = [qubit for qubit in range(nqubits)] + qubits = list(range(nqubits)) circuit.add(gates.M(*qubits)) + circuit._wire_names = list(platform.qubits)[:nqubits] sequence = compile_circuit(circuit, platform) assert len(sequence.channels) == 1 * nqubits @@ -86,6 +89,7 @@ def test_rz_to_sequence(platform: Platform): circuit = Circuit(1) circuit.add(gates.RZ(0, theta=0.2)) circuit.add(gates.Z(0)) + circuit._wire_names = list(platform.qubits)[:1] sequence = compile_circuit(circuit, platform) assert len(sequence.channels) == 1 assert len(sequence) == 2 @@ -96,6 +100,7 @@ def test_gpi_to_sequence(platform: Platform): circuit = Circuit(1) circuit.add(gates.GPI(0, phi=0.2)) + circuit._wire_names = list(platform.qubits)[:1] sequence = compile_circuit(circuit, platform) assert len(sequence.channels) == 1 @@ -109,6 +114,7 @@ def test_gpi2_to_sequence(platform: Platform): circuit = Circuit(1) circuit.add(gates.GPI2(0, phi=0.2)) + circuit._wire_names = list(platform.qubits)[:1] sequence = compile_circuit(circuit, platform) assert len(sequence.channels) == 1 @@ -124,6 +130,7 @@ def test_cz_to_sequence(): circuit = Circuit(3) circuit.add(gates.CZ(1, 2)) + circuit._wire_names = list(platform.qubits)[:3] sequence = compile_circuit(circuit, platform) test_sequence = natives.two_qubit[(2, 1)].CZ.create_sequence() @@ -136,6 +143,7 @@ def test_cnot_to_sequence(): circuit = Circuit(4) circuit.add(gates.CNOT(2, 3)) + circuit._wire_names = list(platform.qubits)[:4] sequence = compile_circuit(circuit, platform) test_sequence = natives.two_qubit[(2, 3)].CNOT.create_sequence() @@ -149,6 +157,7 @@ def test_add_measurement_to_sequence(platform: Platform): circuit.add(gates.GPI2(0, 0.1)) circuit.add(gates.GPI2(0, 0.2)) circuit.add(gates.M(0)) + circuit._wire_names = list(platform.qubits)[:1] sequence = compile_circuit(circuit, platform) qubit = platform.qubits[0] @@ -181,6 +190,7 @@ def test_align_delay_measurement(platform: Platform, delay): circuit = Circuit(1) circuit.add(gates.Align(0, delay=delay)) circuit.add(gates.M(0)) + circuit._wire_names = list(platform.qubits)[:1] sequence = compile_circuit(circuit, platform) target_sequence = PulseSequence() @@ -197,6 +207,7 @@ def test_align_multiqubit(platform: Platform): circuit.add(gates.GPI2(main, phi=0.2)) circuit.add(gates.CZ(main, coupled)) circuit.add(gates.M(main, coupled)) + circuit._wire_names = list(platform.qubits)[:3] sequence = compile_circuit(circuit, platform) qubits = platform.qubits @@ -219,6 +230,7 @@ def test_inactive_qubits(platform: Platform, joint: bool): else: circuit.add(gates.M(main)) circuit.add(gates.M(coupled)) + circuit._wire_names = list(platform.qubits)[:2] natives = platform.natives.two_qubit[(main, coupled)] = TwoQubitNatives( CZ=Native([]) @@ -275,13 +287,18 @@ def test_joint_split_equivalence(platform: Platform): joint = Circuit(3) joint.add(gates.M(0, 2)) - joint_seq = compile_circuit(circuit + joint, platform) + # TODO: bug in circuit addition, the wire names are not updated + cj = circuit + joint + cj._wire_names = list(platform.qubits)[:3] + joint_seq = compile_circuit(cj, platform) split = Circuit(3) split.add(gates.M(0)) split.add(gates.M(2)) - split_seq = compile_circuit(circuit + split, platform) + cs = circuit + split + cs._wire_names = list(platform.qubits)[:3] + split_seq = compile_circuit(cs, platform) # the inter-channel sorting is unreliable, and mostly irrelevant (unless align # instructions are involved, which is not the case) From f9c134b1fed002a9d36154a2ae427f63db84f5e0 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Tue, 29 Oct 2024 11:47:28 +0400 Subject: [PATCH 4/5] fix: update test docs --- doc/source/tutorials/circuits.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/source/tutorials/circuits.rst b/doc/source/tutorials/circuits.rst index 7e758aeac..a7fc0f4f1 100644 --- a/doc/source/tutorials/circuits.rst +++ b/doc/source/tutorials/circuits.rst @@ -22,6 +22,7 @@ circuits definition that we leave to the `Qibo # attach Hadamard gate and a measurement circuit.add(gates.GPI2(0, phi=np.pi / 2)) circuit.add(gates.M(0)) + circuit._wire_names = [0] # execute on quantum hardware qibo.set_backend("qibolab", platform="dummy") @@ -82,6 +83,7 @@ results: # attach Rotation on X-Pauli with angle = 0 circuit.add(gates.GPI2(0, phi=0)) circuit.add(gates.M(0)) + circuit._wire_names = [0] # define range of angles from [0, 2pi] exp_angles = np.arange(0, 2 * np.pi, np.pi / 16) From b486a8f7204a322926a511bd0a20b63757ef8dc7 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Thu, 31 Oct 2024 14:27:59 +0400 Subject: [PATCH 5/5] fix: update condition in temp fix --- src/qibolab/_core/backends.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/qibolab/_core/backends.py b/src/qibolab/_core/backends.py index f1729ff10..380b4b490 100644 --- a/src/qibolab/_core/backends.py +++ b/src/qibolab/_core/backends.py @@ -126,8 +126,8 @@ def execute_circuit(self, circuit, initial_state=None, nshots=1000): ) # Temporary fix: overwrite the wire names - if not all(q in circuit.wire_names for q in self.qubits): - circuit._wire_names = self.qubits + if not all(q in self.qubits for q in circuit.wire_names): + circuit._wire_names = self.qubits[: circuit.nqubits] sequence, measurement_map = self.compiler.compile(circuit, self.platform) @@ -170,8 +170,8 @@ def execute_circuits(self, circuits, initial_states=None, nshots=1000): # Temporary fix: overwrite the wire names for circuit in circuits: - if not all(q in circuit.wire_names for q in self.qubits): - circuit._wire_names = self.qubits + if not all(q in self.qubits for q in circuit.wire_names): + circuit._wire_names = self.qubits[: circuit.nqubits] # TODO: Maybe these loops can be parallelized sequences, measurement_maps = zip(