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

Transpiler refactor #1500

Merged
merged 37 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
2c7190e
fix: update connectivity in default transpiler
Oct 17, 2024
e1e769a
fix: remove initial_layout & use wire_names & update test files
Oct 22, 2024
2a888ad
fix: update code-examples
Oct 23, 2024
05b6f68
fix: update comparison in test_bell_state_3q
Oct 23, 2024
876b79c
feat: circuit draw with int qubit names
Oct 23, 2024
02e9945
fix: placer.py coverage
Oct 23, 2024
05f36e7
fix: circuit.py coverage
Oct 23, 2024
01b667d
fix: router.py coverage
Oct 23, 2024
4e21132
fix: remove dead codes
Oct 24, 2024
12bf8b0
fix: refactor connectivity graphs in test files
Oct 24, 2024
36c1348
fix: return type of a router
Oct 24, 2024
f19dc6e
fix: minor changes
Oct 25, 2024
7534ef2
fix: remove add_nodes_from when setting backend
Oct 25, 2024
67fae1f
fix: Circuit remove default wire_names / dict wire_names
Oct 28, 2024
2ac1a85
fix: wire_names update docstrings
Oct 28, 2024
788fd06
fix: line connectivity remove fixture
Oct 28, 2024
4e3dbce
fix: test files default qubit
Oct 28, 2024
d4e507c
fix: refactor assert functions
Oct 28, 2024
5cf75b5
fix: refactor assert funcs
Oct 28, 2024
942991a
fix: pandoc update result draw()
Oct 28, 2024
25d8788
fix: pandoc update
Oct 28, 2024
93ac772
feat: Circuit __init__ int/list first arg init
Oct 28, 2024
41f5eff
fix: Circuit._parse -> _resolve_qubits
Oct 28, 2024
a4f2092
fix: minor updates / update docstring of qibo.Circuit
Oct 30, 2024
10be459
fix: update test files _resolve_qubits / wire_names setter
Oct 30, 2024
a1d3302
fix: combine similar assert func to assert_placement
Oct 30, 2024
0851eeb
fix: minor test files update
Oct 30, 2024
ec37f34
Merge branch 'master' into transpiler_refactor
Nov 4, 2024
32c9ac2
Merge branch 'master' into transpiler_refactor
csookim Nov 8, 2024
ff89c58
Merge branch 'master' into transpiler_refactor
Nov 19, 2024
824bb76
passes move connectivity check to __call__
Nov 21, 2024
214a4ea
fix: remove Trivial and Custom
Nov 22, 2024
abed4b9
fix: remove default star transpiler / enforce connectivity
Nov 22, 2024
ad079de
fix: modify is_satisfied
Nov 22, 2024
1f55f3c
fix: revert, make connectivity optiona
Nov 22, 2024
797331e
fix: type errors, Preprocessor connectivity check, utils.py -> assert…
Nov 25, 2024
049ff01
fix: utils -> asserts
Nov 25, 2024
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
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 @@ -135,7 +135,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 @@ -147,21 +146,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