Skip to content

Commit

Permalink
Add option to user config to control idle_wires in circuit drawer (#…
Browse files Browse the repository at this point in the history
…12462)

* Add option to user config to control idle_wires in circuit drawer

Co-Authored-By: diemilio <diemilio@live.com>

* docs

* 11339

* Update qiskit/visualization/circuit/circuit_visualization.py

Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com>

---------

Co-authored-by: diemilio <diemilio@live.com>
Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com>
  • Loading branch information
3 people committed Jun 12, 2024
1 parent bc685d3 commit b933f6d
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 22 deletions.
28 changes: 15 additions & 13 deletions qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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``,
Expand Down Expand Up @@ -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.
Expand All @@ -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:
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions qiskit/user_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class UserConfig:
circuit_mpl_style = default
circuit_mpl_style_path = ~/.qiskit:<default location>
circuit_reverse_bits = True
circuit_idle_wires = False
transpile_optimization_level = 1
parallel = False
num_processes = 4
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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",
Expand Down
12 changes: 9 additions & 3 deletions qiskit/visualization/circuit/circuit_visualization.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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``,
Expand Down Expand Up @@ -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":
Expand All @@ -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."
Expand Down
14 changes: 14 additions & 0 deletions releasenotes/notes/workaroud_12361-994d0ac2d2a6ed41.yaml
Original file line number Diff line number Diff line change
@@ -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.
29 changes: 29 additions & 0 deletions test/python/test_user_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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)
Expand All @@ -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,
Expand Down
9 changes: 5 additions & 4 deletions test/python/transpiler/test_basis_translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
55 changes: 53 additions & 2 deletions test/python/visualization/test_circuit_text_drawer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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)


Expand Down

0 comments on commit b933f6d

Please sign in to comment.