Skip to content

Commit

Permalink
Merge pull request #1500 from qiboteam/transpiler_refactor
Browse files Browse the repository at this point in the history
Transpiler refactor
  • Loading branch information
csookim authored Nov 27, 2024
2 parents ea6984d + 049ff01 commit 7898f00
Show file tree
Hide file tree
Showing 30 changed files with 1,132 additions and 1,383 deletions.
31 changes: 13 additions & 18 deletions doc/source/code-examples/advancedexamples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1314,9 +1314,9 @@ Let's see how to use them. For starters, let's define a dummy circuit with some
# visualize the circuit
circuit.draw()

# q0: ─RZ─RX─RZ─RX─RZ─o────o────────M─
# q1: ─RZ─RX─RZ─RX─RZ─X─RZ─X─o────o─M─
# q2: ─RZ─RX─RZ─RX─RZ────────X─RZ─X─M─
# 0: ─RZ─RX─RZ─RX─RZ─o────o────────M─
# 1: ─RZ─RX─RZ─RX─RZ─X─RZ─X─o────o─M─
# 2: ─RZ─RX─RZ─RX─RZ────────X─RZ─X─M─

.. testoutput::
:hide:
Expand Down Expand Up @@ -1432,12 +1432,12 @@ number of CNOT or RX pairs (depending on the value of ``insertion_gate``) insert
circuit in correspondence to the original ones. Since we decided to simulate noisy CNOTs::

Level 1
q0: ─X─ --> q0: ─X───X──X─
q1: ─o─ --> q1: ─o───o──o─
0: ─X─ --> 0: ─X───X──X─
1: ─o─ --> 1: ─o───o──o─

Level 2
q0: ─X─ --> q0: ─X───X──X───X──X─
q1: ─o─ --> q1: ─o───o──o───o──o─
0: ─X─ --> 0: ─X───X──X───X──X─
1: ─o─ --> 1: ─o───o──o───o──o─

.
.
Expand Down Expand Up @@ -2139,8 +2139,6 @@ Qibo implements a built-in transpiler with customizable options for each step. T
be used at each transpiler step are reported below with a short description.

The initial placement can be found with one of the following procedures:
- Trivial: logical-physical qubit mapping is an identity.
- Custom: custom logical-physical qubit mapping.
- Random greedy: the best mapping is found within a set of random layouts based on a greedy policy.
- Subgraph isomorphism: the initial mapping is the one that guarantees the execution of most gates at
the beginning of the circuit without introducing any SWAP.
Expand Down Expand Up @@ -2168,22 +2166,22 @@ Multiple transpilation steps can be implemented using the :class:`qibo.transpile

from qibo import gates
from qibo.models import Circuit
from qibo.transpiler.pipeline import Passes, assert_transpiling
from qibo.transpiler.pipeline import Passes
from qibo.transpiler.optimizer import Preprocessing
from qibo.transpiler.router import ShortestPaths
from qibo.transpiler.unroller import Unroller, NativeGates
from qibo.transpiler.placer import Random
from qibo.transpiler.asserts import assert_transpiling

# Define connectivity as nx.Graph
def star_connectivity():
chip = nx.Graph()
chip.add_nodes_from(list(range(5)))
graph_list = [(i, 2) for i in range(5) if i != 2]
chip.add_edges_from(graph_list)
chip = nx.Graph([("q0", "q2"), ("q1", "q2"), ("q2", "q3"), ("q2", "q4")])
return chip

# Define the circuit
circuit = Circuit(2)
# wire_names must match nodes in the connectivity graph.
# The index in wire_names represents the logical qubit number in the circuit.
circuit = Circuit(2, wire_names=["q0", "q1"])
circuit.add(gates.H(0))
circuit.add(gates.CZ(0, 1))

Expand All @@ -2205,13 +2203,10 @@ Multiple transpilation steps can be implemented using the :class:`qibo.transpile
transpiled_circ, final_layout = custom_pipeline(circuit)

