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

Native gates directly defined by the platform #1077

Merged
merged 18 commits into from
Feb 14, 2025
Merged
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Merge branch 'fix_backend' into native_gates
  • Loading branch information
Edoardo-Pedicillo committed Feb 11, 2025
commit b433c46479eb71ef004513612ba02322bcaff25b
82 changes: 27 additions & 55 deletions src/qibocal/auto/transpile.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,17 @@
from typing import Optional

from qibo import Circuit, gates
from qibo.backends import construct_backend, get_backend
from qibo.backends import Backend
from qibo.transpiler.pipeline import Passes
from qibo.transpiler.unroller import NativeGates, Unroller
from qibolab import MetaBackend
from qibolab.platform import Platform
from qibolab.platform.platform import PulseSequence
from qibolab.pulses import PulseSequence
from qibolab.qubits import QubitId


def _get_platforms():
"""Qibolab platforms."""
try:
platforms = list(MetaBackend().list_available())
except RuntimeError:
platforms = []

return platforms + ["dummy"]


AVAILABLE_PLATFORMS = _get_platforms()
"""Available qibolab platforms."""


def transpile_circuits(
circuits: list[Circuit],
qubit_maps: list[list[QubitId]],
platform: Platform,
backend: Backend,
transpiler: Optional[Passes],
):
"""Transpile and pad `circuits` according to the platform.
@@ -45,26 +29,25 @@ def transpile_circuits(
are all string or all integers.
"""
transpiled_circuits = []
if platform.name in AVAILABLE_PLATFORMS:
qubits = list(platform.qubits)
if isinstance(qubit_maps[0][0], str):
for i, qubit_map in enumerate(qubit_maps):
qubit_map = map(lambda x: qubits.index(x), qubit_map)
qubit_maps[i] = list(qubit_map)
platform_nqubits = platform.nqubits
for circuit, qubit_map in zip(circuits, qubit_maps):
new_circuit = pad_circuit(platform_nqubits, circuit, qubit_map)
transpiled_circ, _ = transpiler(new_circuit)
transpiled_circuits.append(transpiled_circ)
else:
transpiled_circuits = circuits
platform = backend.platform
qubits = list(platform.qubits)
if isinstance(qubit_maps[0][0], str):
for i, qubit_map in enumerate(qubit_maps):
qubit_map = map(lambda x: qubits.index(x), qubit_map)
qubit_maps[i] = list(qubit_map)
platform_nqubits = platform.nqubits
for circuit, qubit_map in zip(circuits, qubit_maps):
new_circuit = pad_circuit(platform_nqubits, circuit, qubit_map)
transpiled_circ, _ = transpiler(new_circuit)
transpiled_circuits.append(transpiled_circ)

return transpiled_circuits


