Repository: Qiskit/qiskit

Issue:
Allow formatting in names of custom gates
### What is the expected enhancement?

Allow formatting of custom gate names in circuit diagrams. The names of custom gates are shown as plain text. It would be nice to format these as latex or html.
For example the following code generates a circuit diagram with a custom gate
```
from qiskit import QuantumCircuit
from qiskit.circuit import Gate

qc = QuantumCircuit(1, 1)
qc.append(Gate(name=r'X_t', num_qubits=1, params=[]), [0])
qc.measure([0, ], [0,])
qc.draw(output='latex', interactive=False, filename='test.png')
```
The custom gate is labelled as `X_t`, but I want the name to be formatted as an `X` with subscript `t`. The custom gate name is transformed by `utf8tolatex`, but it is escaping the `_`.
Also variations as `qc.append(Gate(name=r'$X_t$', num_qubits=1, params=[]), [0])` do not work.

Note: here is also a discussion on stack exchange on this: https://quantumcomputing.stackexchange.com/questions/8329/how-to-plot-custom-gate-labels-in-qiskit



Discussion:

Resolving patch:
diff --git a/qiskit/visualization/latex.py b/qiskit/visualization/latex.py
--- a/qiskit/visualization/latex.py
+++ b/qiskit/visualization/latex.py
@@ -22,17 +22,11 @@
 import math
 import re
 
-try:
-    from pylatexenc.latexencode import utf8tolatex
-
-    HAS_PYLATEX = True
-except ImportError:
-    HAS_PYLATEX = False
-
 import numpy as np
 from qiskit.visualization import qcstyle as _qcstyle
 from qiskit.visualization import exceptions
 from .tools.pi_check import pi_check
+from .utils import generate_latex_label
 
 
 class QCircuitImage:
@@ -63,11 +57,6 @@ def __init__(self, qubits, clbits, ops, scale, style=None,
         Raises:
             ImportError: If pylatexenc is not installed
         """
-        if not HAS_PYLATEX:
-            raise ImportError('The latex and latex_source drawers need '
-                              'pylatexenc installed. Run "pip install '
-                              'pylatexenc" before using the latex or '
-                              'latex_source drawers.')
         # style sheet
         self._style = _qcstyle.BWStyle()
         if style:
@@ -374,7 +363,7 @@ def _build_latex_array(self, aliases=None):
                                       'b').zfill(self.cregs[if_reg])[::-1]
                 if op.name not in ['measure', 'barrier', 'snapshot', 'load',
                                    'save', 'noise']:
-                    nm = utf8tolatex(op.name).replace(" ", "\\,")
+                    nm = generate_latex_label(op.name).replace(" ", "\\,")
                     qarglist = op.qargs
                     if aliases is not None:
                         qarglist = map(lambda x: aliases[x], qarglist)
diff --git a/qiskit/visualization/utils.py b/qiskit/visualization/utils.py
--- a/qiskit/visualization/utils.py
+++ b/qiskit/visualization/utils.py
@@ -12,9 +12,14 @@
 # copyright notice, and modified files need to carry a notice indicating
 # that they have been altered from the originals.
 
+# pylint: disable=anomalous-backslash-in-string
+
 """Common visualization utilities."""
 
+import re
+
 import numpy as np
+
 from qiskit.converters import circuit_to_dag
 from qiskit.visualization.exceptions import VisualizationError
 
@@ -24,6 +29,36 @@
 except ImportError:
     HAS_PIL = False
 
+try:
+    from pylatexenc.latexencode import utf8tolatex
+
+    HAS_PYLATEX = True
+except ImportError:
+    HAS_PYLATEX = False
+
+
+def generate_latex_label(label):
+    """Convert a label to a valid latex string."""
+    if not HAS_PYLATEX:
+        raise ImportError('The latex and latex_source drawers need '
+                          'pylatexenc installed. Run "pip install '
+                          'pylatexenc" before using the latex or '
+                          'latex_source drawers.')
+
+    regex = re.compile(r"(?<!\\)\$(.*)(?<!\\)\$")
+    match = regex.search(label)
+    if not match:
+        label = label.replace('\$', '$')  # noqa
+        return utf8tolatex(label)
+    else:
+        mathmode_string = match.group(1).replace('\$', '$')  # noqa
+        before_match = label[:match.start()]
+        before_match = before_match.replace('\$', '$')  # noqa
+        after_match = label[match.end():]
+        after_match = after_match.replace('\$', '$')  # noqa
+        return utf8tolatex(before_match) + mathmode_string + utf8tolatex(
+            after_match)
+
 
 def _validate_input_state(quantum_state):
     """Validates the input to state visualization functions.


Test patch:
diff --git a/test/python/visualization/test_visualization.py b/test/python/visualization/test_visualization.py
--- a/test/python/visualization/test_visualization.py
+++ b/test/python/visualization/test_visualization.py
@@ -12,6 +12,8 @@
 # copyright notice, and modified files need to carry a notice indicating
 # that they have been altered from the originals.
 
+# pylint: disable=anomalous-backslash-in-string
+
 """Tests for visualization tools."""
 
 import os
@@ -382,6 +384,39 @@ def test_get_layered_instructions_right_justification_less_simple(self):
         self.assertEqual(r_exp,
                          [[(op.name, op.qargs, op.cargs) for op in ops] for ops in layered_ops])
 
+    def test_generate_latex_label_nomathmode(self):
+        """Test generate latex label default."""
+        self.assertEqual('abc', utils.generate_latex_label('abc'))
+
+    def test_generate_latex_label_nomathmode_utf8char(self):
+        """Test generate latex label utf8 characters."""
+        self.assertEqual('{\\ensuremath{\\iiint}}X{\\ensuremath{\\forall}}Y',
+                         utils.generate_latex_label('∭X∀Y'))
+
+    def test_generate_latex_label_mathmode_utf8char(self):
+        """Test generate latex label mathtext with utf8."""
+        self.assertEqual(
+            'abc_{\\ensuremath{\\iiint}}X{\\ensuremath{\\forall}}Y',
+            utils.generate_latex_label('$abc_$∭X∀Y'))
+
+    def test_generate_latex_label_mathmode_underscore_outside(self):
+        """Test generate latex label with underscore outside mathmode."""
+        self.assertEqual(
+            'abc{\\_}{\\ensuremath{\\iiint}}X{\\ensuremath{\\forall}}Y',
+            utils.generate_latex_label('$abc$_∭X∀Y'))
+
+    def test_generate_latex_label_escaped_dollar_signs(self):
+        """Test generate latex label with escaped dollarsign."""
+        self.assertEqual(
+            '{\\$}{\\ensuremath{\\forall}}{\\$}',
+            utils.generate_latex_label('\$∀\$'))  # noqa
+
+    def test_generate_latex_label_escaped_dollar_sign_in_mathmode(self):
+        """Test generate latex label with escaped dollar sign in mathmode."""
+        self.assertEqual(
+            'a$bc{\\_}{\\ensuremath{\\iiint}}X{\\ensuremath{\\forall}}Y',
+            utils.generate_latex_label('$a$bc$_∭X∀Y'))  # noqa
+
 
 if __name__ == '__main__':
     unittest.main(verbosity=2)