/ galgebra / printer.py
printer.py
   1  r"""
   2  ANSI Enhanced Text Printing, Text Printer and LaTeX Printer for all Geometric Algebra classes
   3  
   4  :math:`\LaTeX` printing
   5  -----------------------
   6  
   7  .. note::
   8  
   9      :mod:`galgebra` works out of the box with the usual
  10      :ref:`sympy printing <sympy:tutorial-printing>` and will show Latex in
  11      IPython by default. In many cases, all that is needed is::
  12  
  13          sympy.init_printing(
  14              use_latex='mathjax',
  15              latex_printer=galgebra.printer.latex,
  16              # described below in `GaLatexPrinter`
  17              omit_function_args=True,
  18              omit_partial_derivative_fraction=True,
  19          )
  20  
  21      The rest of this section primarily describes an orthogonal feature for
  22      writing out ``.tex`` files with :func:`print`.
  23  
  24  The latex printer is turned on with the :func:`Format` function
  25  
  26  .. function:: Format(Fmode=True, Dmode=True, ipy=False)
  27  
  28  where ``Fmode`` is the function printing mode that suppresses printing arguments,
  29  ``Dmode`` is the derivative printing mode that does not use fractions, and
  30  ``ipy=True`` is the IPython notebook mode that does not redirect the print output.
  31  
  32  The latex output is post processed and displayed with the function
  33  
  34  .. function:: xpdf(filename='tmplatex.tex', debug=False)
  35  
  36  where ``filename`` is the name of the tex file one would keep for future
  37  inclusion in documents and ``debug=True`` would display the tex file
  38  immediately.
  39  
  40  There are three options for printing multivectors in latex.  They are
  41  accessed with the multivector member function
  42  
  43  .. function:: galgebra.mv.Mv.Fmt(self, fmt=1, title=None)
  44  
  45  where ``fmt`` of 1, 2, or 3 determines whether the entire multivector A is
  46  printed entirely on one line, or one grade is printed per line, or
  47  one base is printed per line.  If ``title`` is not None then the latex
  48  string generated is of the form::
  49  
  50      title + ' = ' + str(A)
  51  
  52  where it is assumed that title is a latex math mode string. If title
  53  contains '%' it is treated as a pure latex math mode string.  If it
  54  does not contain '%' then the following character mappings are applied::
  55  
  56      'grad' -> '\bm{\nabla} '
  57      '*'    -> ''
  58      '^'    -> '\W '
  59      '|'    -> '\cdot '
  60      '>'    -> '\lfloor '
  61      '<'    -> '\rfloor '
  62  
  63  In the case of a print statement of the form::
  64  
  65      print(title, A)
  66  
  67  everything in the title processing still applies except that the multivector
  68  formatting is one multivector per line.
  69  
  70  For print statements of the form::
  71  
  72      print(title)
  73  
  74  where no program variables are printed if title contains `#` then title
  75  is printed as regular latex line.  If title does not contain `#` then
  76  title is printed in equation mode. `%` has the same effect in title as
  77  in the ``Fmt()`` member function.
  78  """
  79  
  80  import copy
  81  import os
  82  import sys
  83  import io
  84  import builtins
  85  import functools
  86  import inspect
  87  import re
  88  import shutil
  89  import warnings
  90  from collections import ChainMap
  91  
  92  from sympy import MatrixBase, Basic, S, Symbol, Function, Derivative, Pow
  93  from sympy.printing.str import StrPrinter
  94  from sympy.printing.conventions import split_super_sub
  95  from sympy.printing.latex import (
  96      LatexPrinter, accepted_latex_functions, other_symbols
  97  )
  98  from sympy.core.function import _coeff_isneg
  99  from sympy.core.operations import AssocOp
 100  from sympy import init_printing
 101  from sympy.core.alphabets import greeks
 102  
 103  try:
 104      from IPython.display import display, Latex, Math, display_latex
 105  except ImportError:
 106      pass
 107  try:
 108      from sympy.interactive import printing
 109  except ImportError:
 110      pass
 111  
 112  from inspect import getouterframes, currentframe
 113  
 114  from ._utils import parser as _parser
 115  from ._utils.printable import Printable as SympyPrintable
 116  
 117  ZERO_STR = ' 0 '
 118  
 119  Format_cnt = 0
 120  
 121  ip_cmds = r"""
 122  $\DeclareMathOperator{\Tr}{Tr}
 123  \DeclareMathOperator{\Adj}{Adj}
 124  \newcommand{\bfrac}[2]{\displaystyle\frac{#1}{#2}}
 125  \newcommand{\lp}{\left (}
 126  \newcommand{\rp}{\right )}
 127  \newcommand{\paren}[1]{\lp {#1} \rp}
 128  \newcommand{\half}{\frac{1}{2}}
 129  \newcommand{\llt}{\left <}
 130  \newcommand{\rgt}{\right >}
 131  \newcommand{\abs}[1]{\left |{#1}\right | }
 132  \newcommand{\pdiff}[2]{\bfrac{\partial {#1}}{\partial {#2}}}
 133  \newcommand{\npdiff}[3]{\bfrac{\partial^{#3} {#1}}{\partial {#2}^{#3}}}
 134  \newcommand{\lbrc}{\left \{}
 135  \newcommand{\rbrc}{\right \}}
 136  \newcommand{\W}{\wedge}
 137  \newcommand{\prm}[1]{{#1}'}
 138  \newcommand{\ddt}[1]{\bfrac{d{#1}}{dt}}
 139  \newcommand{\R}{\dagger}
 140  \newcommand{\deriv}[3]{\bfrac{d^{#3}#1}{d{#2}^{#3}}}
 141  \newcommand{\grade}[1]{\left < {#1} \right >}
 142  \newcommand{\f}[2]{{#1}\lp {#2} \rp}
 143  \newcommand{\eval}[2]{\left . {#1} \right |_{#2}}$
 144  """
 145  
 146  SYS_CMD = {'linux2': {'rm': 'rm', 'evince': 'evince', 'null': ' > /dev/null', '&': '&'},
 147             'linux': {'rm': 'rm', 'evince': 'evince', 'null': ' > /dev/null', '&': '&'},
 148             'win32': {'rm': 'del', 'evince': 'start', 'null': ' > NUL', '&': ''},
 149             'darwin': {'rm': 'rm', 'evince': 'open', 'null': ' > /dev/null', '&': '&'}}
 150  
 151  
 152  def isinteractive():  #Is ipython running
 153      """
 154      We will assume that if ipython is running then jupyter notebook is
 155      running.
 156      """
 157      try:
 158          __IPYTHON__
 159          return True
 160      except NameError:
 161          return False
 162  
 163  
 164  def ostr(obj, dict_mode=False, indent=True):
 165      return GaPrinter(dict(dict_mode=dict_mode)).doprint(obj)
 166  
 167  
 168  def find_functions(expr):
 169      f_lst = []
 170      for f in list(expr.atoms(Function)):
 171          if str(f) not in GaPrinter.function_names:
 172              f_lst.append(f)
 173      f_lst += list(expr.atoms(Derivative))
 174      return f_lst
 175  
 176  
 177  def coef_simplify(expr):
 178      # fcts = find_functions(expr)
 179      # return expr.collect(fcts)
 180      return expr
 181  
 182  
 183  def oprint(*args, dict_mode=False):
 184      """
 185      Debug printing for iterated (list/tuple/dict/set) objects. args is
 186      of form ``(title1, object1, title2, object2, ...)`` and prints::
 187  
 188          title1 = object1
 189          title2 = object2
 190          ...
 191  
 192      If you only wish to print a title set ``object = None``.
 193      """
 194  
 195      if isinstance(args[0], str) or args[0] is None:
 196          titles = args[0::2]
 197          objs = args[1::2]
 198          strs = [
 199              ostr(obj, dict_mode) if obj is not None else None
 200              for obj in objs
 201          ]
 202          n = max((
 203              len(title)
 204              for title, s in zip(titles, strs)
 205              if s is not None and '\n' not in s
 206          ), default=0)
 207  
 208          for title, s in zip(titles, strs):
 209              if s is None:
 210                  print(title)
 211              else:
 212                  npad = n - len(title)
 213                  if '\n' in s:
 214                      print(title + ':\n' + s)
 215                  else:
 216                      print(title + npad * ' ' + ' = ' + s)
 217      else:
 218          for arg in args:
 219              print(ostr(arg, dict_mode))
 220  
 221  
 222  _ansi_colors = {
 223      'black':       '\033[0;30m', 'dark gray':     '\033[1;30m',
 224      'red':         '\033[0;31m', 'bright red':    '\033[1;31m',
 225      'green':       '\033[0;32m', 'bright green':  '\033[1;32m',
 226      'yellow':      '\033[0;33m', 'bright yellow': '\033[1;33m',
 227      'blue':        '\033[0;34m', 'bright blue':   '\033[1;34m',
 228      'purple':      '\033[0;35m', 'bright purple': '\033[1;35m',
 229      'cyan':        '\033[0;36m', 'bright cyan':   '\033[1;36m',
 230      'bright gray': '\033[0;37m', 'white':         '\033[1;37m',
 231  }
 232  _ansi_reset = '\033[0m'
 233  
 234  
 235  def _apply_ansi_color(color: str, text: str) -> str:
 236      if color is None:
 237          return text
 238      else:
 239          return _ansi_colors.get(color, color) + text + _ansi_reset
 240  
 241  
 242  def enhance_print(base='blue', fct='red', deriv='cyan'):
 243      """ Enable ansi color codes in plain-text formatting.
 244  
 245      Valid color names are:
 246  
 247      {colors}
 248  
 249      Pass ``None`` to disable coloring.
 250      """
 251      GaPrinter.set_global_settings(
 252          function_color=fct,
 253          derivative_color=deriv,
 254          basis_vector_color=base,
 255      )
 256  
 257  
 258  # patch the docstring using our known color names
 259  enhance_print.__doc__ = enhance_print.__doc__.format(colors='\n    '.join(
 260      " - ``{!r}``".format(k) for k in _ansi_colors
 261  ))
 262  
 263  
 264  def Eprint(*args, **kwargs):
 265      """ Alias for :func:`enhance_print` """
 266      return enhance_print(*args, **kwargs)
 267  
 268  
 269  class GaPrinter(StrPrinter):
 270      """
 271      This subclass of the builtin string printer makes some customizations which
 272      make output a little more readable for GA usage.
 273  
 274      The customizations are:
 275  
 276      * :class:`~sympy.core.function.Derivative` objects are printed as ``D{x}y``
 277        instead of ``Derivative(y, x)``.
 278      * :class:`~sympy.core.function.Function` objects are printed without
 279        arguments. This is useful for defining fields over
 280        :attr:`~galgebra.ga.Ga.coords`, but sometimes misfires.
 281      * A new ``dict_mode`` setting, which when ``True`` prints :class:`dict`
 282        objects with ``->`` and one entry per line.
 283      * New ANSI color settings:
 284  
 285        * ``derivative_color``, for adjusting the color of ``D{x}``.
 286        * ``function_color``, for adjusting the color of argument-less functions.
 287        * ``basis_vector_color``, for adjusting the color of basis vector symbols.
 288  
 289      When :mod:`galgebra.printer` is imported, builtin sympy objects are patched
 290      to use this printer for their ``__repr__`` instead of the builtin
 291      :class:`~sympy.printing.str.StrPrinter`. There is currently no way to
 292      disable this patching.
 293      """
 294  
 295      _default_settings = ChainMap({
 296          # if true, print dicts with `->` instead of `:`, one entry per line
 297          "dict_mode": False,
 298          "derivative_color": None,
 299          "function_color": None,
 300          "basis_vector_color": None,
 301      }, StrPrinter._default_settings)
 302  
 303      function_names = ('acos', 'acosh', 'acot', 'acoth', 'arg', 'asin', 'asinh',
 304                        'atan', 'atan2', 'atanh', 'ceiling', 'conjugate', 'cos',
 305                        'cosh', 'cot', 'coth', 'exp', 'floor', 'im', 'log', 're',
 306                        'root', 'sin', 'sinh', 'sqrt', 'sign', 'tan', 'tanh', 'Abs')
 307  
 308      def _print_Function(self, expr):
 309          name = expr.func.__name__
 310  
 311          if expr.func.nargs is not None:
 312              if name in GaPrinter.function_names:
 313                  return expr.func.__name__ + "(%s)" % self.stringify(expr.args, ", ")
 314  
 315          return _apply_ansi_color(
 316              self._settings["function_color"], "%s" % (name,))
 317  
 318      def _print_BasisVectorSymbol(self, expr):
 319          return _apply_ansi_color(
 320              self._settings["basis_vector_color"], self._print_Symbol(expr))
 321  
 322      def _print_Derivative(self, expr):
 323          # Break the following to support both py 2 & 3
 324          # function, *diff_args = expr.args
 325          function = expr.args[0]
 326          diff_args = expr.args[1:]
 327  
 328          xi = []
 329          ni = []
 330          for x, n in diff_args:
 331              if x in xi:
 332                  i = xi.index(x)
 333                  ni[i] += n
 334              else:
 335                  xi.append(self._print(x))
 336                  ni.append(n)
 337  
 338          s = 'D'
 339          for x, n in zip(xi, ni):
 340              s += '{' + str(x) + '}'
 341              if n > 1:
 342                  s += '^' + str(n)
 343          s += str(self._print(function))
 344          return _apply_ansi_color(self._settings["derivative_color"], s)
 345  
 346      def _print_dict(self, expr):
 347          if not self._settings['dict_mode']:
 348              return super()._print_dict(expr)
 349  
 350          return '\n'.join(
 351              '{} -> {}'.format(self._print(k), self._print(v))
 352              for k, v in expr.items()
 353          )
 354  
 355  
 356  # Inheriting from SympyPrintable ensure we take part in interactive printing
 357  # customization
 358  class GaPrintable(SympyPrintable):
 359      """ Mixin class providing default implementations of printing hooks """
 360      def __ga_print_str__(self):
 361          if GaLatexPrinter.latex_flg:
 362              return GaLatexPrinter().doprint(self)
 363          else:
 364              return GaPrinter().doprint(self)
 365  
 366      def __repr__(self):
 367          return GaPrinter().doprint(self)
 368  
 369  
 370  # Change sympy builtins to use our printer by default.
 371  # We do this because we always have done, and stopping now would break
 372  # compatibility.
 373  if issubclass(Basic, SympyPrintable):
 374      SympyPrintable.__ga_print_str__ = GaPrintable.__ga_print_str__
 375      SympyPrintable.__repr__ = GaPrintable.__repr__
 376  else:
 377      # sympy < 1.7
 378      Basic.__ga_print_str__ = GaPrintable.__ga_print_str__
 379      Basic.__repr__ = GaPrintable.__repr__
 380  
 381      MatrixBase.__ga_print_str__ = GaPrintable.__ga_print_str__
 382      MatrixBase.__repr__ = GaPrintable.__repr__
 383  
 384  
 385  # This is the lesser of two evils. Previously, we overwrote `Basic.__str__` in
 386  # order to customise `print(sympy)`. This broke a bunch of assumptions inside
 387  # sympy, so isn't safe. Instead of clobbering `__str__`, we add a
 388  # `__ga_print_str__` attribute, and have `print` use it if present.
 389  _old_print = builtins.print
 390  
 391  
 392  @functools.wraps(_old_print)
 393  def _print(*values, **kwargs):
 394      values_new = []
 395      for v in values:
 396          try:
 397              f = type(v).__ga_print_str__
 398          except AttributeError:
 399              values_new.append(v)
 400          else:
 401              values_new.append(f(v))
 402      _old_print(*values_new, **kwargs)
 403  
 404  
 405  builtins.print = _print
 406  
 407  
 408  class GaLatexPrinter(LatexPrinter):
 409      r"""
 410      This subclass of the builtin string printer makes some customizations which
 411      make output a little more readable for GA usage.
 412  
 413      The customizations are:
 414  
 415      * A new ``omit_partial_derivative_fraction`` setting that affects the
 416        printing of :class:`~sympy.core.function.Derivative` objects, with
 417        possible values:
 418  
 419        * ``False``, to use the *sympy* default, :math:`\pdiff{f}{x}`.
 420        * ``True``, to use a condensed notation, :math:`\partial_{x}f`.
 421  
 422      * A new ``omit_function_args`` setting which affects the printing of
 423        :class:`~sympy.core.function.Function` objects, with possible values:
 424  
 425        * ``False``, to use the sympy default, :math:`{{f}\lp {x,y,z} \rp }`.
 426        * ``True``, to print as :math:`f`. This is similar to the behavior of
 427          :class:`GaPrinter`.
 428  
 429      * A change to function printing to allow function names to contain
 430        subscripts and superscripts.
 431  
 432      * Use of ``boldsymbol`` instead of ``mathbf`` for bold symbol names.
 433  
 434      Note that this printer is not *required* for using GA objects, the base
 435      class printer will work fine too.
 436      """
 437      # overrides of base class settings, and new settings for our printers
 438      _default_settings = ChainMap({
 439          "mat_str": "array",
 440          "omit_function_args": False,
 441          "omit_partial_derivative_fraction": False,
 442      }, LatexPrinter._default_settings)
 443  
 444      latex_flg = False
 445      latex_str = ''
 446      ipy = False
 447  
 448      preamble = \
 449  """
 450  \\pagestyle{empty}
 451  \\usepackage[latin1]{inputenc}
 452  \\usepackage{amsmath}
 453  \\usepackage{amsfonts}
 454  \\usepackage{amssymb}
 455  \\usepackage{amsbsy}
 456  \\usepackage{tensor}
 457  \\usepackage{listings}
 458  \\usepackage{color}
 459  \\usepackage{xcolor}
 460  \\usepackage{bm}
 461  \\usepackage{breqn}
 462  \\definecolor{gray}{rgb}{0.95,0.95,0.95}
 463  \\setlength{\\parindent}{0pt}
 464  \\DeclareMathOperator{\\Tr}{Tr}
 465  \\DeclareMathOperator{\\Adj}{Adj}
 466  \\newcommand{\\bfrac}[2]{\\displaystyle\\frac{#1}{#2}}
 467  \\newcommand{\\lp}{\\left (}
 468  \\newcommand{\\rp}{\\right )}
 469  \\newcommand{\\paren}[1]{\\lp {#1} \\rp}
 470  \\newcommand{\\half}{\\frac{1}{2}}
 471  \\newcommand{\\llt}{\\left <}
 472  \\newcommand{\\rgt}{\\right >}
 473  \\newcommand{\\abs}[1]{\\left |{#1}\\right | }
 474  \\newcommand{\\pdiff}[2]{\\bfrac{\\partial {#1}}{\\partial {#2}}}
 475  \\newcommand{\\lbrc}{\\left \\{}
 476  \\newcommand{\\rbrc}{\\right \\}}
 477  \\newcommand{\\W}{\\wedge}
 478  \\newcommand{\\prm}[1]{{#1}'}
 479  \\newcommand{\\ddt}[1]{\\bfrac{d{#1}}{dt}}
 480  \\newcommand{\\R}{\\dagger}
 481  \\newcommand{\\deriv}[3]{\\bfrac{d^{#3}#1}{d{#2}^{#3}}}
 482  \\newcommand{\\grade}[1]{\\left < {#1} \\right >}
 483  \\newcommand{\\f}[2]{{#1}\\lp{#2}\\rp}
 484  \\newcommand{\\eval}[2]{\\left . {#1} \\right |_{#2}}
 485  \\newcommand{\\Nabla}{\\boldsymbol{\\nabla}}
 486  \\newcommand{\\eb}{\\boldsymbol{e}}
 487  \\usepackage{float}
 488  \\floatstyle{plain} % optionally change the style of the new float
 489  \\newfloat{Code}{H}{myc}
 490  \\lstloadlanguages{Python}
 491  
 492  \\begin{document}
 493  """
 494      postscript = '\\end{document}\n'
 495      macros = '\\newcommand{\\f}[2]{{#1}\\left ({#2}\\right )}'
 496  
 497      # Used by _print_Symbol
 498      greek_translated = {'lamda': 'lambda', 'Lamda': 'Lambda'}
 499      other = other_symbols | {'infty'}
 500      special_alphabet = list(reversed(sorted(list(greeks) + list(other), key=len)))
 501  
 502      @staticmethod
 503      def redirect():
 504          GaLatexPrinter.latex_flg = True
 505          if GaLatexPrinter.ipy:
 506              pass
 507          else:
 508              GaLatexPrinter.stdout = sys.stdout
 509              sys.stdout = io.StringIO()
 510  
 511      @staticmethod
 512      def restore():
 513          if GaLatexPrinter.latex_flg:
 514              if not GaLatexPrinter.ipy:
 515                  GaLatexPrinter.latex_str += sys.stdout.getvalue()
 516              GaLatexPrinter.latex_flg = False
 517              if not GaLatexPrinter.ipy:
 518                  sys.stdout = GaLatexPrinter.stdout
 519  
 520      def _print_Pow(self, expr):
 521          base = self._print(expr.base)
 522          if ('_' in base or '^' in base) and 'cdot' not in base:
 523              mode = True
 524          else:
 525              mode = False
 526  
 527          # Treat x**Rational(1, n) as special case
 528          if expr.exp.is_Rational and abs(expr.exp.p) == 1 and expr.exp.q != 1:
 529              #base = self._print(expr.base)
 530              expq = expr.exp.q
 531  
 532              if expq == 2:
 533                  tex = r"\sqrt{%s}" % base
 534              elif self._settings['itex']:
 535                  tex = r"\root{%d}{%s}" % (expq, base)
 536              else:
 537                  tex = r"\sqrt[%d]{%s}" % (expq, base)
 538  
 539              if expr.exp.is_negative:
 540                  return r"\frac{1}{%s}" % tex
 541              else:
 542                  return tex
 543          elif self._settings['fold_frac_powers'] \
 544              and expr.exp.is_Rational \
 545                  and expr.exp.q != 1:
 546              base, p, q = self._print(expr.base), expr.exp.p, expr.exp.q
 547              if mode:
 548                  return r"{\left ( %s \right )}^{%s/%s}" % (base, p, q)
 549              else:
 550                  return r"%s^{%s/%s}" % (base, p, q)
 551  
 552          elif expr.exp.is_Rational and expr.exp.is_negative and expr.base.is_Function:
 553              # Things like 1/x
 554              return r"\frac{%s}{%s}" % \
 555                  (1, self._print(Pow(expr.base, -expr.exp)))
 556          else:
 557              if expr.base.is_Function:
 558                  return r"{%s}^{%s}" % (self._print(expr.base), self._print(expr.exp))
 559              else:
 560                  if expr.is_commutative and expr.exp == -1:
 561                      #solves issue 1030
 562                      #As Mul always simplify 1/x to x**-1
 563                      #The objective is achieved with this hack
 564                      #first we get the latex for -1 * expr,
 565                      #which is a Mul expression
 566                      tex = self._print(S.NegativeOne * expr).strip()
 567                      #the result comes with a minus and a space, so we remove
 568                      if tex[:1] == "-":
 569                          return tex[1:].strip()
 570                  if self._needs_brackets(expr.base):
 571                      tex = r"\left(%s\right)^{%s}"
 572                  else:
 573                      if mode:
 574                          tex = r"{\left ( %s \right )}^{%s}"
 575                      else:
 576                          tex = r"%s^{%s}"
 577  
 578                  return tex % (self._print(expr.base),
 579                                self._print(expr.exp))
 580  
 581      def _print_Symbol(self, expr, style='plain'):
 582  
 583          def str_symbol(name_str):
 584  
 585              def translate(s):
 586                  tmp = s
 587  
 588                  parse_dict = {}
 589                  i_sub = 1
 590  
 591                  for glyph in GaLatexPrinter.special_alphabet:
 592                      escaped_glyph = '\\' + glyph
 593                      if glyph in tmp:
 594                          parse_sym = '????' + str(i_sub)
 595                          i_sub += 1
 596                          # If this glyph is already escaped, avoid escaping again
 597                          translated_glyph = (escaped_glyph + ' ') if escaped_glyph not in tmp else glyph
 598                          parse_dict[parse_sym] = translated_glyph
 599                          tmp = tmp.replace(glyph, parse_sym)
 600  
 601                  for parse_sym in parse_dict:
 602                      tmp = tmp.replace(parse_sym, parse_dict[parse_sym])
 603  
 604                  for glyph in GaLatexPrinter.greek_translated:
 605                      if glyph in tmp:
 606                          tmp = tmp.replace(glyph, GaLatexPrinter.greek_translated[glyph])
 607  
 608                  return tmp
 609  
 610              name, supers, subs = split_super_sub(name_str)
 611  
 612              name = translate(name)
 613  
 614              if style == 'bold':
 615                  name = '\\boldsymbol{' + name + '}'
 616  
 617              supers = list(map(translate, supers))
 618              subs = list(map(translate, subs))
 619  
 620              # glue all items together:
 621              if len(supers) > 0:
 622                  name += "^{%s}" % " ".join(supers)
 623              if len(subs) > 0:
 624                  name += "_{%s}" % " ".join(subs)
 625  
 626              return name
 627  
 628          if expr in self._settings['symbol_names']:
 629              return self._settings['symbol_names'][expr]
 630  
 631          return str_symbol(expr.name)
 632  
 633      def _print_Function(self, expr, exp=None):
 634  
 635          func = expr.func.__name__
 636          name = func
 637          if hasattr(self, '_print_' + func):
 638              return getattr(self, '_print_' + func)(expr, exp)
 639          else:
 640              args = [str(self._print(arg)) for arg in expr.args]
 641  
 642              # How inverse trig functions should be displayed, formats are:
 643              # abbreviated: asin, full: arcsin, power: sin^-1
 644              inv_trig_style = self._settings['inv_trig_style']
 645              # If we are dealing with a power-style inverse trig function
 646              inv_trig_power_case = False
 647              # If it is applicable to fold the argument brackets
 648              can_fold_brackets = self._settings['fold_func_brackets'] and \
 649                  len(args) == 1 and not self._needs_function_brackets(expr.args[0])
 650  
 651              inv_trig_table = ["asin", "acos", "atan", "acot", "acosh", "asinh", "atanh"]
 652  
 653              # If the function is an inverse trig function, handle the style
 654              if func in inv_trig_table:
 655                  if inv_trig_style == "abbreviated":
 656                      func = func
 657                  elif inv_trig_style == "full":
 658                      func = "arc" + func[1:]
 659                  elif inv_trig_style == "power":
 660                      func = func[1:]
 661                      inv_trig_power_case = True
 662  
 663                      # Can never fold brackets if we're raised to a power
 664                      if exp is not None:
 665                          can_fold_brackets = False
 666  
 667              if inv_trig_power_case:
 668                  if func in accepted_latex_functions:
 669                      name = r"\%s^{-1}" % func
 670                  else:
 671                      name = r"\operatorname{%s}^{-1}" % func
 672              elif exp is not None:
 673                  if func in accepted_latex_functions:
 674                      name = r"\%s^{%s}" % (func, exp)
 675                  else:
 676                      name = latex(Symbol(func)) + ' '
 677                      if '_' in func or '^' in func:
 678                          name = r'{\left ( ' + name + r'\right ) }^{' + exp + '}'
 679                      else:
 680                          name += '^{' + exp + '}'
 681              else:
 682                  if func in accepted_latex_functions:
 683                      name = r"\%s" % func
 684                  else:
 685                      name = latex(Symbol(func)) + ' '
 686                      if exp is not None:
 687                          if '_' in name or '^' in name:
 688                              name = r'\left ( ' + name + r'\right )^{' + exp + '}'
 689                          else:
 690                              name += '^{' + exp + '}'
 691  
 692              if can_fold_brackets:
 693                  if func in accepted_latex_functions:
 694                      # Wrap argument safely to avoid parse-time conflicts
 695                      # with the function name itself
 696                      name += r" {%s}"
 697                  else:
 698                      if not self._settings["omit_function_args"]:
 699                          name += r"%s"
 700              else:
 701                  if func in accepted_latex_functions or not self._settings["omit_function_args"]:
 702                      name += r"{\left (%s \right )}"
 703  
 704              if inv_trig_power_case and exp is not None:
 705                  name += r"^{%s}" % exp
 706  
 707              if func in accepted_latex_functions or not self._settings["omit_function_args"]:
 708                  if len(args) == 1:
 709                      name = name % args[0]
 710                  else:
 711                      name = name % ",".join(args)
 712  
 713              return name
 714  
 715      def _print_Derivative(self, expr):
 716          dim = len(expr.variables)
 717          imax = 1
 718          if dim == 1:
 719              if self._settings["omit_partial_derivative_fraction"]:
 720                  tex = r"\partial_{%s}" % self._print(expr.variables[0])
 721              else:
 722                  tex = r"\frac{\partial}{\partial %s}" % self._print(expr.variables[0])
 723          else:
 724              multiplicity, i, tex = [], 1, ""
 725              current = expr.variables[0]
 726              for symbol in expr.variables[1:]:
 727                  if symbol == current:
 728                      i = i + 1
 729                  else:
 730                      multiplicity.append((current, i))
 731                      current, i = symbol, 1
 732              else:
 733                  imax = max(imax, i)
 734                  multiplicity.append((current, i))
 735  
 736              if self._settings["omit_partial_derivative_fraction"]:
 737                  tex = ''
 738                  for x, i in multiplicity:
 739                      if i == 1:
 740                          tex += r"\partial_{%s}" % (self._print(x),)
 741                      else:
 742                          tex += r"\partial^{%i}_{%s}" % (i, self._print(x))
 743              else:
 744                  for x, i in multiplicity:
 745                      if i == 1:
 746                          tex += r"\partial %s" % self._print(x)
 747                      else:
 748                          tex += r"\partial^{%s} %s" % (i, self._print(x))
 749                  tex = r"\frac{\partial^{%s}}{%s} " % (dim, tex)
 750  
 751          if isinstance(expr.expr, AssocOp):
 752              s = r"%s\left(%s\right)" % (tex, self._print(expr.expr))
 753          else:
 754              s = r"%s %s" % (tex, self._print(expr.expr))
 755          return s
 756  
 757      def _print_Determinant(self, expr):
 758          # sympy `uses |X|` by default, we want `det (X)`
 759          return r"\det\left ( {}\right )".format(self._print(expr.args[0]))
 760  
 761      @staticmethod
 762      def latex(expr, **settings):
 763  
 764          if not isinstance(expr, list):
 765              return GaLatexPrinter(settings).doprint(expr)
 766          else:
 767              s = '\\begin{align*}'
 768              for x in expr:
 769                  s += '\n & ' + latex(x) + ' \\\\'
 770              s += '\n\\end{align*}'
 771              return s
 772  
 773  
 774  def latex(expr, **settings) -> str:
 775      """
 776      Get the latex representation of expr using :class:`GaLatexPrinter`.
 777  
 778      Takes the same options as :func:`sympy.printing.latex.latex`; see that
 779      function for more information.
 780  
 781      This can be used as the ``latex_printer`` argument to
 782      :func:`~sympy.interactive.printing.init_printing` to make IPython always
 783      use :class:`GaLatexPrinter`.
 784      """
 785      return GaLatexPrinter(settings).doprint(expr)
 786  
 787  
 788  def print_latex(expr, **settings):
 789      """Prints LaTeX representation of the given expression.
 790  
 791      Takes the same settings as :func:`latex`."""
 792      print(latex(expr, **settings))
 793  
 794  
 795  def Format(Fmode: bool = True, Dmode: bool = True, inverse='full'):
 796      r"""
 797      Turns on latex printing with configurable options.
 798  
 799      This redirects printer output so that latex compiler can capture it.
 800  
 801      ``Format()`` is also required for printing from *ipython notebook* (note that ``xpdf()`` is not needed to print from *ipython notebook*).
 802  
 803      Parameters
 804      ----------
 805      Fmode:
 806          Value for the ``omit_function_args`` setting of
 807          :class:`GaLatexPrinter`.
 808      Dmode:
 809          Value for the ``omit_partial_derivative_fraction`` setting of
 810          :class:`GaLatexPrinter`.
 811      """
 812      global Format_cnt
 813  
 814      GaLatexPrinter.set_global_settings(
 815          omit_partial_derivative_fraction=Dmode,
 816          omit_function_args=Fmode,
 817          inv_trig_style=inverse,
 818      )
 819  
 820      if Format_cnt == 0:
 821          Format_cnt += 1
 822  
 823          GaLatexPrinter.latex_flg = True
 824          GaLatexPrinter.redirect()
 825  
 826          if isinteractive():
 827              init_printing(
 828                  use_latex='mathjax',
 829                  latex_mode='equation*',
 830                  latex_printer=latex,
 831                  # Affects only the plaintext printing, and makes our printing
 832                  # tests easier to maintain
 833                  wrap_line=False,
 834              )
 835  
 836      return
 837  
 838  
 839  def _texify(s: str) -> str:
 840      """ Convert python GA operator notation to LaTeX """
 841      repl_pairs = [
 842          (r'\|', r'\cdot '),
 843          (r'\^(?!{)', r'\W '),
 844          (r'\*', ' '),
 845          (r'\brgrad\b', r'\bar{\boldsymbol{\nabla}} '),
 846          (r'\bgrad\b', r'\boldsymbol{\nabla} '),
 847          (r'>>', r' \times '),
 848          (r'<<', r' \bar{\times} '),
 849          (r'<', r'\rfloor '),
 850          (r'>', r'\lfloor '),
 851      ]
 852  
 853      def repl_func(m):
 854          # only one group will be present, use the corresponding match
 855          return next(
 856              r
 857              for (p, r), g in zip(repl_pairs, m.groups())
 858              if g is not None
 859          )
 860      pattern = '|'.join("({})".format(p) for p, _ in repl_pairs)
 861      return re.sub(pattern, repl_func, s)
 862  
 863  
 864  def tex(paper=(14, 11), debug=False, prog=False, pt='10pt'):
 865      r"""
 866      Post processes LaTeX output (see comments below), adds preamble and
 867      postscript.
 868  
 869      This postprocessing has two main behaviors:
 870  
 871      1. Converting strings on the left hand side of the last ``=`` into TeX.
 872         This translates the ``*``, ``^``, ``|``, ``>``, ``<``, ``<<``, ``>>``,
 873         ``grad``, and ``rgrad`` operators of galgebra into the appropriate latex
 874         operators. If there is no ``=`` in the line, no conversion is applied.
 875  
 876      2. Wrapping lines of latex into ``equation*`` environments if they are not
 877         already in environments, and moving labels that were prepended outside
 878         ``align`` environments inside those environments.
 879  
 880      Both behaviors are applied line by line, unless a line starts with the
 881      following text:
 882  
 883      ``#%`` or ``%``
 884          Disables only behavior 1 for the rest of the line.
 885  
 886      ``##``
 887          Disables behaviors 1 and 2 until the end of the next line starting with
 888          ``##``. This includes processing any of the other special characters,
 889          which will be emitted verbatim.
 890  
 891      ``#``
 892          Disables behaviors 1 and 2 for the rest of the line.
 893  
 894      We assume that if :func:`tex` is called, then :func:`Format` has been called
 895      at the beginning of the program.
 896      """
 897  
 898      latex_str = GaLatexPrinter.latex_str + sys.stdout.getvalue()
 899      GaLatexPrinter.latex_str = ''
 900      GaLatexPrinter.restore()
 901      r"""
 902      Each line in the latex_str is interpreted to be an equation or align
 903      environment.  If the line does not begin with '\begin{align*}' then
 904      'begin{equation*}' will be added to the beginning of the line and
 905      '\end{equation*}' to the end of the line.
 906      The latex strings generated by galgebra and sympy expressions for
 907      printing must not contain '\n' except as the final character.  Thus
 908      all '\n' must be removed from a compound (not a simple type) expression
 909      and a '\n' added to the end of the string to delimit it when the string
 910      is generated.
 911      """
 912      latex_lst = latex_str.split('\n')
 913      latex_str = ''
 914  
 915      code_flg = False
 916  
 917      for latex_line in latex_lst:
 918          if not latex_line:
 919              pass
 920          elif latex_line.startswith('##'):
 921              # a post-processing toggle used by `Print_Function`
 922              code_flg = not code_flg
 923              latex_line = latex_line[2:]
 924          elif code_flg:
 925              pass
 926          elif latex_line.startswith('#') and not latex_line.startswith('#%'):
 927              # do not process this line
 928              latex_line = latex_line[1:]
 929          else:
 930              # two different spellings of "do not process the LHS"
 931              if latex_line.startswith('%'):
 932                  latex_line = latex_line[1:]
 933              elif latex_line.startswith('#%'):
 934                  latex_line = latex_line[2:]
 935              # otherwise, process it if we can find it
 936              elif '=' in latex_line:
 937                  lhs, latex_line = latex_line.rsplit('=', 1)
 938                  latex_line = _texify(lhs) + '=' + latex_line
 939  
 940              # in either case, perform the environment wrapping
 941              if r'\begin{align*}' in latex_line:
 942                  latex_line = r'\begin{align*} ' + latex_line.replace(r'\begin{align*}', '', 1).lstrip()
 943              else:
 944                  latex_line = r'\begin{equation*} ' + latex_line.strip() + r' \end{equation*}'
 945  
 946          latex_str += latex_line + '\n'
 947  
 948      latex_str = latex_str.replace('\n\n', '\n')
 949  
 950      if prog:
 951          with open(sys.argv[0], 'r') as prog_file:
 952              prog_str = prog_file.read()
 953          prog_str = '{\\Large \\bf Program:}\\begin{lstlisting}[language=Python,showspaces=false,' + \
 954                     'showstringspaces=false]\n' + \
 955                     prog_str + '\n\\end{lstlisting}\n {\\Large \\bf Code Output:} \n'
 956          latex_str = prog_str + latex_str
 957  
 958      if debug:
 959          print(latex_str)
 960  
 961      if paper == 'letter':
 962          paper_size = \
 963  """
 964  \\documentclass[@10pt@,fleqn]{report}
 965  """
 966      else:
 967          paper_size = \
 968  """
 969  \\documentclass[@10pt@,fleqn]{report}
 970  \\usepackage[vcentering]{geometry}
 971  """
 972          if paper == 'landscape':
 973              paper = [11, 8.5]
 974          paper_size += '\\geometry{papersize={' + str(paper[0]) + \
 975                        'in,' + str(paper[1]) + 'in},total={' + str(paper[0] - 1) + \
 976                        'in,' + str(paper[1] - 1) + 'in}}\n'
 977  
 978      paper_size = paper_size.replace('@10pt@', pt)
 979      latex_str = paper_size + GaLatexPrinter.preamble + latex_str + GaLatexPrinter.postscript
 980  
 981      return latex_str
 982  
 983  
 984  def xpdf(filename=None, paper=(14, 11), crop=False, png=False, prog=False, debug=False, pt='10pt', pdfprog='pdflatex'):
 985  
 986      """
 987      Post processes LaTeX output (see comments below), adds preamble and
 988      postscript, generates tex file, inputs file to latex, displays resulting
 989      pdf file.
 990  
 991      Arg         Value       Result
 992      pdfprog    'pdflatex'   Use pdfprog to generate pdf output, only generate tex if pdfprog is None
 993      crop        True        Use "pdfcrop" to crop output file (pdfcrop must be installed, linux only)
 994      png         True        Use "convert" to produce png output (imagemagick must be installed, linux only)
 995  
 996      We assume that if xpdf() is called then Format() has been called at the beginning of the program.
 997      """
 998  
 999      sys_cmd = SYS_CMD[sys.platform]