def execute_transpiled_circuits(
circuits: list[Circuit],
qubit_maps: list[list[QubitId]],
platform: Platform,
backend: Backend,
transpiler: Optional[Passes],
initial_states=None,
nshots=1000,
@@ -82,13 +65,9 @@ def execute_transpiled_circuits(
transpiled_circuits = transpile_circuits(
circuits,
qubit_maps,
platform,
backend,
transpiler,
)
if platform.name in AVAILABLE_PLATFORMS:
backend = construct_backend(backend="qibolab", platform=platform)
else:
backend = get_backend()
return transpiled_circuits, backend.execute_circuits(
transpiled_circuits, initial_states=initial_states, nshots=nshots
)
@@ -97,7 +76,7 @@ def execute_transpiled_circuits(
def execute_transpiled_circuit(
circuit: Circuit,
qubit_map: list[QubitId],
platform: Platform,
backend: Backend,
transpiler: Optional[Passes],
initial_state=None,
nshots=1000,
@@ -116,14 +95,9 @@ def execute_transpiled_circuit(
transpiled_circ = transpile_circuits(
[circuit],
[qubit_map],
platform,
backend,
transpiler,
)[0]
if platform.name in AVAILABLE_PLATFORMS:
backend = construct_backend(backend="qibolab", platform=platform)
else:
backend = get_backend()
set_compiler(backend)
return transpiled_circ, backend.execute_circuit(
transpiled_circ, initial_state=initial_state, nshots=nshots
)
@@ -152,12 +126,10 @@ def get_natives(platform):
return natives


def set_compiler(backend):
def set_compiler(backend, natives):
"""
Set the compiler to execute the native gates defined by the platform.
"""
platform = backend.platform
natives = get_natives(platform)
compiler = backend.compiler
for native in natives:
gate = getattr(gates, native)
@@ -174,17 +146,17 @@ def rule(qubits_ids, platform, parameters=None):
backend.compiler[gate] = rule
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, usually it is better to avoid mutating objects, especially nested ones.
I could lift the suggestion by one level, and suggest creating the entire backend (here just returning the compiler, instead of modifying the backend). But let's limit to the compiler itself.

You could create an empty dictionary rules, and here do

Suggested change
backend.compiler[gate] = rule
rules[gate] = rule

eventually setting the compiler out of the loops as:

    backend.compiler = Compiler(rules=rules)

(in principle, you can avoid even modifying the dictionary rules, by defining it immediately through comprehension - but I get this is a bit unfamiliar).



def dummy_transpiler(platform) -> Optional[Passes]:
def dummy_transpiler(backend: Backend) -> Passes:
"""
If the backend is `qibolab`, a transpiler with just an unroller is returned,
otherwise None.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Properly document that this function is not pure, since it has the side effect of modifying the input backend (in particular, affecting its .compiler attribute).

I know this seems pedantic, but losing track of this changes may have effects later on, when they will become unexpected.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My idea at the beginning was to make the set_compiler function in each protocol that executes circuits explicit. I decided to put it here to make my life easier but I will open an issue about it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My idea at the beginning was to make the set_compiler function in each protocol that executes circuits explicit.

No need.

I agree with your choice, since there is nothing protocol-specific, so it should not be repeated over and over.

to make my life easier

Everyone's life easier. Good choice :)

I decided to put it here

Even better, you could have put it here

backend = construct_backend(backend="qibolab", platform=platform)
platform = self.platform = backend.platform

if it were the single place where the backend was created.

Since unfortunately it is not, instead of this:

backend = construct_backend("qibolab", platform=platform)
transpiler = dummy_transpiler(backend)

you could actually just make a single function, and invoke as

    backend, transpiler = construct_qibolab_backend(platform)

considering that this double functions invocation is repeat over and over.

https://github.com/search?q=repo%3Aqiboteam%2Fqibocal%20construct_backend&type=code

(in any case, this one could be an issue - but it's especially relevant for 0.2, since we won't maintain 0.1 for long)

"""
if platform.name in AVAILABLE_PLATFORMS:
native_gates = get_natives(platform)
native_gates = list(map(lambda x: getattr(gates, x), native_gates))

unroller = Unroller(NativeGates.from_gatelist(native_gates))
return Passes(connectivity=platform.topology, passes=[unroller])
platform = backend.platform
native_gates = get_natives(platform)
set_compiler(backend, native_gates)
native_gates = list(map(lambda x: getattr(gates, x), native_gates))
unroller = Unroller(NativeGates.from_gatelist(native_gates))
return Passes(connectivity=platform.topology, passes=[unroller])


def pad_circuit(nqubits, circuit: Circuit, qubit_map: list[int]) -> Circuit:
38 changes: 6 additions & 32 deletions src/qibocal/protocols/randomized_benchmarking/utils.py
Original file line number Diff line number Diff line change
@@ -7,15 +7,13 @@
import numpy as np
import numpy.typing as npt
from qibo import gates
from qibo.backends import construct_backend, get_backend
from qibo.config import raise_error
from qibo.backends import construct_backend
from qibo.models import Circuit
from qibolab.platform import Platform
from qibolab.qubits import QubitId, QubitPairId

from qibocal.auto.operation import Data, Parameters, Results
from qibocal.auto.transpile import (
AVAILABLE_PLATFORMS,
dummy_transpiler,
execute_transpiled_circuit,
execute_transpiled_circuits,
@@ -347,21 +345,7 @@ def setup(
Returns:
tuple: A tuple containing the experiment data and backend.
"""
if platform.name in AVAILABLE_PLATFORMS:
backend = construct_backend(backend="qibolab", platform=platform)
else:
backend = get_backend()
# For simulations, a noise model can be added.
noise_model = None
if params.noise_model is not None:
if backend.name == "qibolab":
raise_error(
ValueError,
"Backend qibolab (%s) does not perform noise models simulation. ",
)

noise_model = getattr(noisemodels, params.noise_model)(params.noise_params)
params.noise_params = noise_model.params.tolist()
backend = construct_backend(backend="qibolab", platform=platform)
# Set up the scan (here an iterator of circuits of random clifford gates with an inverse).
if single_qubit:
cls = RBData
@@ -449,7 +433,7 @@ def execute_circuits(circuits, targets, params, backend, single_qubit=True):
"""
# Execute the circuits
platform = backend.platform
transpiler = dummy_transpiler(platform)
transpiler = dummy_transpiler(backend)
qubit_maps = (
[[i] for i in targets] * (len(params.depths) * params.niter)
if single_qubit
@@ -459,7 +443,7 @@ def execute_circuits(circuits, targets, params, backend, single_qubit=True):
_, executed_circuits = execute_transpiled_circuits(
circuits,
qubit_maps=qubit_maps,
platform=platform,
backend=backend,
nshots=params.nshots,
transpiler=transpiler,
)
@@ -468,7 +452,7 @@ def execute_circuits(circuits, targets, params, backend, single_qubit=True):
execute_transpiled_circuit(
circuit,
qubit_map=qubit_map,
platform=platform,
backend=backend,
nshots=params.nshots,
transpiler=transpiler,
)[1]
@@ -544,20 +528,10 @@ def twoq_rb_acquisition(
RB2QData: The acquired data for two qubit randomized benchmarking.
"""
targets = [tuple(pair) if isinstance(pair, list) else pair for pair in targets]
data, noise_model, backend = setup(params, platform, single_qubit=False)
data, backend = setup(params, platform, single_qubit=False)
circuits, indexes, npulses_per_clifford = get_circuits(
params, targets, add_inverse_layer, interleave, single_qubit=False
)
# To retrieve the native gates we suppose all the qubits/pairs in the platform
# have the same
two_qubit_natives = list(platform.pairs[targets[0]].native_gates.raw.keys())
single_qubit_natives = [
"GPI2",
"RZ",
"M",
]
natives = single_qubit_natives + two_qubit_natives
natives = list(map(lambda x: getattr(gates, x), natives))
executed_circuits = execute_circuits(
circuits, targets, params, backend, single_qubit=False
)
7 changes: 4 additions & 3 deletions src/qibocal/protocols/readout_mitigation_matrix.py
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@
import numpy.typing as npt
import plotly.express as px
from qibo import gates
from qibo.backends import construct_backend
from qibo.models import Circuit
from qibolab.platform import Platform
from qibolab.qubits import QubitId
@@ -60,8 +61,8 @@ def _acquisition(
data = ReadoutMitigationMatrixData(
nshots=params.nshots, qubit_list=[list(qq) for qq in targets]
)

transpiler = dummy_transpiler(platform)
backend = construct_backend("qibolab", platform=platform)
transpiler = dummy_transpiler(backend)
qubit_map = [i for i in range(platform.nqubits)]
for qubits in targets:
nqubits = len(qubits)
@@ -75,7 +76,7 @@ def _acquisition(
c.add(gates.X(q))
c.add(gates.M(*range(nqubits)))
_, results = execute_transpiled_circuit(
c, qubits, platform, nshots=params.nshots, transpiler=transpiler
c, qubits, backend, nshots=params.nshots, transpiler=transpiler
)
frequencies = np.zeros(2 ** len(qubits))
for i, freq in results.frequencies().items():
7 changes: 4 additions & 3 deletions src/qibocal/protocols/state_tomography.py
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from qibo import Circuit, gates
from qibo.backends import NumpyBackend, matrices
from qibo.backends import NumpyBackend, construct_backend, matrices
from qibo.quantum_info import fidelity, partial_trace
from qibolab.platform import Platform
from qibolab.qubits import QubitId
@@ -102,7 +102,8 @@ def _acquisition(
if params.circuit is None:
params.circuit = Circuit(len(targets))

transpiler = dummy_transpiler(platform)
backend = construct_backend("qibolab", platform=platform)
transpiler = dummy_transpiler(backend)

data = StateTomographyData(
circuit=params.circuit, targets={target: i for i, target in enumerate(targets)}
@@ -119,7 +120,7 @@ def _acquisition(
_, results = execute_transpiled_circuit(
basis_circuit,
targets,
platform,
backend,
nshots=params.nshots,
transpiler=transpiler,
)
6 changes: 4 additions & 2 deletions src/qibocal/protocols/two_qubit_interaction/chsh/protocol.py
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@
import numpy as np
import numpy.typing as npt
import plotly.graph_objects as go
from qibo.backends import construct_backend
from qibolab import ExecutionParameters
from qibolab.platform import Platform
from qibolab.qubits import QubitId, QubitPairId
@@ -222,7 +223,8 @@ def _acquisition_circuits(
bell_states=params.bell_states,
thetas=thetas.tolist(),
)
transpiler = dummy_transpiler(platform)
backend = construct_backend("qibolab", platform=platform)
transpiler = dummy_transpiler(backend)
if params.apply_error_mitigation:
mitigation_data = mitigation_acquisition(
mitigation_params(nshots=params.nshots), platform, targets
@@ -250,7 +252,7 @@ def _acquisition_circuits(
circuit,
nshots=params.nshots,
transpiler=transpiler,
platform=platform,
backend=backend,
qubit_map=pair,
)
frequencies = result.frequencies()
7 changes: 4 additions & 3 deletions src/qibocal/protocols/two_qubit_state_tomography.py
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from qibo import Circuit, gates
from qibo.backends import NumpyBackend
from qibo.backends import NumpyBackend, construct_backend
from qibo.quantum_info import fidelity, partial_trace
from qibo.result import QuantumState
from qibolab.platform import Platform
@@ -98,7 +98,8 @@ def _acquisition(
params.circuit = Circuit(len(qubits))

simulator = NumpyBackend()
transpiler = dummy_transpiler(platform)
backend = construct_backend("qibolab", platform=platform)
transpiler = dummy_transpiler(backend)

simulated_state = simulator.execute_circuit(deepcopy(params.circuit))
data = StateTomographyData(simulated=simulated_state)
@@ -125,7 +126,7 @@ def _acquisition(
_, results = execute_transpiled_circuit(
basis_circuit,
qubits,
platform,
backend,
nshots=params.nshots,
transpiler=transpiler,
)
16 changes: 6 additions & 10 deletions tests/test_transpile.py
Original file line number Diff line number Diff line change
@@ -29,12 +29,10 @@ def test_execute_transpiled_circuit():
circuit.add(gates.X(0))
circuit.add(gates.X(1))
qubit_map = [1, 2]
set_backend("qibolab", platform="dummy")
backend = get_backend()
platform = backend.platform
transpiler = dummy_transpiler(platform)
backend = construct_backend("qibolab", platform="dummy")
transpiler = dummy_transpiler(backend)
transpiled_circuit, _ = execute_transpiled_circuit(
circuit, qubit_map, platform, transpiler=transpiler
circuit, qubit_map, backend, transpiler=transpiler
)
true_circuit = Circuit(5)
true_circuit.add(gates.GPI2(1, np.pi / 2))
@@ -52,12 +50,10 @@ def test_execute_transpiled_circuits():
circuit.add(gates.X(0))
circuit.add(gates.X(1))
qubit_map = [1, 2]
set_backend("qibolab", platform="dummy")
backend = get_backend()
platform = backend.platform
transpiler = dummy_transpiler(platform)
backend = construct_backend("qibolab", platform="dummy")
transpiler = dummy_transpiler(backend)
transpiled_circuits, _ = execute_transpiled_circuits(
[circuit], [qubit_map], platform, transpiler=transpiler
[circuit], [qubit_map], backend, transpiler=transpiler
)
true_circuit = Circuit(5)
true_circuit.add(gates.GPI2(1, np.pi / 2))
You are viewing a condensed version of this merge commit. You can view the full changes here.