Skip to content

Commit

Permalink
Merge pull request #1079 from qiboteam/qibo_wire_patch
Browse files Browse the repository at this point in the history
Use of `wire_names`
  • Loading branch information
csookim authored Oct 31, 2024
2 parents 065aabb + 134efd2 commit 44461ef
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 32 deletions.
5 changes: 2 additions & 3 deletions doc/source/tutorials/compiler.rst
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,10 @@ The following example shows how to modify the compiler in order to execute a cir


# define a compiler rule that translates X to the pi-pulse
def x_rule(gate, platform):
def x_rule(qubits_ids, platform, parameters=None):
"""X gate applied with a single pi-pulse."""
qubit = gate.target_qubits[0]
sequence = PulseSequence()
sequence.add(platform.create_RX_pulse(qubit, start=0))
sequence.add(platform.create_RX_pulse(qubits_ids[1][0], start=0))
return sequence, {}


Expand Down
11 changes: 11 additions & 0 deletions src/qibolab/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ def execute_circuit(self, circuit, initial_state=None, nshots=1000):
"Hardware backend only supports circuits as initial states.",
)

# This should be done in qibo side
# 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)

if not self.platform.is_connected:
Expand Down Expand Up @@ -167,6 +172,12 @@ def execute_circuits(self, circuits, initial_states=None, nshots=1000):
"Hardware backend only supports circuits as initial states.",
)

# This should be done in qibo side
# 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
24 changes: 21 additions & 3 deletions src/qibolab/compilers/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,24 @@ def inner(func):
return inner

def _compile_gate(
self, gate, platform, sequence, virtual_z_phases, moment_start, delays
self,
gate,
platform,
sequence,
virtual_z_phases,
moment_start,
delays,
wire_names,
):
"""Adds a single gate to the pulse sequence."""
rule = self[gate.__class__]

# get local sequence and phases for the current gate
gate_sequence, gate_phases = rule(gate, platform)
qubits_ids = (
[wire_names[qubit] for qubit in gate.control_qubits],
[wire_names[qubit] for qubit in gate.target_qubits],
)
gate_sequence, gate_phases = rule(qubits_ids, platform, gate.parameters)

# update global pulse sequence
# determine the right start time based on the availability of the qubits involved
Expand Down Expand Up @@ -154,7 +166,13 @@ def compile(self, circuit, platform):
delays[qubit] += gate.delay
continue
gate_sequence, gate_phases = self._compile_gate(
gate, platform, sequence, virtual_z_phases, moment_start, delays
gate,
platform,
sequence,
virtual_z_phases,
moment_start,
delays,
circuit.wire_names,
)
for qubit in gate.qubits:
delays[qubit] = 0
Expand Down
46 changes: 22 additions & 24 deletions src/qibolab/compilers/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,52 +8,48 @@
from qibolab.pulses import PulseSequence


def identity_rule(gate, platform):
def identity_rule(qubits_ids, platform, parameters=None):
"""Identity gate skipped."""
return PulseSequence(), {}


def z_rule(gate, platform):
def z_rule(qubits_ids, platform, parameters=None):
"""Z gate applied virtually."""
qubit = list(platform.qubits)[gate.target_qubits[0]]
return PulseSequence(), {qubit: math.pi}
return PulseSequence(), {qubits_ids[1][0]: math.pi}


def rz_rule(gate, platform):
def rz_rule(qubits_ids, platform, parameters=None):
"""RZ gate applied virtually."""
qubit = list(platform.qubits)[gate.target_qubits[0]]
return PulseSequence(), {qubit: -gate.parameters[0]}
return PulseSequence(), {qubits_ids[1][0]: -parameters[0]}


def gpi2_rule(gate, platform):
def gpi2_rule(qubits_ids, platform, parameters=None):
"""Rule for GPI2."""
qubit = list(platform.qubits)[gate.target_qubits[0]]
theta = gate.parameters[0]
theta = parameters[0]
sequence = PulseSequence()
pulse = platform.create_RX90_pulse(qubit, start=0, relative_phase=theta)
pulse = platform.create_RX90_pulse(qubits_ids[1][0], start=0, relative_phase=theta)
sequence.add(pulse)
return sequence, {}


