From 2f090de520962958d6f719c4ab29d185b6f2caf2 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Mon, 27 May 2024 13:21:31 +0200 Subject: [PATCH 1/4] Add option to user config to control idle_wires in circuit drawer Co-Authored-By: diemilio --- qiskit/circuit/quantumcircuit.py | 14 +++-- qiskit/user_config.py | 14 +++++ .../circuit/circuit_visualization.py | 12 +++- .../workaroud_12361-994d0ac2d2a6ed41.yaml | 14 +++++ test/python/test_user_config.py | 29 ++++++++++ .../visualization/test_circuit_text_drawer.py | 55 ++++++++++++++++++- 6 files changed, 127 insertions(+), 11 deletions(-) create mode 100644 releasenotes/notes/workaroud_12361-994d0ac2d2a6ed41.yaml diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index a157f04375a3..9ec5e1e022aa 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -1759,12 +1759,12 @@ def compose( clbits (list[Clbit|int]): clbits of self to compose onto. front (bool): If True, front composition will be performed. This is not possible within control-flow builder context managers. - inplace (bool): If True, modify the object. Otherwise return composed circuit. + inplace (bool): If True, modify the object. Otherwise, return composed circuit. copy (bool): If ``True`` (the default), then the input is treated as shared, and any contained instructions will be copied, if they might need to be mutated in the future. You can set this to ``False`` if the input should be considered owned by the base circuit, in order to avoid unnecessary copies; in this case, it is not - valid to use ``other`` afterwards, and some instructions may have been mutated in + valid to use ``other`` afterward, and some instructions may have been mutated in place. var_remap (Mapping): mapping to use to rewrite :class:`.expr.Var` nodes in ``other`` as they are inlined into ``self``. This can be used to avoid naming conflicts. @@ -2068,7 +2068,7 @@ def tensor(self, other: "QuantumCircuit", inplace: bool = False) -> Optional["Qu Args: other (QuantumCircuit): The other circuit to tensor this circuit with. - inplace (bool): If True, modify the object. Otherwise return composed circuit. + inplace (bool): If ``True``, modify the object. Otherwise return composed circuit. Examples: @@ -3126,7 +3126,7 @@ def draw( reverse_bits: bool | None = None, justify: str | None = None, vertical_compression: str | None = "medium", - idle_wires: bool = True, + idle_wires: bool | None = None, with_layout: bool = True, fold: int | None = None, # The type of ax is matplotlib.axes.Axes, but this is not a fixed dependency, so cannot be @@ -3157,7 +3157,7 @@ def draw( Args: output: Select the output method to use for drawing the circuit. Valid choices are ``text``, ``mpl``, ``latex``, ``latex_source``. - By default the `text` drawer is used unless the user config file + By default, the `text` drawer is used unless the user config file (usually ``~/.qiskit/settings.conf``) has an alternative backend set as the default. For example, ``circuit_drawer = latex``. If the output kwarg is set, that backend will always be used over the default in @@ -3203,7 +3203,9 @@ def draw( will take less vertical room. Default is ``medium``. Only used by the ``text`` output, will be silently ignored otherwise. idle_wires: Include idle wires (wires with no circuit elements) - in output visualization. Default is ``True``. + in output visualization. Default is ``True`` unless the + user config file (usually ``~/.qiskit/settings.conf``) has an + alternative value set. For example, ``circuit_idle_wires = False``. with_layout: Include layout information, with labels on the physical layout. Default is ``True``. fold: Sets pagination. It can be disabled using -1. In ``text``, diff --git a/qiskit/user_config.py b/qiskit/user_config.py index 666bf53d962b..73a68eb6bfd5 100644 --- a/qiskit/user_config.py +++ b/qiskit/user_config.py @@ -31,6 +31,7 @@ class UserConfig: circuit_mpl_style = default circuit_mpl_style_path = ~/.qiskit: circuit_reverse_bits = True + circuit_idle_wires = False transpile_optimization_level = 1 parallel = False num_processes = 4 @@ -130,6 +131,18 @@ def read_config_file(self): if circuit_reverse_bits is not None: self.settings["circuit_reverse_bits"] = circuit_reverse_bits + # Parse circuit_idle_wires + try: + circuit_idle_wires = self.config_parser.getboolean( + "default", "circuit_idle_wires", fallback=None + ) + except ValueError as err: + raise exceptions.QiskitUserConfigError( + f"Value assigned to circuit_idle_wires is not valid. {str(err)}" + ) + if circuit_idle_wires is not None: + self.settings["circuit_idle_wires"] = circuit_idle_wires + # Parse transpile_optimization_level transpile_optimization_level = self.config_parser.getint( "default", "transpile_optimization_level", fallback=-1 @@ -191,6 +204,7 @@ def set_config(key, value, section=None, file_path=None): "circuit_mpl_style", "circuit_mpl_style_path", "circuit_reverse_bits", + "circuit_idle_wires", "transpile_optimization_level", "parallel", "num_processes", diff --git a/qiskit/visualization/circuit/circuit_visualization.py b/qiskit/visualization/circuit/circuit_visualization.py index bea6021c23a8..a2da4de4daec 100644 --- a/qiskit/visualization/circuit/circuit_visualization.py +++ b/qiskit/visualization/circuit/circuit_visualization.py @@ -63,7 +63,7 @@ def circuit_drawer( reverse_bits: bool | None = None, justify: str | None = None, vertical_compression: str | None = "medium", - idle_wires: bool = True, + idle_wires: bool | None = None, with_layout: bool = True, fold: int | None = None, # The type of ax is matplotlib.axes.Axes, but this is not a fixed dependency, so cannot be @@ -115,7 +115,7 @@ def circuit_drawer( output: Select the output method to use for drawing the circuit. Valid choices are ``text``, ``mpl``, ``latex``, ``latex_source``. - By default the `text` drawer is used unless the user config file + By default, the `text` drawer is used unless the user config file (usually ``~/.qiskit/settings.conf``) has an alternative backend set as the default. For example, ``circuit_drawer = latex``. If the output kwarg is set, that backend will always be used over the default in @@ -141,7 +141,9 @@ def circuit_drawer( will take less vertical room. Default is ``medium``. Only used by the ``text`` output, will be silently ignored otherwise. idle_wires: Include idle wires (wires with no circuit elements) - in output visualization. Default is ``True``. + in output visualization. Default is ``True`` unless the + user config file (usually ``~/.qiskit/settings.conf``) has an + alternative value set. For example, ``circuit_idle_wires = False``. with_layout: Include layout information, with labels on the physical layout. Default is ``True``. fold: Sets pagination. It can be disabled using -1. In ``text``, @@ -200,6 +202,7 @@ def circuit_drawer( # Get default from config file else use text default_output = "text" default_reverse_bits = False + default_idle_wires = config.get("circuit_idle_wires", True) if config: default_output = config.get("circuit_drawer", "text") if default_output == "auto": @@ -215,6 +218,9 @@ def circuit_drawer( if reverse_bits is None: reverse_bits = default_reverse_bits + if idle_wires is None: + idle_wires = default_idle_wires + if wire_order is not None and reverse_bits: raise VisualizationError( "The wire_order option cannot be set when the reverse_bits option is True." diff --git a/releasenotes/notes/workaroud_12361-994d0ac2d2a6ed41.yaml b/releasenotes/notes/workaroud_12361-994d0ac2d2a6ed41.yaml new file mode 100644 index 000000000000..9c19be117ed2 --- /dev/null +++ b/releasenotes/notes/workaroud_12361-994d0ac2d2a6ed41.yaml @@ -0,0 +1,14 @@ +--- +features_visualization: + - | + The user configuration file has a new option ``circuit_idle_wires``, which takes a Boolean + value. This allows users to set their preferred default behavior of the ``idle_wires`` option + of the circuit drawers :meth:`.QuantumCircuit.draw` and :func:`.circuit_drawer`. For example, + adding a section to ``~/.qiskit/settings.conf`` with: + + .. code-block:: text + + [default] + circuit_idle_wires = false + + will change the default to display the bits in reverse order. diff --git a/test/python/test_user_config.py b/test/python/test_user_config.py index e3e012134639..ecc4ffaaa966 100644 --- a/test/python/test_user_config.py +++ b/test/python/test_user_config.py @@ -94,6 +94,31 @@ def test_circuit_reverse_bits_valid(self): config.read_config_file() self.assertEqual({"circuit_reverse_bits": False}, config.settings) + def test_invalid_circuit_idle_wires(self): + test_config = """ + [default] + circuit_idle_wires = Neither + """ + self.addCleanup(os.remove, self.file_path) + with open(self.file_path, "w") as file: + file.write(test_config) + file.flush() + config = user_config.UserConfig(self.file_path) + self.assertRaises(exceptions.QiskitUserConfigError, config.read_config_file) + + def test_circuit_idle_wires_valid(self): + test_config = """ + [default] + circuit_idle_wires = true + """ + self.addCleanup(os.remove, self.file_path) + with open(self.file_path, "w") as file: + file.write(test_config) + file.flush() + config = user_config.UserConfig(self.file_path) + config.read_config_file() + self.assertEqual({"circuit_idle_wires": True}, config.settings) + def test_optimization_level_valid(self): test_config = """ [default] @@ -152,6 +177,7 @@ def test_all_options_valid(self): circuit_mpl_style = default circuit_mpl_style_path = ~:~/.qiskit circuit_reverse_bits = false + circuit_idle_wires = true transpile_optimization_level = 3 suppress_packaging_warnings = true parallel = false @@ -170,6 +196,7 @@ def test_all_options_valid(self): "circuit_mpl_style": "default", "circuit_mpl_style_path": ["~", "~/.qiskit"], "circuit_reverse_bits": False, + "circuit_idle_wires": True, "transpile_optimization_level": 3, "num_processes": 15, "parallel_enabled": False, @@ -184,6 +211,7 @@ def test_set_config_all_options_valid(self): user_config.set_config("circuit_mpl_style", "default", file_path=self.file_path) user_config.set_config("circuit_mpl_style_path", "~:~/.qiskit", file_path=self.file_path) user_config.set_config("circuit_reverse_bits", "false", file_path=self.file_path) + user_config.set_config("circuit_idle_wires", "true", file_path=self.file_path) user_config.set_config("transpile_optimization_level", "3", file_path=self.file_path) user_config.set_config("parallel", "false", file_path=self.file_path) user_config.set_config("num_processes", "15", file_path=self.file_path) @@ -198,6 +226,7 @@ def test_set_config_all_options_valid(self): "circuit_mpl_style": "default", "circuit_mpl_style_path": ["~", "~/.qiskit"], "circuit_reverse_bits": False, + "circuit_idle_wires": True, "transpile_optimization_level": 3, "num_processes": 15, "parallel_enabled": False, diff --git a/test/python/visualization/test_circuit_text_drawer.py b/test/python/visualization/test_circuit_text_drawer.py index 5f72a7d1bbbc..3f018c085109 100644 --- a/test/python/visualization/test_circuit_text_drawer.py +++ b/test/python/visualization/test_circuit_text_drawer.py @@ -495,6 +495,58 @@ def test_text_reverse_bits_read_from_config(self): test_reverse = str(circuit_drawer(circuit, output="text")) self.assertEqual(test_reverse, expected_reverse) + def test_text_idle_wires_read_from_config(self): + """Swap drawing with idle_wires set in the configuration file.""" + expected_with = "\n".join( + [ + " ┌───┐", + "q1_0: ┤ H ├", + " └───┘", + "q1_1: ─────", + " ┌───┐", + "q2_0: ┤ H ├", + " └───┘", + "q2_1: ─────", + " ", + ] + ) + expected_without = "\n".join( + [ + " ┌───┐", + "q1_0: ┤ H ├", + " ├───┤", + "q2_0: ┤ H ├", + " └───┘", + ] + ) + qr1 = QuantumRegister(2, "q1") + qr2 = QuantumRegister(2, "q2") + circuit = QuantumCircuit(qr1, qr2) + circuit.h(qr1[0]) + circuit.h(qr2[0]) + + self.assertEqual( + str( + circuit_drawer( + circuit, + output="text", + ) + ), + expected_with, + ) + + config_content = """ + [default] + circuit_idle_wires = false + """ + with tempfile.TemporaryDirectory() as dir_path: + file_path = pathlib.Path(dir_path) / "qiskit.conf" + with open(file_path, "w") as fptr: + fptr.write(config_content) + with unittest.mock.patch.dict(os.environ, {"QISKIT_SETTINGS": str(file_path)}): + test_without = str(circuit_drawer(circuit, output="text")) + self.assertEqual(test_without, expected_without) + def test_text_cswap(self): """CSwap drawing.""" expected = "\n".join( @@ -514,6 +566,7 @@ def test_text_cswap(self): circuit.cswap(qr[0], qr[1], qr[2]) circuit.cswap(qr[1], qr[0], qr[2]) circuit.cswap(qr[2], qr[1], qr[0]) + self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_cswap_reverse_bits(self): @@ -4223,7 +4276,6 @@ def test_text_4q_2c(self): cr6 = ClassicalRegister(6, "c") circuit = QuantumCircuit(qr6, cr6) circuit.append(inst, qr6[1:5], cr6[1:3]) - self.assertEqual(str(circuit_drawer(circuit, output="text", initial_state=True)), expected) def test_text_2q_1c(self): @@ -5668,7 +5720,6 @@ def test_registerless_one_bit(self): qry = QuantumRegister(1, "qry") crx = ClassicalRegister(2, "crx") circuit = QuantumCircuit(qrx, [Qubit(), Qubit()], qry, [Clbit(), Clbit()], crx) - self.assertEqual(circuit.draw(output="text", cregbundle=True).single_string(), expected) From e47fb682aa8bd9270d6a4c89647be6bdf4683d85 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Mon, 27 May 2024 13:34:56 +0200 Subject: [PATCH 2/4] docs --- qiskit/circuit/quantumcircuit.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 9ec5e1e022aa..22186135b07d 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -1757,9 +1757,9 @@ def compose( this can be anything that :obj:`.append` will accept. qubits (list[Qubit|int]): qubits of self to compose onto. clbits (list[Clbit|int]): clbits of self to compose onto. - front (bool): If True, front composition will be performed. This is not possible within + front (bool): If ``True``, front composition will be performed. This is not possible within control-flow builder context managers. - inplace (bool): If True, modify the object. Otherwise, return composed circuit. + inplace (bool): If ``True``, modify the object. Otherwise, return composed circuit. copy (bool): If ``True`` (the default), then the input is treated as shared, and any contained instructions will be copied, if they might need to be mutated in the future. You can set this to ``False`` if the input should be considered owned by @@ -2084,7 +2084,7 @@ def tensor(self, other: "QuantumCircuit", inplace: bool = False) -> Optional["Qu tensored.draw('mpl') Returns: - QuantumCircuit: The tensored circuit (returns None if inplace==True). + QuantumCircuit: The tensored circuit (returns ``None`` if ``inplace=True``). """ num_qubits = self.num_qubits + other.num_qubits num_clbits = self.num_clbits + other.num_clbits @@ -3157,7 +3157,7 @@ def draw( Args: output: Select the output method to use for drawing the circuit. Valid choices are ``text``, ``mpl``, ``latex``, ``latex_source``. - By default, the `text` drawer is used unless the user config file + By default, the ``text`` drawer is used unless the user config file (usually ``~/.qiskit/settings.conf``) has an alternative backend set as the default. For example, ``circuit_drawer = latex``. If the output kwarg is set, that backend will always be used over the default in @@ -3294,7 +3294,7 @@ def size( Args: filter_function (callable): a function to filter out some instructions. Should take as input a tuple of (Instruction, list(Qubit), list(Clbit)). - By default filters out "directives", such as barrier or snapshot. + By default, filters out "directives", such as barrier or snapshot. Returns: int: Total number of gate operations. @@ -3313,7 +3313,7 @@ def depth( filter_function: A function to decide which instructions count to increase depth. Should take as a single positional input a :class:`CircuitInstruction`. Instructions for which the function returns ``False`` are ignored in the - computation of the circuit depth. By default filters out "directives", such as + computation of the circuit depth. By default, filters out "directives", such as :class:`.Barrier`. Returns: @@ -3351,7 +3351,7 @@ def depth( # Here we are playing a modified version of # Tetris where we stack gates, but multi-qubit # gates, or measurements have a block for each - # qubit or cbit that are connected by a virtual + # qubit or clbit that are connected by a virtual # line so that they all stacked at the same depth. # Conditional gates act on all cbits in the register # they are conditioned on. @@ -3463,7 +3463,7 @@ def num_connected_components(self, unitary_only: bool = False) -> int: bits = self.qubits if unitary_only else (self.qubits + self.clbits) bit_indices: dict[Qubit | Clbit, int] = {bit: idx for idx, bit in enumerate(bits)} - # Start with each qubit or cbit being its own subgraph. + # Start with each qubit or clbit being its own subgraph. sub_graphs = [[bit] for bit in range(len(bit_indices))] num_sub_graphs = len(sub_graphs) @@ -3834,7 +3834,7 @@ def measure_active(self, inplace: bool = True) -> Optional["QuantumCircuit"]: inplace (bool): All measurements inplace or return new circuit. Returns: - QuantumCircuit: Returns circuit with measurements when `inplace = False`. + QuantumCircuit: Returns circuit with measurements when ``inplace = False``. """ from qiskit.converters.circuit_to_dag import circuit_to_dag From c1efe88a916fa4f8d81d470aa7885892eaa72784 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Mon, 27 May 2024 13:45:22 +0200 Subject: [PATCH 3/4] 11339 --- qiskit/circuit/quantumcircuit.py | 2 +- test/python/transpiler/test_basis_translator.py | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 22186135b07d..0ce330b3d702 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -5722,7 +5722,7 @@ class to prepare the qubits in a specified state. * Statevector or vector of complex amplitudes to initialize to. * Labels of basis states of the Pauli eigenstates Z, X, Y. See :meth:`.Statevector.from_label`. Notice the order of the labels is reversed with - respect to the qubit index to be applied to. Example label '01' initializes the + respect to the qubit index to be applied to. Example label ``'01'`` initializes the qubit zero to :math:`|1\rangle` and the qubit one to :math:`|0\rangle`. * An integer that is used as a bitmap indicating which qubits to initialize to :math:`|1\rangle`. Example: setting params to 5 would initialize qubit 0 and qubit diff --git a/test/python/transpiler/test_basis_translator.py b/test/python/transpiler/test_basis_translator.py index 24e5e68ba987..fc933cd8f666 100644 --- a/test/python/transpiler/test_basis_translator.py +++ b/test/python/transpiler/test_basis_translator.py @@ -1150,15 +1150,16 @@ def setUp(self): self.target.add_instruction(CXGate(), cx_props) def test_2q_with_non_global_1q(self): - """Test translation works with a 2q gate on an non-global 1q basis.""" + """Test translation works with a 2q gate on a non-global 1q basis.""" qc = QuantumCircuit(2) qc.cz(0, 1) bt_pass = BasisTranslator(std_eqlib, target_basis=None, target=self.target) output = bt_pass(qc) - # We need a second run of BasisTranslator to correct gates outside of - # the target basis. This is a known isssue, see: - # https://docs.quantum.ibm.com/api/qiskit/release-notes/0.33#known-issues + # We need a second run of BasisTranslator to correct gates outside + # the target basis. This is a known issue, see: + # https://github.com/Qiskit/qiskit/issues/11339 + # TODO: remove the second bt_pass call once fixed. output = bt_pass(output) expected = QuantumCircuit(2) expected.rz(pi, 1) From eb037c85e8576454722f1e422cc2011c70764ef7 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Mon, 27 May 2024 14:37:47 +0200 Subject: [PATCH 4/4] Update qiskit/visualization/circuit/circuit_visualization.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> --- qiskit/visualization/circuit/circuit_visualization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/visualization/circuit/circuit_visualization.py b/qiskit/visualization/circuit/circuit_visualization.py index a2da4de4daec..a1672dc1676c 100644 --- a/qiskit/visualization/circuit/circuit_visualization.py +++ b/qiskit/visualization/circuit/circuit_visualization.py @@ -115,7 +115,7 @@ def circuit_drawer( output: Select the output method to use for drawing the circuit. Valid choices are ``text``, ``mpl``, ``latex``, ``latex_source``. - By default, the `text` drawer is used unless the user config file + By default, the ``text`` drawer is used unless the user config file (usually ``~/.qiskit/settings.conf``) has an alternative backend set as the default. For example, ``circuit_drawer = latex``. If the output kwarg is set, that backend will always be used over the default in