diff --git a/src/qibo/models/circuit.py b/src/qibo/models/circuit.py index bd92eadfbb..3629959281 100644 --- a/src/qibo/models/circuit.py +++ b/src/qibo/models/circuit.py @@ -119,7 +119,8 @@ class Circuit: - *nqubits* - *accelerators* - - *density_matrix*. + - *density_matrix* + - *wires_names*. queue (_Queue): List that holds the queue of gates of a circuit. parametrized_gates (_ParametrizedGates): List of parametric gates. @@ -133,6 +134,8 @@ class Circuit: Defaults to ``False``. accelerators (dict): Dictionary that maps device names to the number of times each device will be used. Defaults to ``None``. + wires_names (list or dict): Names for each qubit, + if None the default names ``q0``, ``q1``... ``qn`` will be used. 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``. @@ -140,7 +143,13 @@ class Circuit: Defaults to ``None``. """ - def __init__(self, nqubits, accelerators=None, density_matrix=False): + def __init__( + self, + nqubits: int, + accelerators=None, + density_matrix: bool = False, + wires_names: Union[list, dict] = None, + ): if not isinstance(nqubits, int): raise_error( TypeError, @@ -152,10 +161,12 @@ def __init__(self, nqubits, accelerators=None, density_matrix=False): f"Number of qubits must be positive but is {nqubits}.", ) self.nqubits = nqubits + self.wires_names = wires_names self.init_kwargs = { "nqubits": nqubits, "accelerators": accelerators, "density_matrix": density_matrix, + "wires_names": wires_names, } self.queue = _Queue(nqubits) # Keep track of parametrized gates for the ``set_parameters`` method @@ -259,6 +270,31 @@ def __add__(self, circuit): return newcircuit + @property + def wires_names(self): + return self._wires_names + + @wires_names.setter + def wires_names(self, wires_names: Union[list, dict]): + if isinstance(wires_names, list): + if len(wires_names) != self.nqubits: + raise_error( + ValueError, + f"Number of wires names must be equal to the number of qubits but is {len(wires_names)}.", + ) + self._wires_names = wires_names + elif isinstance(wires_names, dict): + self._wires_names = [ + wires_names.get(f"q{i}", f"q{i}") for i in range(self.nqubits) + ] + elif isinstance(wires_names, type(None)): + self._wires_names = [f"q{i}" for i in range(self.nqubits)] + else: + raise_error( + TypeError, + f"Wire names must be a list or dict but is {type(wires_names)}.", + ) + @property def repeated_execution(self): return self.has_collapse or ( @@ -1416,11 +1452,12 @@ def draw(self, line_wrap=70, legend=False) -> str: matrix[row][col] += "─" * (1 + maxlen - len(matrix[row][col])) # Print to terminal + max_name_len = max(len(name) for name in self.wires_names) output = "" for q in range(self.nqubits): output += ( - f"q{q}" - + " " * (len(str(self.nqubits)) - len(str(q))) + self.wires_names[q] + + " " * (max_name_len - len(self.wires_names[q])) + ": ─" + "".join(matrix[q]) + "\n" @@ -1453,7 +1490,7 @@ def chunkstring(string, length): for row in range(self.nqubits): chunks, nchunks = chunkstring( - loutput[row][3 + len(str(self.nqubits)) :], line_wrap + loutput[row][3 + max_name_len - 1 :], line_wrap ) if nchunks == 1: loutput = None @@ -1462,8 +1499,8 @@ def chunkstring(string, length): loutput += ["" for _ in range(self.nqubits)] suffix = " ...\n" prefix = ( - f"q{row}" - + " " * (len(str(self.nqubits)) - len(str(row))) + self.wires_names[row] + + " " * (max_name_len - len(self.wires_names[row])) + ": " ) if i == 0: diff --git a/tests/test_models_circuit.py b/tests/test_models_circuit.py index b3b86fc646..2a1a59f70b 100644 --- a/tests/test_models_circuit.py +++ b/tests/test_models_circuit.py @@ -627,6 +627,30 @@ def test_circuit_draw(): assert circuit.draw() == ref +def test_circuit_wires_names_error(): + with pytest.raises(TypeError): + circuit = Circuit(5, wires_names=1) + + +def test_circuit_draw_names(): + """Test circuit text draw.""" + ref = ( + "a : ─H─U1─U1─U1─U1───────────────────────────x───\n" + "b : ───o──|──|──|──H─U1─U1─U1────────────────|─x─\n" + "hello: ──────o──|──|────o──|──|──H─U1─U1────────|─|─\n" + "1 : ─────────o──|───────o──|────o──|──H─U1───|─x─\n" + "q4 : ────────────o──────────o───────o────o──H─x───" + ) + circuit = Circuit(5, wires_names=["a", "b", "hello", "1", "q4"]) + for i1 in range(5): + circuit.add(gates.H(i1)) + for i2 in range(i1 + 1, 5): + circuit.add(gates.CU1(i2, i1, theta=0)) + circuit.add(gates.SWAP(0, 4)) + circuit.add(gates.SWAP(1, 3)) + assert circuit.draw() == ref + + def test_circuit_draw_line_wrap(): """Test circuit text draw with line wrap.""" ref_line_wrap_50 = ( @@ -684,6 +708,63 @@ def test_circuit_draw_line_wrap(): assert circuit.draw(line_wrap=30) == ref_line_wrap_30 +def test_circuit_draw_line_wrap_names(): + """Test circuit text draw with line wrap.""" + ref_line_wrap_50 = ( + "q0: ─H─U1─U1─U1─U1───────────────────────────x───I───f ...\n" + "a : ───o──|──|──|──H─U1─U1─U1────────────────|─x─I───| ...\n" + "q2: ──────o──|──|────o──|──|──H─U1─U1────────|─|─────| ...\n" + "q3: ─────────o──|───────o──|────o──|──H─U1───|─x───M─| ...\n" + "q4: ────────────o──────────o───────o────o──H─x───────f ...\n" + "\n" + "q0: ... ─o────gf───M─\n" + "a : ... ─U3───|──o─M─\n" + "q2: ... ────X─gf─o─M─\n" + "q3: ... ────o────o───\n" + "q4: ... ────o────X───" + ) + + ref_line_wrap_30 = ( + "q0: ─H─U1─U1─U1─U1──────────────── ...\n" + "a : ───o──|──|──|──H─U1─U1─U1───── ...\n" + "q2: ──────o──|──|────o──|──|──H─U1 ...\n" + "q3: ─────────o──|───────o──|────o─ ...\n" + "q4: ────────────o──────────o────── ...\n" + "\n" + "q0: ... ───────────x───I───f─o────gf── ...\n" + "a : ... ───────────|─x─I───|─U3───|──o ...\n" + "q2: ... ─U1────────|─|─────|────X─gf─o ...\n" + "q3: ... ─|──H─U1───|─x───M─|────o────o ...\n" + "q4: ... ─o────o──H─x───────f────o────X ...\n" + "\n" + "q0: ... ─M─\n" + "a : ... ─M─\n" + "q2: ... ─M─\n" + "q3: ... ───\n" + "q4: ... ───" + ) + + import numpy as np + + circuit = Circuit(5, wires_names={"q1": "a"}) + for i1 in range(5): + circuit.add(gates.H(i1)) + for i2 in range(i1 + 1, 5): + circuit.add(gates.CU1(i2, i1, theta=0)) + circuit.add(gates.SWAP(0, 4)) + circuit.add(gates.SWAP(1, 3)) + circuit.add(gates.I(*range(2))) + circuit.add(gates.M(3, collapse=True)) + circuit.add(gates.fSim(0, 4, 0, 0)) + circuit.add(gates.CU3(0, 1, 0, 0, 0)) + circuit.add(gates.TOFFOLI(4, 3, 2)) + circuit.add(gates.GeneralizedfSim(0, 2, np.eye(2), 0)) + circuit.add(gates.X(4).controlled_by(1, 2, 3)) + circuit.add(gates.M(*range(3))) + assert circuit.draw(line_wrap=50) == ref_line_wrap_50 + assert circuit.draw(line_wrap=30) == ref_line_wrap_30 + + @pytest.mark.parametrize("legend", [True, False]) def test_circuit_draw_channels(legend): """Check that channels are drawn correctly."""