def gpi_rule(gate, platform):
def gpi_rule(qubits_ids, platform, parameters=None):
"""Rule for GPI."""
qubit = list(platform.qubits)[gate.target_qubits[0]]
theta = gate.parameters[0]
theta = parameters[0]
sequence = PulseSequence()
# the following definition has a global phase difference compare to
# to the matrix representation. See
# https://github.com/qiboteam/qibolab/pull/804#pullrequestreview-1890205509
# for more detail.
pulse = platform.create_RX_pulse(qubit, start=0, relative_phase=theta)
pulse = platform.create_RX_pulse(qubits_ids[1][0], start=0, relative_phase=theta)
sequence.add(pulse)
return sequence, {}


def u3_rule(gate, platform):
def u3_rule(qubits_ids, platform, parameters=None):
"""U3 applied as RZ-RX90-RZ-RX90-RZ."""
qubit = list(platform.qubits)[gate.target_qubits[0]]
qubit = qubits_ids[1][0]
# Transform gate to U3 and add pi/2-pulses
theta, phi, lam = gate.parameters
theta, phi, lam = parameters
# apply RZ(lam)
virtual_z_phases = {qubit: lam}
sequence = PulseSequence()
Expand All @@ -79,24 +75,26 @@ def u3_rule(gate, platform):
return sequence, virtual_z_phases


def cz_rule(gate, platform):
def cz_rule(qubits_ids, platform, parameters=None):
"""CZ applied as defined in the platform runcard.
Applying the CZ gate may involve sending pulses on qubits that the
gate is not directly acting on.
"""
return platform.create_CZ_pulse_sequence(gate.qubits)
qubits = qubits_ids[0] + qubits_ids[1]
return platform.create_CZ_pulse_sequence(qubits)


def cnot_rule(gate, platform):
def cnot_rule(qubits_ids, platform, parameters=None):
"""CNOT applied as defined in the platform runcard."""
return platform.create_CNOT_pulse_sequence(gate.qubits)
qubits = qubits_ids[0] + qubits_ids[1]
return platform.create_CNOT_pulse_sequence(qubits)


def measurement_rule(gate, platform):
def measurement_rule(qubits_ids, platform, parameters=None):
"""Measurement gate applied using the platform readout pulse."""
sequence = PulseSequence()
for qubit in gate.target_qubits:
for qubit in qubits_ids[1]:
MZ_pulse = platform.create_MZ_pulse(qubit, start=0)
sequence.add(MZ_pulse)
return sequence, {}
15 changes: 13 additions & 2 deletions tests/test_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@
from qibolab.backends import QibolabBackend


def generate_circuit_with_gate(nqubits, gate, **kwargs):
def generate_circuit_with_gate(nqubits, gate, names, **kwargs):
circuit = Circuit(nqubits)
circuit.add(gate(qubit, **kwargs) for qubit in range(nqubits))
circuit.add(gates.M(*range(nqubits)))
circuit._wire_names = names
return circuit


Expand Down Expand Up @@ -86,6 +87,7 @@ def test_execute_circuit_initial_state():
circuit = Circuit(1)
circuit.add(gates.GPI2(0, phi=0))
circuit.add(gates.M(0))
circuit._wire_names = [0]
with pytest.raises(ValueError):
backend.execute_circuit(circuit, initial_state=np.ones(2))

Expand All @@ -107,7 +109,7 @@ def test_execute_circuit_initial_state():
def test_execute_circuit(gate, kwargs):
backend = QibolabBackend("dummy")
nqubits = backend.platform.nqubits
circuit = generate_circuit_with_gate(nqubits, gate, **kwargs)
circuit = generate_circuit_with_gate(nqubits, gate, list(range(nqubits)), **kwargs)
result = backend.execute_circuit(circuit, nshots=100)


Expand All @@ -117,12 +119,14 @@ def test_measurement_samples():

