Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compatibility Update for Qibolab 0.2 #1083

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/source/tutorials/circuits.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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)
Expand Down
36 changes: 36 additions & 0 deletions src/qibolab/_core/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.")

Expand Down Expand Up @@ -98,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 self.qubits for q in circuit.wire_names):
circuit._wire_names = self.qubits[: circuit.nqubits]

sequence, measurement_map = self.compiler.compile(circuit, self.platform)

self.platform.connect()
Expand Down Expand Up @@ -137,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 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(
*(self.compiler.compile(circuit, self.platform) for circuit in circuits)
Expand Down
23 changes: 15 additions & 8 deletions src/qibolab/_core/compilers/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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])

Expand All @@ -111,14 +115,15 @@ 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)

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
Expand Down Expand Up @@ -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
Expand Down
49 changes: 49 additions & 0 deletions tests/test_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
27 changes: 22 additions & 5 deletions tests/test_compilers_default.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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)

Expand All @@ -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
Expand All @@ -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
Expand All @@ -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

Expand All @@ -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

Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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]
Expand Down Expand Up @@ -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()
Expand All @@ -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
Expand All @@ -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([])
Expand Down Expand Up @@ -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)
Expand Down
Loading