# Optinally call assert_transpiling to check that the final circuit can be executed on hardware
# For this test it is necessary to get the initial layout
initial_layout = custom_pipeline.get_initial_layout()
assert_transpiling(
original_circuit=circuit,
transpiled_circuit=transpiled_circ,
connectivity=star_connectivity(),
initial_layout=initial_layout,
final_layout=final_layout,
native_gates=NativeGates.default()
)
Expand Down
20 changes: 10 additions & 10 deletions doc/source/code-examples/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -313,20 +313,20 @@ For example
circuit.draw()
# Prints
'''
q0: ─H─U1─U1─U1─U1───────────────────────────x───
q1: ───o──|──|──|──H─U1─U1─U1────────────────|─x─
q2: ──────o──|──|────o──|──|──H─U1─U1────────|─|─
q3: ─────────o──|───────o──|────o──|──H─U1───|─x─
q4: ────────────o──────────o───────o────o──H─x───
0: ─H─U1─U1─U1─U1───────────────────────────x───
1: ───o──|──|──|──H─U1─U1─U1────────────────|─x─
2: ──────o──|──|────o──|──|──H─U1─U1────────|─|─
3: ─────────o──|───────o──|────o──|──H─U1───|─x─
4: ────────────o──────────o───────o────o──H─x───
'''
.. testoutput::
:hide:

q0: ─H─U1─U1─U1─U1───────────────────────────x───
q1: ───o──|──|──|──H─U1─U1─U1────────────────|─x─
q2: ──────o──|──|────o──|──|──H─U1─U1────────|─|─
q3: ─────────o──|───────o──|────o──|──H─U1───|─x─
q4: ────────────o──────────o───────o────o──H─x───
0: ─H─U1─U1─U1─U1───────────────────────────x───
1: ───o──|──|──|──H─U1─U1─U1────────────────|─x─
2: ──────o──|──|────o──|──|──H─U1─U1────────|─|─
3: ─────────o──|───────o──|────o──|──H─U1───|─x─
4: ────────────o──────────o───────o────o──H─x───

How to visualize a circuit with style?
--------------------------------------
Expand Down
16 changes: 3 additions & 13 deletions src/qibo/backends/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,6 @@ def set_transpiler(cls, transpiler):
def _default_transpiler(cls):
from qibo.transpiler.optimizer import Preprocessing
from qibo.transpiler.pipeline import Passes
from qibo.transpiler.placer import Trivial
from qibo.transpiler.router import Sabre
from qibo.transpiler.unroller import NativeGates, Unroller

Expand All @@ -148,21 +147,12 @@ def _default_transpiler(cls):
and natives is not None
and connectivity_edges is not None
):
# only for q{i} naming
node_mapping = {q: i for i, q in enumerate(qubits)}
edges = [
(node_mapping[e[0]], node_mapping[e[1]]) for e in connectivity_edges
]
connectivity = nx.Graph()
connectivity.add_nodes_from(list(node_mapping.values()))
connectivity.add_edges_from(edges)

connectivity = nx.Graph(connectivity_edges)
return Passes(
connectivity=connectivity,
passes=[
Preprocessing(connectivity),
Trivial(connectivity),
Sabre(connectivity),
Preprocessing(),
Sabre(),
Unroller(NativeGates[natives]),
],
)
Expand Down
8 changes: 4 additions & 4 deletions src/qibo/gates/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,10 +273,10 @@ def on_qubits(self, qubit_map) -> "Gate":
circuit.draw()
.. testoutput::
q0: ───X─────
q1: ───|─o─X─
q2: ─o─|─|─o─
q3: ─X─o─X───
0: ───X─────
1: ───|─o─X─
2: ─o─|─|─o─
3: ─X─o─X───
"""
if self.is_controlled_by:
targets = (qubit_map.get(q) for q in self.target_qubits)
Expand Down
6 changes: 3 additions & 3 deletions src/qibo/gates/measurements.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,9 +252,9 @@ def on_qubits(self, qubit_map) -> "Gate":
circuit.draw()
.. testoutput::
q0: ─M─
q1: ─|─
q2: ─M─
0: ─M─
1: ─|─
2: ─M─
"""

