diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index ea4361fd8255..606d0e04373a 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -1757,14 +1757,14 @@ 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 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: @@ -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 @@ -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``, @@ -3292,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. @@ -3314,7 +3316,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: @@ -3445,7 +3447,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) @@ -3816,7 +3818,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 @@ -5704,7 +5706,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/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..a1672dc1676c 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/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) 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)