1000  
1001      latex_str = tex(paper=paper, debug=debug, prog=prog, pt=pt)
1002  
1003      if filename is None:
1004          pyfilename = sys.argv[0]
1005          rootfilename = pyfilename.replace('.py', '')
1006          filename = rootfilename + '.tex'
1007  
1008      if debug:
1009          print('latex file =', filename)
1010  
1011      latex_file = open(filename, 'w')
1012      latex_file.write(latex_str)
1013      latex_file.close()
1014  
1015      latex_str = None
1016  
1017      if pdfprog is None:
1018          return
1019  
1020      pdflatex = shutil.which(pdfprog)
1021  
1022      if debug:
1023          print('pdflatex path =', pdflatex)
1024  
1025      if pdfprog is not None:
1026          if debug:  # Display latex excution output for debugging purposes
1027              os.system(pdfprog + ' ' + filename[:-4])
1028          else:  # Works for Linux don't know about Windows
1029              os.system(pdfprog + ' ' + filename[:-4] + sys_cmd['null'])
1030  
1031          print_cmd = sys_cmd['evince'] + ' ' + filename[:-4] + '.pdf ' + sys_cmd['&']
1032          print(print_cmd)
1033  
1034          os.system(print_cmd)
1035          eval(input('!!!!Return to continue!!!!\n'))
1036  
1037          if debug:
1038              os.system(sys_cmd['rm'] + ' ' + filename[:-4] + '.aux ' + filename[:-4] + '.log')
1039          else:
1040              os.system(sys_cmd['rm'] + ' ' + filename[:-4] + '.aux ' + filename[:-4] + '.log ' + filename[:-4] + '.tex')
1041          if crop:
1042              os.system('pdfcrop ' + filename[:-4] + '.pdf')
1043              os.remove(filename[:-4] + '.pdf')
1044              os.rename(filename[:-4] + '-crop.pdf', filename[:-4] + '.pdf')
1045          if png:
1046              os.system('Pdf2Png ' + filename[:-4])
1047      return
1048  
1049  
1050  def xdvi(filename=None, debug=False, paper=(14, 11)):
1051      xpdf(filename=filename, paper=paper, crop=False, png=False, prog=False, debug=debug, pt='10pt')
1052      return
1053  
1054  
1055  def LatexFormat(Fmode=True, Dmode=True, ipy=False):
1056      GaLatexPrinter.set_global_settings(
1057          omit_partial_derivative_fraction=Dmode,
1058          omit_function_args=Fmode
1059      )
1060      GaLatexPrinter.ipy = ipy
1061      GaLatexPrinter.redirect()
1062      return
1063  
1064  
1065  off_mode = False
1066  
1067  
1068  def Get_Program(off=False):
1069      global off_mode
1070      off_mode = off
1071      # galgebra 0.5.0
1072      warnings.warn(
1073          "galgebra.printer.Get_Program is deprecated, and exists solely to "
1074          "toggle whether galgebra.printer.Print_Function does anything. If you "
1075          "want to turn off program printing, then just don't call Print_Function!",
1076          DeprecationWarning, stacklevel=2)
1077  
1078  
1079  def Print_Function():
1080      """ Print out the source of the current function """
1081      if off_mode:
1082          return
1083  
1084      tmp_str = inspect.getsource(inspect.currentframe().f_back)
1085      if GaLatexPrinter.latex_flg:
1086          #print '#Code for '+fct_name
1087          print(r'##\begin{lstlisting}[language=Python,showspaces=false,'
1088                r'showstringspaces=false,backgroundcolor=\color{gray},frame=single]')
1089          print(tmp_str)
1090          print('##\\end{lstlisting}')
1091          print('#Code Output:')
1092      else:
1093          print('\n' + 80 * '*')
1094          #print '\nCode for '+fct_name
1095          print(tmp_str)
1096          print('Code output:\n')
1097      return
1098  
1099  
1100  _eval_global_dict = {}
1101  _eval_parse_order = []
1102  
1103  
1104  def def_prec(gd: dict, op_ord: str = '<>|,^,*') -> None:
1105      """
1106      This is used with the ``GAeval()`` function to evaluate a string representing a multivector expression with a revised operator precedence.
1107  
1108      Parameters
1109      ----------
1110      gd :
1111          The ``globals()`` dictionary to lookup variable names in.
1112      op_ord :
1113          The order of operator precedence from high to low with groups of equal precedence separated by commas.
1114          The default precedence, ``'<>|,^,*'``, is that used by Hestenes (:cite:`Hestenes`, p7, :cite:`Doran`, p38).
1115          This means that the ``<``, ``>``, and ``|`` operations have equal
1116          precedence, followed by ``^``, and lastly ``*``.
1117      """
1118      global _eval_global_dict, _eval_parse_order
1119      op_ord_list = op_ord.split(',')
1120      _parser.validate_op_order(op_ord_list)
1121      _eval_global_dict = gd
1122      _eval_parse_order = op_ord_list
1123  
1124  
1125  def GAeval(s: str, pstr: bool = False):
1126      """
1127      Evaluate a multivector expression string ``s``.
1128  
1129      The operator precedence and variable values within the string are
1130      controlled by :func:`def_prec`. The documentation for that function
1131      describes the default precedence.
1132  
1133      The implementation works by adding parenthesis to the input string ``s``
1134      according to the requested precedence, and then calling :func:`eval` on the
1135      result.
1136  
1137      For example consider where ``X``, ``Y``, ``Z``, and ``W`` are multivectors::
1138  
1139          def_prec(globals())
1140          V = GAeval('X|Y^Z*W')
1141  
1142      The *sympy* variable ``V`` would evaluate to ``((X|Y)^Z)*W``.
1143  
1144      Parameters
1145      ----------
1146      s :
1147          The string to evaluate.
1148      pstr :
1149          If ``True``, the values of ``s`` and ``s`` with parenthesis added to
1150          enforce operator precedence are printed.
1151      """
1152  
1153      seval = _parser.parse_line(s, _eval_parse_order)
1154      if pstr:
1155          print(s)
1156          print(seval)
1157      return eval(seval, _eval_global_dict)
1158  
1159  
1160  def Fmt(obj, fmt=0):
1161      if isinstance(obj, (list, tuple, dict)):
1162          n = len(obj)
1163          if isinstance(obj, list):
1164              ldelim = '['
1165              rdelim = ']'
1166          elif isinstance(obj, dict):
1167              ldelim = r'\{'
1168              rdelim = r'\}'
1169          else:
1170              ldelim = '('
1171              rdelim = ')'
1172          if fmt == 1:
1173              latex_str = r' \left ' + ldelim + r' \begin{array}{' + n*'c' + '} '
1174              for cell in obj:
1175                  if isinstance(obj, dict):
1176                      #cell.title = None
1177                      latex_cell = latex(cell) + ' : ' + latex(obj[cell])
1178                  else:
1179                      #title = cell.title
1180                      #cell.title = None
1181                      latex_cell = latex(cell)
1182                  latex_cell = latex_cell.replace('\n', ' ')
1183                  latex_str += latex_cell + ', & '
1184                  #cell.title = title
1185              latex_str = latex_str[:-4]
1186              latex_str += r'\\ \end{array} \right ' + rdelim + ' \n'
1187          else:
1188              latex_str = ''
1189              i = 1
1190              for cell in obj:
1191                  #title = cell.title
1192                  #cell.title = None
1193                  latex_cell = latex(cell)
1194                  latex_cell = latex_cell.replace('\n', ' ')
1195                  #cell.title = title
1196                  if i == 1:
1197                      latex_str += r'\begin{array}{c} \left ' + ldelim + r' ' + latex_cell + r', \right. \\ '
1198                  elif i == n:
1199                      latex_str += r' \left. ' + latex_cell + r'\right ' + rdelim + r' \\ \end{array}'
1200                  else:
1201                      latex_str += r' ' + latex_cell + r', \\'
1202                  i += 1
1203          if isinteractive():  # For Ipython notebook
1204              latex_str = r'\begin{equation*} ' + latex_str + r'\end{equation*}'
1205              return latex_str
1206          else:
1207              return latex_str
1208  
1209      elif isinstance(obj, int):
1210          LatexPrinter.set_global_settings(galgebra_mv_fmt=obj)
1211          return
1212      else:
1213          raise TypeError(str(type(obj)) + ' not allowed arg type in Fmt')
1214  
1215  
1216  class _WithSettings(GaPrintable):
1217      """ Helper class to attach print settings to an object """
1218      def __init__(self, obj, settings: dict = {}):
1219          self._obj = obj
1220          self._settings = settings
1221  
1222      def __do_print(self, printer):
1223          # make a copy of the printer with the specified setting applied
1224          new_printer = copy.copy(printer)
1225          new_printer._settings = copy.copy(new_printer._settings)
1226          new_printer._settings.update(self._settings)
1227          return new_printer._print(self._obj)
1228  
1229      _latex = _pretty = _sympystr = __do_print
1230  
1231  
1232  class _FmtResult(GaPrintable):
1233      """ Object returned from .Fmt methods, which can be printed as latex """
1234      def __new__(cls, obj, label: str) -> GaPrintable:
1235          if label is None:
1236              return obj
1237          self = super().__new__(cls)
1238          self._obj = obj
1239          self._label = label
1240          return self
1241  
1242      def _latex(self, printer):
1243          return self._label + ' = ' + printer._print(self._obj)
1244  
1245      def _sympystr(self, printer):
1246          return self._label + ' = ' + printer._print(self._obj)