qubits = (qubit_map.get(q) for q in self.qubits)
Expand Down
119 changes: 65 additions & 54 deletions src/qibo/models/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,23 @@ class Circuit:
This circuit is symbolic and cannot perform calculations.
A specific backend has to be used for performing calculations.
Circuits can be created with a specific number of qubits and wire names.
Example:
.. testcode::
from qibo import Circuit
c = Circuit(5) # Default wire names are [0, 1, 2, 3, 4]
c = Circuit(["A", "B", "C", "D", "E"])
c = Circuit(5, wire_names=["A", "B", "C", "D", "E"])
c = Circuit(wire_names=["A", "B", "C", "D", "E"])
Args:
nqubits (int): Total number of qubits in the circuit.
nqubits (int | list, optional): Number of qubits in the circuit or a list of wire names.
wire_names (list, optional): List of wire names.
- Either ``nqubits`` or ``wire_names`` must be provided.
- If only ``nqubits`` is provided, wire names will default to [``0``, ``1``, ..., ``nqubits - 1``].
- If only ``wire_names`` is provided, ``nqubits`` will be set to the length of ``wire_names``.
- ``nqubits`` and ``wire_names`` must be consistent with each other.
init_kwargs (dict): a dictionary with the following keys
- *nqubits*
Expand All @@ -141,11 +156,6 @@ class Circuit:
Defaults to ``False``.
accelerators (dict, optional): Dictionary that maps device names to the number of times each
device will be used. Defaults to ``None``.
wire_names (list or dict, optional): Names for qubit wires.
If ``None``, defaults to (``q0``, ``q1``... ``qn``).
If ``list`` is passed, length of ``list`` must match ``nqubits``.
If ``dict`` is passed, the keys should match the default pattern.
Defaults to ``None``.
ndevices (int): Total number of devices. Defaults to ``None``.
nglobal (int): Base two logarithm of the number of devices. Defaults to ``None``.
nlocal (int): Total number of available qubits in each device. Defaults to ``None``.
Expand All @@ -155,29 +165,20 @@ class Circuit:

def __init__(
self,
nqubits: int,
nqubits: Optional[Union[int, list]] = None,
accelerators=None,
density_matrix: bool = False,
wire_names: Optional[Union[list, dict]] = None,
wire_names: Optional[list] = None,
):
if not isinstance(nqubits, int):
raise_error(
TypeError,
f"Number of qubits must be an integer but is {nqubits}.",
)
if nqubits < 1:
raise_error(
ValueError,
f"Number of qubits must be positive but is {nqubits}.",
)
nqubits, wire_names = _resolve_qubits(nqubits, wire_names)
self.nqubits = nqubits
self.wire_names = wire_names
self.init_kwargs = {
"nqubits": nqubits,
"accelerators": accelerators,
"density_matrix": density_matrix,
"wire_names": wire_names,
}
self.wire_names = wire_names
self.queue = _Queue(nqubits)
# Keep track of parametrized gates for the ``set_parameters`` method
self.parametrized_gates = _ParametrizedGates()
Expand Down Expand Up @@ -282,49 +283,29 @@ def __add__(self, circuit):

@property
def wire_names(self):
if self._wire_names is None:
return list(range(self.nqubits))
return self._wire_names

@wire_names.setter
def wire_names(self, wire_names: Union[list, dict]):
if not isinstance(wire_names, (list, dict, type(None))):
def wire_names(self, wire_names: Optional[list]):
if not isinstance(wire_names, (list, type(None))):
raise_error(
TypeError,
f"``wire_names`` must be type ``list`` or ``dict``, but is {type(wire_names)}.",
f"``wire_names`` must be type ``list``, but is {type(wire_names)}.",
)

if isinstance(wire_names, list):
if wire_names is not None:
if len(wire_names) != self.nqubits:
raise_error(
ValueError,
"Number of wire names must be equal to the number of qubits, "
f"but is {len(wire_names)}.",
)

if any([not isinstance(name, str) for name in wire_names]):
raise_error(ValueError, "all wire names must be type ``str``.")

