Repository: sympy/sympy
Pull request: Some minor fixes for LaTeX printer
<!-- Your title above should be a short description of what
was changed. Do not include the issue number in the title. -->

#### References to other Issues or PRs
<!-- If this pull request fixes an issue, write "Fixes #NNNN" in that exact
format, e.g. "Fixes #1234". See
https://github.com/blog/1506-closing-issues-via-pull-requests . Please also
write a comment on that issue linking back to this pull request once it is
open. -->
Closes #15934

#### Brief description of what is fixed or changed
Some fixes for the LaTeX printer:
 * Always use `\text` instead of `\mathrm` (see #15934 )
 * Use `mathbf` instead of `\boldmath{\mathrm` (gives identical rendering)
 * Added tests for some DiffGeom printing
 * Made a unified function for printing of transforms
 * Corrected documentation for `imaginary_unit`

#### Other comments
`\text` is from `amsmath` but we rely on that heavily elsewhere. The "problem" with `\mathrm` is that `rm` stand for a Roman (Serif) font. In certain situations, the template use non-Roman fonts for the math and then `\text` works better as it simply gives the non-italic version of the math font.

#### Release Notes

<!-- Write the release notes for this release below. See
https://github.com/sympy/sympy/wiki/Writing-Release-Notes for more information
on how to write release notes. The bot will check your release notes
automatically to see if they are formatted correctly. -->

<!-- BEGIN RELEASE NOTES -->
* printing
  * `\text` is consistently used instead of `\mathrm` in LaTeX printer to get non-italic text.
  * Added option to select output format for `re` and `im` in LaTeX printer: `gothic_re_im` 
<!-- END RELEASE NOTES -->


Problem statement:
LaTeX priniting: \mathrm vs. \text
It turns out that both `\mathrm` and `\text` is used for printing "text" in the LaTeX printing routines. It seems like there should be one that is standard and that it should be possible to change that by configuration.

### \text
 * **Pro** adapts the font to the used math-font, not necessarily a Roman font
 * **Con** Relies on the `amsmath` (or some other `ams*` package

### \mathrm
 * **Pro** works in default LaTeX without importing any packages
 * **Con** always gives a Roman (Serif) font, even if the current math font is not Roman

Some quick grep:ing tells us that there are 123 instances of `\mathrm` and 46 instances of `\text` in the .py-files.

Ideally, this should be configurable, but even then we need to decide on a standard. Personally, I will set it to `\text` the second it is implemented, but the package dependency may be confusing for newer users. (It seems like both options works in e.g. iPython but I have not explicitly tested, just not noted any issue.)

Probably the easiest way to implement it is to add a helper function `print_text` or something so people do not have to test at every single instance.

Hints from issue discussion:
I should probably add that many of these instances are probably from documentation, so the effort to implement it will not be to manually change 169 instances.
I think that \text and \mathrm are intended for different kinds of use. \mathrm is used for symbols like 'sin' that contain typically only a few letters. \text is used for non-mathematical comments like 'otherwise', and the font is the current font used outside math.
There is also `\operatorname` which seems to be the best of two worlds. When it comes to operator names. This is used 183 times. So maybe it is less of a issue than I initially thought.

The main problem with `\mathrm` as I see it is if you are using your equations in a presentation with Beamer and the template is using a san-serif font. In this case all math will also be using a sans font, including sin etc, except for stuff put in `\mathrm` (one can of course redefine that font as well, but that doesn't seem like the right thing to do.

Looking a bit more into it, the current usage of `\mathrm` consists of primarily these classes:
 * To make a d in e.g. dt for an integral or differential non-italic (can be argued what the best way is) (primarily in documentation, but maybe whatever the correct way is should be used in the code?)
 * For "for" (should be text, in documentation, `\text` is used in printing)
 * For some specific functions (in documentation), should most of the time be operatorname instead
 * For some constants in core, like NaN and True. I think this is not correct as there is not point in having NaN in Roman in an otherwise non-Roman equation

So should be quite manageable. 
It is my understanding that \operatorname will also use a math font (\mathrm, I think) but will also add automatic space around the name in question, so "\operatorname{sin}x" will print "sin", a (thin) space and "x".
You may be correct. I'll try to confirm my (old) experience with Beamer etc and see what I get out of that.
I believe our LaTeX printer already assumes amsmath is used in several places. I don't use LaTeX much, but are there instances where amsmath can't be used? 
I remembered correctly. This is an output from beamer with the different options:
![image](https://user-images.githubusercontent.com/8114497/52373097-25233b80-2a5a-11e9-8d26-d74860eda71f.png)
This shows quite clearly why `\mathrm` isn't a good idea (and why `\operatorname` is better for operators, as it uses the same spacing). The other spacing one can somehow ignore though, as of course it is possible to get it to be identical, but it requires figuring out what it is.
Regarding amsmath: I do not know of any such case. I am thinking more from the perspective that many people have a hard time getting started with LaTeX and packages and so on. But I think that `\operatorname` and `\text` is still the way to go. Unless there are some symbols that for sure should be written in a Roman font.
In latex.py, `\mathrm` is used in the printers for `bool`, `NoneType`, `Trace`, `BaseScalarField`, and `Differential` (which has some unreachable code btw). For Trace we should obviously be using `\operatorname`, but for the others I'm not so sure. `\text` is currently used only in the Piecewise printer. 
Regarding amsmath I realized that \dddot and \ddddot are from amsmath, so #15982 added a bit more amsmath dependencies... (They were already used in the physics.vector printer earlier though.)

Commit message: Changed mathrm to text and added tests

Patch to review:
diff --git a/sympy/core/numbers.py b/sympy/core/numbers.py
index 60ec5d36bec6..dec24a51b10d 100644
--- a/sympy/core/numbers.py
+++ b/sympy/core/numbers.py
@@ -3200,7 +3200,7 @@ def __new__(cls):
         return AtomicExpr.__new__(cls)
 
     def _latex(self, printer):
-        return r"\mathrm{NaN}"
+        return r"\text{NaN}"
 
     @_sympifyit('other', NotImplemented)
     def __add__(self, other):
@@ -3644,7 +3644,7 @@ class TribonacciConstant(with_metaclass(Singleton, NumberSymbol)):
     __slots__ = []
 
     def _latex(self, printer):
-        return r"\mathrm{TribonacciConstant}"
+        return r"\text{TribonacciConstant}"
 
     def __int__(self):
         return 2
diff --git a/sympy/core/tests/test_numbers.py b/sympy/core/tests/test_numbers.py
index efc36b74a3f6..a4a6c374e074 100644
--- a/sympy/core/tests/test_numbers.py
+++ b/sympy/core/tests/test_numbers.py
@@ -1666,12 +1666,12 @@ def test_latex():
     assert latex(pi) == r"\pi"
     assert latex(E) == r"e"
     assert latex(GoldenRatio) == r"\phi"
-    assert latex(TribonacciConstant) == r"\mathrm{TribonacciConstant}"
+    assert latex(TribonacciConstant) == r"\text{TribonacciConstant}"
     assert latex(EulerGamma) == r"\gamma"
     assert latex(oo) == r"\infty"
     assert latex(-oo) == r"-\infty"
     assert latex(zoo) == r"\tilde{\infty}"
-    assert latex(nan) == r"\mathrm{NaN}"
+    assert latex(nan) == r"\text{NaN}"
     assert latex(I) == r"i"
 
 
diff --git a/sympy/diffgeom/diffgeom.py b/sympy/diffgeom/diffgeom.py
index e756196756d9..7782f6b99140 100644
--- a/sympy/diffgeom/diffgeom.py
+++ b/sympy/diffgeom/diffgeom.py
@@ -39,7 +39,7 @@ def __new__(cls, name, dim):
         return obj
 
     def _latex(self, printer, *args):
-        return r'\mathrm{%s}' % self.name
+        return r'\text{%s}' % self.name
 
 
 class Patch(Basic):
@@ -83,7 +83,7 @@ def dim(self):
         return self.manifold.dim
 
     def _latex(self, printer, *args):
-        return r'\mathrm{%s}_{%s}' % (self.name, self.manifold._latex(printer, *args))
+        return r'\text{%s}_{%s}' % (self.name, self.manifold._latex(printer, *args))
 
 
 class CoordSystem(Basic):
@@ -341,7 +341,7 @@ def point_to_coords(self, point):
     ##########################################################################
 
     def _latex(self, printer, *args):
-        return r'\mathrm{%s}^{\mathrm{%s}}_{%s}' % (
+        return r'\text{%s}^{\text{%s}}_{%s}' % (
             self.name, self.patch.name, self.patch.manifold._latex(printer, *args))
 
 
diff --git a/sympy/printing/latex.py b/sympy/printing/latex.py
index bcb24bd0aa5f..724d425be6fb 100644
--- a/sympy/printing/latex.py
+++ b/sympy/printing/latex.py
@@ -312,13 +312,13 @@ def _print_Basic(self, expr):
         return self._deal_with_super_sub(expr.__class__.__name__) + r"\left(%s\right)" % ", ".join(l)
 
     def _print_bool(self, e):
-        return r"\mathrm{%s}" % e
+        return r"\text{%s}" % e
 
     _print_BooleanTrue = _print_bool
     _print_BooleanFalse = _print_bool
 
     def _print_NoneType(self, e):
-        return r"\mathrm{%s}" % e
+        return r"\text{%s}" % e
 
     def _print_Add(self, expr, order=None):
         if self.order == 'none':
@@ -1494,7 +1494,7 @@ def _print_Transpose(self, expr):
 
     def _print_Trace(self, expr):
         mat = expr.arg
-        return r"\mathrm{tr}\left(%s \right)" % self._print(mat)
+        return r"\text{tr}\left(%s \right)" % self._print(mat)
 
     def _print_Adjoint(self, expr):
         mat = expr.arg
@@ -2052,35 +2052,38 @@ def _print_catalan(self, expr, exp=None):
             tex = r"%s^{%s}" % (tex, self._print(exp))
         return tex
 
+    def _print_UnifiedTransform(self, expr, s, inverse=False):
+        return r"\mathcal{{{}}}{}_{{{}}}\left[{}\right]\left({}\right)".format(s, '^{-1}' if inverse else '', self._print(expr.args[1]), self._print(expr.args[0]), self._print(expr.args[2]))
+
     def _print_MellinTransform(self, expr):
-        return r"\mathcal{M}_{%s}\left[%s\right]\left(%s\right)" % (self._print(expr.args[1]), self._print(expr.args[0]), self._print(expr.args[2]))
+        return self._print_UnifiedTransform(expr, 'M')
 
     def _print_InverseMellinTransform(self, expr):
-        return r"\mathcal{M}^{-1}_{%s}\left[%s\right]\left(%s\right)" % (self._print(expr.args[1]), self._print(expr.args[0]), self._print(expr.args[2]))
+        return self._print_UnifiedTransform(expr, 'M', True)
 
     def _print_LaplaceTransform(self, expr):
-        return r"\mathcal{L}_{%s}\left[%s\right]\left(%s\right)" % (self._print(expr.args[1]), self._print(expr.args[0]), self._print(expr.args[2]))
+        return self._print_UnifiedTransform(expr, 'L')
 
     def _print_InverseLaplaceTransform(self, expr):
-        return r"\mathcal{L}^{-1}_{%s}\left[%s\right]\left(%s\right)" % (self._print(expr.args[1]), self._print(expr.args[0]), self._print(expr.args[2]))
+        return self._print_UnifiedTransform(expr, 'L', True)
 
     def _print_FourierTransform(self, expr):
-        return r"\mathcal{F}_{%s}\left[%s\right]\left(%s\right)" % (self._print(expr.args[1]), self._print(expr.args[0]), self._print(expr.args[2]))
+        return self._print_UnifiedTransform(expr, 'F')
 
     def _print_InverseFourierTransform(self, expr):
-        return r"\mathcal{F}^{-1}_{%s}\left[%s\right]\left(%s\right)" % (self._print(expr.args[1]), self._print(expr.args[0]), self._print(expr.args[2]))
+        return self._print_UnifiedTransform(expr, 'F', True)
 
     def _print_SineTransform(self, expr):
-        return r"\mathcal{SIN}_{%s}\left[%s\right]\left(%s\right)" % (self._print(expr.args[1]), self._print(expr.args[0]), self._print(expr.args[2]))
+        return self._print_UnifiedTransform(expr, 'SIN')
 
     def _print_InverseSineTransform(self, expr):
-        return r"\mathcal{SIN}^{-1}_{%s}\left[%s\right]\left(%s\right)" % (self._print(expr.args[1]), self._print(expr.args[0]), self._print(expr.args[2]))
+        return self._print_UnifiedTransform(expr, 'SIN', True)
 
     def _print_CosineTransform(self, expr):
-        return r"\mathcal{COS}_{%s}\left[%s\right]\left(%s\right)" % (self._print(expr.args[1]), self._print(expr.args[0]), self._print(expr.args[2]))
+        return self._print_UnifiedTransform(expr, 'COS')
 
     def _print_InverseCosineTransform(self, expr):
-        return r"\mathcal{COS}^{-1}_{%s}\left[%s\right]\left(%s\right)" % (self._print(expr.args[1]), self._print(expr.args[0]), self._print(expr.args[2]))
+        return self._print_UnifiedTransform(expr, 'COS', True)
 
     def _print_DMP(self, p):
         try:
@@ -2124,7 +2127,7 @@ def _print_CompositeMorphism(self, morphism):
         return component_names + pretty_morphism
 
     def _print_Category(self, morphism):
-        return "\\mathbf{%s}" % self._print(Symbol(morphism.name))
+        return r"\mathbf{{{}}}".format(Symbol(morphism.name))
 
     def _print_Diagram(self, diagram):
         if not diagram.premises:
@@ -2201,7 +2204,7 @@ def _print_MatrixHomomorphism(self, h):
 
     def _print_BaseScalarField(self, field):
         string = field._coord_sys._names[field._index]
-        return r'\boldsymbol{\mathrm{%s}}' % self._print(Symbol(string))
+        return r'\mathbf{{{}}}'.format(self._print(Symbol(string)))
 
     def _print_BaseVectorField(self, field):
         string = field._coord_sys._names[field._index]
@@ -2211,11 +2214,11 @@ def _print_Differential(self, diff):
         field = diff._form_field
         if hasattr(field, '_coord_sys'):
             string = field._coord_sys._names[field._index]
-            return r'\mathrm{d}%s' % self._print(Symbol(string))
+            return r'\text{d}%s' % self._print(Symbol(string))
         else:
             return 'd(%s)' % self._print(field)
             string = self._print(field)
-            return r'\mathrm{d}\left(%s\right)' % string
+            return r'\text{d}\left(%s\right)' % string
 
     def _print_Tr(self, p):
         #Todo: Handle indices
@@ -2363,8 +2366,8 @@ def latex(expr, fold_frac_powers=False, fold_func_brackets=False,
         a MatrixSymbol A will be printed as ``\mathbf{A}``, otherwise as ``A``.
     imaginary_unit : string, optional
         String to use for the imaginary unit. Defined options are "i" (default)
-        and "j". Adding "b" or "t" in front gives ``\mathrm`` or ``\text``, so
-        "bi" leads to ``\mathrm{i}`` which gives `\mathrm{i}`.
+        and "j". Adding "r" or "t" in front gives ``\mathrm`` or ``\text``, so
+        "ri" leads to ``\mathrm{i}`` which gives `\mathrm{i}`.
 
     Notes
     =====
diff --git a/sympy/printing/tests/test_latex.py b/sympy/printing/tests/test_latex.py
index d9cd85f3f477..3e137a0749ab 100644
--- a/sympy/printing/tests/test_latex.py
+++ b/sympy/printing/tests/test_latex.py
@@ -148,11 +148,11 @@ def test_latex_basic():
 
 
 def test_latex_builtins():
-    assert latex(True) == r"\mathrm{True}"
-    assert latex(False) == r"\mathrm{False}"
-    assert latex(None) == r"\mathrm{None}"
-    assert latex(true) == r"\mathrm{True}"
-    assert latex(false) == r'\mathrm{False}'
+    assert latex(True) == r"\text{True}"
+    assert latex(False) == r"\text{False}"
+    assert latex(None) == r"\text{None}"
+    assert latex(true) == r"\text{True}"
+    assert latex(false) == r'\text{False}'
 
 
 def test_latex_SingularityFunction():
@@ -1785,7 +1785,7 @@ def test_WedgeProduct_printing():
     from sympy.diffgeom.rn import R2
     from sympy.diffgeom import WedgeProduct
     wp = WedgeProduct(R2.dx, R2.dy)
-    assert latex(wp) == r"\mathrm{d}x \wedge \mathrm{d}y"
+    assert latex(wp) == r"\text{d}x \wedge \text{d}y"
 
 
 def test_issue_14041():
@@ -1904,8 +1904,8 @@ def test_trace():
     # Issue 15303
     from sympy import trace
     A = MatrixSymbol("A", 2, 2)
-    assert latex(trace(A)) == r"\mathrm{tr}\left(A \right)"
-    assert latex(trace(A**2)) == r"\mathrm{tr}\left(A^{2} \right)"
+    assert latex(trace(A)) == r"\text{tr}\left(A \right)"
+    assert latex(trace(A**2)) == r"\text{tr}\left(A^{2} \right)"
 
 
 def test_print_basic():
@@ -1936,8 +1936,8 @@ def test_MatrixSymbol_bold():
     # Issue #15871
     from sympy import trace
     A = MatrixSymbol("A", 2, 2)
-    assert latex(trace(A), mat_symbol_style='bold') == r"\mathrm{tr}\left(\mathbf{A} \right)"
-    assert latex(trace(A), mat_symbol_style='plain') == r"\mathrm{tr}\left(A \right)"
+    assert latex(trace(A), mat_symbol_style='bold') == r"\text{tr}\left(\mathbf{A} \right)"
+    assert latex(trace(A), mat_symbol_style='plain') == r"\text{tr}\left(A \right)"
 
     A = MatrixSymbol("A", 3, 3)
     B = MatrixSymbol("B", 3, 3)
@@ -1958,3 +1958,16 @@ def test_imaginary_unit():
     assert latex(1 + I, imaginary_unit='foo') == '1 + foo'
     assert latex(I, imaginary_unit="ti") == '\\text{i}'
     assert latex(I, imaginary_unit="tj") == '\\text{j}'
+
+
+def test_DiffGeomMethods():
+    from sympy.diffgeom import Manifold, Patch, CoordSystem, Point, BaseScalarField
+    r0, theta0 = symbols('r0, theta0')
+    m = Manifold('M', 2)
+    assert latex(m) == r'\text{M}'
+    p = Patch('P', m)
+    assert latex(p) == r'\text{P}_{\text{M}}'
+    rect = CoordSystem('rect', p)
+    assert latex(rect) == r'\text{rect}^{\text{P}}_{\text{M}}'
+    b = BaseScalarField(rect, 0)
+    assert latex(b) ==  r'\mathbf{rect_{0}}'


Code review comments:
diff_hunk: @@ -1958,3 +1958,16 @@ def test_imaginary_unit():
     assert latex(1 + I, imaginary_unit='foo') == '1 + foo'
     assert latex(I, imaginary_unit="ti") == '\\text{i}'
     assert latex(I, imaginary_unit="tj") == '\\text{j}'
+
+
+def test_DiffGeomMethods():
+    from sympy.diffgeom import Manifold, Patch, CoordSystem, Point, BaseScalarField
+    r0, theta0 = symbols('r0, theta0')
path: sympy/printing/tests/test_latex.py
text: @author:
These were apparently used later on in the code snippet I copied from... Will remove them, but waiting for review comments first.