Repository: Qiskit/qiskit-terra (python)

Issue:
Add option in user config file to control `reverse_bits` in `circuit_drawer`
### What should we add?

Currently, the user config file supports `circuit_drawer` display options such as `output` (`"text"`, "`mpl`", etc.). Including the ability to change the `reverse_bits` option would allow users that prefer setting this option to `True` to not have to explicitly set this parameter every time a circuit is drawn.

Pull request description:
Add option to user config to control reverse_bits in circuit drawer 
<!--
⚠️ If you do not respect this template, your pull request will be closed.
⚠️ Your pull request title should be short detailed and understandable for all.
⚠️ Also, please add a release note file using reno if the change needs to be
  documented in the release notes.
⚠️ If your pull request fixes an open issue, please link to the issue.

- [ ] I have added the tests to cover my changes.
- [ ] I have updated the documentation accordingly.
- [ ] I have read the CONTRIBUTING document.
-->

### Summary
Fixes #9150. 

### Details and comments
This PR adds the option `circuit_reverse_bits` to the user config file to modify the default behavior of the `reverse_bits` option in the `circuit_drawer`. For example, the following configuration in the `settings.conf` file:
```text
[default]
circuit_drawer = mpl
state_drawer = latex
circuit_reverse_bits = True
```
the circuit drawer will display a circuit with the most significant bit at the top without explicitly passing the `reverse_bits=True` option to the `draw()` method:
``` python
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
n = 4
qr = QuantumRegister(n, name='q')
cr = ClassicalRegister(n, name='c')
qc = QuantumCircuit(qr,cr)
qc.h(range(n))
for i in range(n-1,0,-1):
    qc.cx(i,i-1)
qc.barrier()
qc.measure(qr,cr)
qc.draw()
```
<img width="513" alt="image" src="https://user-images.githubusercontent.com/65074936/203426621-44eff852-bae4-4349-9be2-b78f73e88783.png">

Patch:
diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
index ca30fe556..55cd82dcf 100644
--- a/qiskit/circuit/quantumcircuit.py
+++ b/qiskit/circuit/quantumcircuit.py
@@ -1752,7 +1752,7 @@ class QuantumCircuit:
         style: Optional[Union[dict, str]] = None,
         interactive: bool = False,
         plot_barriers: bool = True,
-        reverse_bits: bool = False,
+        reverse_bits: bool = None,
         justify: Optional[str] = None,
         vertical_compression: Optional[str] = "medium",
         idle_wires: bool = True,
@@ -1813,7 +1813,9 @@ class QuantumCircuit:
                 `latex_source` output type this has no effect and will be silently
                 ignored. Defaults to False.
             reverse_bits (bool): when set to True, reverse the bit order inside
-                registers for the output visualization. Defaults to False.
+                registers for the output visualization. Defaults to False unless the
+                user config file (usually ``~/.qiskit/settings.conf``) has an
+                alternative value set. For example, ``circuit_reverse_bits = True``.
             plot_barriers (bool): enable/disable drawing barriers in the output
                 circuit. Defaults to True.
             justify (string): options are ``left``, ``right`` or ``none``. If
diff --git a/qiskit/user_config.py b/qiskit/user_config.py
index 8da0d8e27..666bf53d9 100644
--- a/qiskit/user_config.py
+++ b/qiskit/user_config.py
@@ -30,6 +30,7 @@ class UserConfig:
     circuit_drawer = mpl
     circuit_mpl_style = default
     circuit_mpl_style_path = ~/.qiskit:<default location>
+    circuit_reverse_bits = True
     transpile_optimization_level = 1
     parallel = False
     num_processes = 4
@@ -117,6 +118,18 @@ class UserConfig:
                         )
                 self.settings["circuit_mpl_style_path"] = cpath_list
 
