Add dual of mpl mathtext syntax for latex drawer labels (#3224)

* Add dual of mpl mathtext syntax for latex drawer labels

This commit adds a new feature to the latex drawer that adds support for
a similar syntax to matplotlib's mathtext to labels on the latex drawer.
In matplotlib if you specify a string between two dollar signs (for
example $\gamma$) that string will be parsed with mpl's native TeX
expression parser and that will be used to layout and render the string
per the TeX syntax. This uses the same syntax as mathtext, and enables
users to write raw latex syntax as gate labels between a pair of dollar
signs. All characters outside of the dollar signs are treated as utf8
and get encoded by pylatexenc to create valid latex, however all text
inside the dollar signs get passed verbatim to the output latex. This
enables users to mix and match having their unicode encoded for them and
manually passing latex

Fixes #3171

* Fix lint

* Use jupyter-execute instead of describing diagram

* Revert "Use jupyter-execute instead of describing diagram"

This reverts commit ab7bc18c39. Running
the code example in jupyter-execute will require a latex distribution
with qcircuit to be installed. That adds a lot of run time to the doc
build and a non-obvious dependency. To avoid this, we'll just rely on
the code example and a description of the output.
This commit is contained in:
Matthew Treinish 2019-10-28 13:54:59 -04:00 committed by Luciano
parent a94e8a5b48
commit fb87e7c2e2
4 changed files with 103 additions and 13 deletions

View File

@ -22,17 +22,11 @@ import json
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 @@ class QCircuitImage:
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 @@ class QCircuitImage:
'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)

View File

@ -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 @@ try:
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.

View File

@ -0,0 +1,31 @@
---
features:
- |
The ``latex`` output mode for ``qiskit.visualization.circuit_drawer()`` and
the ``qiskit.circuit.QuantumCircuit.draw()`` method now has a mode to
passthrough raw latex from gate labels. The syntax for doing this mirrors
matplotlib's
(mathtext mode)[https://matplotlib.org/tutorials/text/mathtext.html]
syntax. Any portion of a label string between a pair of `$`s will be treated
as raw latex and passed directly to the output latex. This can be used
Prior to this release all gate labels were run through a utf8 -> latex
conversion to make sure that the output latex would compile the string as
expected. This is still what happens for all portions of a label outside
the `$`s. Also if you want to use a dollar sign in your label make sure
you escape it in the label string (ie `'\$'`).
You can mix and match this passthrough with the utf8 -> latex conversion to
create the exact label you want, for example::
from qiskit import circuit
circ = circuit.QuantumCircuit(2)
circ.h([0, 1])
circ.append(circuit.Gate(name='α_gate', num_qubits=1, params=[0]), [0])
circ.append(circuit.Gate(name='α_gate$_2$', num_qubits=1, params=[0]), [1])
circ.append(circuit.Gate(name='\$α\$_gate', num_qubits=1, params=[0]), [1])
circ.draw(output='latex')
will now render the first custom gate's label as ``α_gate``, the second
will be ``α_gate`` with a 2 subscript, and the last custom gate's label
will be ``$α$_gate``.

View File

@ -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 @@ c1_0: 0 ════════════════════════
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)