self._wire_names = wire_names
elif isinstance(wire_names, dict):
if len(wire_names.keys()) > self.nqubits:
raise_error(
ValueError,
"number of elements in the ``wire_names`` dictionary "
+ "cannot be bigger than ``nqubits``.",
)

if any([not isinstance(name, str) for name in wire_names.keys()]) or any(
[not isinstance(name, str) for name in wire_names.values()]
):
raise_error(
ValueError,
"all keys and values in the ``wire_names`` dictionary must be type ``str``.",
)

self._wire_names = [
wire_names.get(f"q{i}", f"q{i}") for i in range(self.nqubits)
]
self._wire_names = wire_names.copy()
else:
self._wire_names = [f"q{i}" for i in range(self.nqubits)]
self._wire_names = None
self.init_kwargs["wire_names"] = self._wire_names

@property
def repeated_execution(self):
Expand Down Expand Up @@ -407,8 +388,8 @@ def light_cone(self, *qubits):
qubit_map = {q: i for i, q in enumerate(sorted(qubits))}
kwargs = dict(self.init_kwargs)
kwargs["nqubits"] = len(qubits)
kwargs["wire_names"] = [self.wire_names[q] for q in sorted(qubits)]
circuit = self.__class__(**kwargs)
circuit.wire_names = [self.wire_names[q] for q in list(sorted(qubits))]
circuit.add(gate.on_qubits(qubit_map) for gate in reversed(list_of_gates))
return circuit, qubit_map

Expand Down Expand Up @@ -1282,6 +1263,7 @@ def diagram(self, line_wrap: int = 70, legend: bool = False) -> str:
"""Build the string representation of the circuit diagram."""
# build string representation of gates
matrix = [[] for _ in range(self.nqubits)]
wire_names = [str(name) for name in self.wire_names]
idx = [0] * self.nqubits

for gate in self.queue:
Expand All @@ -1303,12 +1285,12 @@ def diagram(self, line_wrap: int = 70, legend: bool = False) -> str:
matrix[row][col] += "─" * (1 + maxlen - len(matrix[row][col]))

# Print to terminal
max_name_len = max(len(name) for name in self.wire_names)
max_name_len = max(len(name) for name in wire_names)
output = ""
for q in range(self.nqubits):
output += (
self.wire_names[q]
+ " " * (max_name_len - len(self.wire_names[q]))
wire_names[q]
+ " " * (max_name_len - len(wire_names[q]))
+ ": ─"
+ "".join(matrix[q])
+ "\n"
Expand Down Expand Up @@ -1350,8 +1332,8 @@ def chunkstring(string, length):
loutput += ["" for _ in range(self.nqubits)]
suffix = " ...\n"
prefix = (
self.wire_names[row]
+ " " * (max_name_len - len(self.wire_names[row]))
wire_names[row]
+ " " * (max_name_len - len(wire_names[row]))
+ ": "
)
if i == 0:
Expand Down Expand Up @@ -1388,3 +1370,32 @@ def draw(self, line_wrap: int = 70, legend: bool = False):
String containing text circuit diagram.
"""
sys.stdout.write(self.diagram(line_wrap, legend) + "\n")


def _resolve_qubits(qubits, wire_names):
"""Parse the input arguments for defining a circuit. Allows the user to initialize the circuit as follows:
Example:
.. code-block:: python
from qibo import Circuit
c = Circuit(3)
c = Circuit(3, wire_names=["q0", "q1", "q2"])
c = Circuit(["q0", "q1", "q2"])
c = Circuit(wire_names=["q0", "q1", "q2"])
"""
if qubits is None and wire_names is not None:
return len(wire_names), wire_names
if qubits is not None and wire_names is None:
if isinstance(qubits, int) and qubits > 0:
return qubits, None
if isinstance(qubits, list):
return len(qubits), qubits
if qubits is not None and wire_names is not None:
if isinstance(qubits, int) and isinstance(wire_names, list):
if qubits == len(wire_names):
return qubits, wire_names

raise_error(
ValueError,
"Invalid input arguments for defining a circuit.",
)
Loading

0 comments on commit 7898f00

Please sign in to comment.