circuit = Circuit(nqubits)
circuit.add(gates.M(*range(nqubits)))
circuit._wire_names = list(range(nqubits))
result = backend.execute_circuit(circuit, nshots=100)
assert result.samples().shape == (100, nqubits)
assert sum(result.frequencies().values()) == 100

circuit = Circuit(nqubits)
circuit.add(gates.M(0, 2))
circuit._wire_names = list(range(nqubits))
result = backend.execute_circuit(circuit, nshots=100)
assert result.samples().shape == (100, 2)
assert sum(result.frequencies().values()) == 100
Expand All @@ -135,6 +139,7 @@ def test_execute_circuits():
circuit = Circuit(3)
circuit.add(gates.GPI2(i, phi=np.pi / 2) for i in range(3))
circuit.add(gates.M(0, 1, 2))
circuit._wire_names = list(range(3))

results = backend.execute_circuits(
5 * [circuit], initial_states=initial_state_circuit, nshots=100
Expand All @@ -151,6 +156,7 @@ def test_multiple_measurements():
circuit = Circuit(4)
circuit.add(gates.GPI2(i, phi=np.pi / 2) for i in range(2))
circuit.add(gates.CZ(1, 2))
circuit._wire_names = list(range(4))
res0 = circuit.add(gates.M(0))
res1 = circuit.add(gates.M(3))
res2 = circuit.add(gates.M(1))
Expand All @@ -171,6 +177,7 @@ def dummy_string_qubit_names():
platform.pairs = {
(f"A{q0}", f"A{q1}"): pair for (q0, q1), pair in platform.pairs.items()
}
platform.wire_names = [f"A{q}" for q in range(platform.nqubits)]
return platform


Expand All @@ -182,6 +189,7 @@ def test_execute_circuit_str_qubit_names():
circuit.add(gates.GPI2(i, phi=np.pi / 2) for i in range(2))
circuit.add(gates.CZ(1, 2))
circuit.add(gates.M(0, 1))
circuit._wire_names = ["A0", "A1", "A2"]
result = backend.execute_circuit(circuit, nshots=20)
assert result.samples().shape == (20, 2)

Expand All @@ -195,6 +203,7 @@ def test_ground_state_probabilities_circuit(connected_backend):
nqubits = connected_backend.platform.nqubits
circuit = Circuit(nqubits)
circuit.add(gates.M(*range(nqubits)))
circuit._wire_names = list(range(nqubits))
result = connected_backend.execute_circuit(circuit, nshots=nshots)
freqs = result.frequencies(binary=False)
probs = [freqs[i] / nshots for i in range(2**nqubits)]
Expand All @@ -214,6 +223,7 @@ def test_excited_state_probabilities_circuit(connected_backend):
circuit = Circuit(nqubits)
circuit.add(gates.X(q) for q in range(nqubits))
circuit.add(gates.M(*range(nqubits)))
circuit._wire_names = list(range(nqubits))
result = connected_backend.execute_circuit(circuit, nshots=nshots)
freqs = result.frequencies(binary=False)
probs = [freqs[i] / nshots for i in range(2**nqubits)]
Expand All @@ -237,6 +247,7 @@ def test_superposition_for_all_qubits(connected_backend):
circuit = Circuit(nqubits)
circuit.add(gates.GPI2(q=q, phi=np.pi / 2))
circuit.add(gates.M(q))
circuit._wire_names = list(range(nqubits))
freqs = connected_backend.execute_circuit(circuit, nshots=nshots).frequencies(
binary=False
)
Expand Down
2 changes: 2 additions & 0 deletions tests/test_compilers_default.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ def test_u3_sim_agreement():
def compile_circuit(circuit, platform):
"""Compile a circuit to a pulse sequence."""
compiler = Compiler.default()
# Temporary fix: overwrite the wire names
circuit._wire_names = list(platform.qubits)
sequence, _ = compiler.compile(circuit, platform)
return sequence

Expand Down

0 comments on commit 44461ef

Please sign in to comment.