+            # Parse circuit_reverse_bits
+            try:
+                circuit_reverse_bits = self.config_parser.getboolean(
+                    "default", "circuit_reverse_bits", fallback=None
+                )
+            except ValueError as err:
+                raise exceptions.QiskitUserConfigError(
+                    f"Value assigned to circuit_reverse_bits is not valid. {str(err)}"
+                )
+            if circuit_reverse_bits is not None:
+                self.settings["circuit_reverse_bits"] = circuit_reverse_bits
+
             # Parse transpile_optimization_level
             transpile_optimization_level = self.config_parser.getint(
                 "default", "transpile_optimization_level", fallback=-1
@@ -177,6 +190,7 @@ def set_config(key, value, section=None, file_path=None):
         "circuit_drawer",
         "circuit_mpl_style",
         "circuit_mpl_style_path",
+        "circuit_reverse_bits",
         "transpile_optimization_level",
         "parallel",
         "num_processes",
diff --git a/qiskit/visualization/circuit/circuit_visualization.py b/qiskit/visualization/circuit/circuit_visualization.py
index 301cebbd6..fce33fed4 100644
--- a/qiskit/visualization/circuit/circuit_visualization.py
+++ b/qiskit/visualization/circuit/circuit_visualization.py
@@ -51,7 +51,7 @@ def circuit_drawer(
     output=None,
     interactive=False,
     plot_barriers=True,
-    reverse_bits=False,
+    reverse_bits=None,
     justify=None,
     vertical_compression="medium",
     idle_wires=True,
@@ -111,7 +111,9 @@ def circuit_drawer(
             `latex_source` output type this has no effect and will be silently
             ignored. Defaults to False.
         reverse_bits (bool): when set to True, reverse the bit order inside
-            registers for the output visualization. Defaults to False.
+            registers for the output visualization. Defaults to False unless the
+            user config file (usually ``~/.qiskit/settings.conf``) has an
+            alternative value set. For example, ``circuit_reverse_bits = True``.
         plot_barriers (bool): enable/disable drawing barriers in the output
             circuit. Defaults to True.
         justify (string): options are ``left``, ``right`` or ``none``. If
@@ -182,6 +184,7 @@ def circuit_drawer(
     config = user_config.get_config()
     # Get default from config file else use text
     default_output = "text"
+    default_reverse_bits = False
     if config:
         default_output = config.get("circuit_drawer", "text")
         if default_output == "auto":
@@ -189,9 +192,14 @@ def circuit_drawer(
                 default_output = "mpl"
             else:
                 default_output = "text"
+        if wire_order is None:
+            default_reverse_bits = config.get("circuit_reverse_bits", False)
     if output is None:
         output = default_output
 
+    if reverse_bits is None:
+        reverse_bits = default_reverse_bits
+
     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/add-reverse-bits-to-user-config-options-0e465e6e92d5b49f.yaml b/releasenotes/notes/add-reverse-bits-to-user-config-options-0e465e6e92d5b49f.yaml
new file mode 100644
index 000000000..b01167ee5
--- /dev/null
+++ b/releasenotes/notes/add-reverse-bits-to-user-config-options-0e465e6e92d5b49f.yaml
@@ -0,0 +1,14 @@
+---
+features:
+  - |
+    The user configuration file has a new option ``circuit_reverse_bits``, which takes a Boolean
+    value. This allows users to set their preferred default behavior of the ``reverse_bits`` 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_reverse_bits = True
+
+    will change the default to display the bits in reverse order.


Test patch:
diff --git a/test/python/test_user_config.py b/test/python/test_user_config.py
index 4bb272d27..b122c18b8 100644
--- a/test/python/test_user_config.py
+++ b/test/python/test_user_config.py
@@ -69,6 +69,31 @@ class TestUserConfig(QiskitTestCase):
             config.read_config_file()
             self.assertEqual({"circuit_drawer": "latex"}, config.settings)
 
+    def test_invalid_circuit_reverse_bits(self):
+        test_config = """
+        [default]
+        circuit_reverse_bits = 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_reverse_bits_valid(self):
+        test_config = """
+        [default]
+        circuit_reverse_bits = false
+        """
+        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_reverse_bits": False}, config.settings)
+
     def test_optimization_level_valid(self):
         test_config = """
         [default]
@@ -126,6 +151,7 @@ class TestUserConfig(QiskitTestCase):
         circuit_drawer = latex
         circuit_mpl_style = default
         circuit_mpl_style_path = ~:~/.qiskit
+        circuit_reverse_bits = false
         transpile_optimization_level = 3
         suppress_packaging_warnings = true
         parallel = false
@@ -143,6 +169,7 @@ class TestUserConfig(QiskitTestCase):
                 "circuit_drawer": "latex",
                 "circuit_mpl_style": "default",
                 "circuit_mpl_style_path": ["~", "~/.qiskit"],
+                "circuit_reverse_bits": False,
                 "transpile_optimization_level": 3,
                 "num_processes": 15,
                 "parallel_enabled": False,
@@ -156,6 +183,7 @@ class TestUserConfig(QiskitTestCase):
         user_config.set_config("circuit_drawer", "latex", file_path=self.file_path)
         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("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)
@@ -169,6 +197,7 @@ class TestUserConfig(QiskitTestCase):
                 "circuit_drawer": "latex",
                 "circuit_mpl_style": "default",
                 "circuit_mpl_style_path": ["~", "~/.qiskit"],
+                "circuit_reverse_bits": False,
                 "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 839193934..6e70edc78 100644
--- a/test/python/visualization/test_circuit_text_drawer.py
+++ b/test/python/visualization/test_circuit_text_drawer.py
@@ -12,8 +12,10 @@
 
 """ `_text_circuit_drawer` "draws" a circuit in "ascii art" """
 
+import pathlib
 import os
-import unittest
+import tempfile
+import unittest.mock
 from codecs import encode
 from math import pi
 
@@ -25,6 +27,7 @@ from qiskit.quantum_info.operators import SuperOp
 from qiskit.quantum_info.random import random_unitary
 from qiskit.test import QiskitTestCase
 from qiskit.transpiler.layout import Layout, TranspileLayout
+from qiskit.visualization import circuit_drawer
 from qiskit.visualization.circuit import text as elements
 from qiskit.visualization.circuit.circuit_visualization import _text_circuit_drawer
 from qiskit.extensions import UnitaryGate, HamiltonianGate
@@ -382,6 +385,53 @@ class TestTextDrawerGatesInCircuit(QiskitTestCase):
         circuit.swap(qr1, qr2)
         self.assertEqual(str(_text_circuit_drawer(circuit, reverse_bits=True)), expected)
 
+    def test_text_reverse_bits_read_from_config(self):
+        """Swap drawing with reverse_bits set in the configuration file."""
+        expected_forward = "\n".join(
+            [
+                "            ",
+                "q1_0: ─X────",
+                "       │    ",
+                "q1_1: ─┼──X─",
+                "       │  │ ",
+                "q2_0: ─X──┼─",
+                "          │ ",
+                "q2_1: ────X─",
+                "            ",
+            ]
+        )
+        expected_reverse = "\n".join(
+            [
+                "            ",
+                "q2_1: ────X─",
+                "          │ ",
+                "q2_0: ─X──┼─",
+                "       │  │ ",
+                "q1_1: ─┼──X─",
+                "       │    ",
+                "q1_0: ─X────",
+                "            ",
+            ]
+        )
+        qr1 = QuantumRegister(2, "q1")
+        qr2 = QuantumRegister(2, "q2")
+        circuit = QuantumCircuit(qr1, qr2)
+        circuit.swap(qr1, qr2)
+
+        self.assertEqual(str(circuit_drawer(circuit, output="text")), expected_forward)
+
+        config_content = """
+            [default]
+            circuit_reverse_bits = true
+        """
+        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_reverse = str(circuit_drawer(circuit, output="text"))
+        self.assertEqual(test_reverse, expected_reverse)
+
     def test_text_cswap(self):
         """CSwap drawing."""
         expected = "\n".join(