r"""
ANSI Enhanced Text Printing, Text Printer and LaTeX Printer for all Geometric Algebra classes
:math:`\LaTeX` printing
-----------------------
.. note::
:mod:`galgebra` works out of the box with the usual
:ref:`sympy printing <sympy:tutorial-printing>` and will show Latex in
IPython by default. In many cases, all that is needed is::
sympy.init_printing(
use_latex='mathjax',
latex_printer=galgebra.printer.latex,
# described below in `GaLatexPrinter`
omit_function_args=True,
omit_partial_derivative_fraction=True,
)
The rest of this section primarily describes an orthogonal feature for
writing out ``.tex`` files with :func:`print`.
The latex printer is turned on with the :func:`Format` function
.. function:: Format(Fmode=True, Dmode=True, ipy=False)
where ``Fmode`` is the function printing mode that suppresses printing arguments,
``Dmode`` is the derivative printing mode that does not use fractions, and
``ipy=True`` is the IPython notebook mode that does not redirect the print output.
The latex output is post processed and displayed with the function
.. function:: xpdf(filename='tmplatex.tex', debug=False)
where ``filename`` is the name of the tex file one would keep for future
inclusion in documents and ``debug=True`` would display the tex file
immediately.
There are three options for printing multivectors in latex. They are
accessed with the multivector member function
.. function:: galgebra.mv.Mv.Fmt(self, fmt=1, title=None)
where ``fmt`` of 1, 2, or 3 determines whether the entire multivector A is
printed entirely on one line, or one grade is printed per line, or
one base is printed per line. If ``title`` is not None then the latex
string generated is of the form::
title + ' = ' + str(A)
where it is assumed that title is a latex math mode string. If title
contains '%' it is treated as a pure latex math mode string. If it
does not contain '%' then the following character mappings are applied::
'grad' -> '\bm{\nabla} '
'*' -> ''
'^' -> '\W '
'|' -> '\cdot '
'>' -> '\lfloor '
'<' -> '\rfloor '
In the case of a print statement of the form::
print(title, A)
everything in the title processing still applies except that the multivector
formatting is one multivector per line.
For print statements of the form::
print(title)
where no program variables are printed if title contains `#` then title
is printed as regular latex line. If title does not contain `#` then
title is printed in equation mode. `%` has the same effect in title as
in the ``Fmt()`` member function.
"""
import copy
import os
import sys
import io
import builtins
import functools
import inspect
import re
import shutil
import warnings
from collections import ChainMap
from sympy import MatrixBase, Basic, S, Symbol, Function, Derivative, Pow
from sympy.printing.str import StrPrinter
from sympy.printing.conventions import split_super_sub
from sympy.printing.latex import (
LatexPrinter, accepted_latex_functions, other_symbols
)
from sympy.core.function import _coeff_isneg
from sympy.core.operations import AssocOp
from sympy import init_printing
from sympy.core.alphabets import greeks
try:
from IPython.display import display, Latex, Math, display_latex
except ImportError:
pass
try:
from sympy.interactive import printing
except ImportError:
pass
from inspect import getouterframes, currentframe
from ._utils import parser as _parser
from ._utils.printable import Printable as SympyPrintable
ZERO_STR = ' 0 '
Format_cnt = 0
ip_cmds = r"""
$\DeclareMathOperator{\Tr}{Tr}
\DeclareMathOperator{\Adj}{Adj}
\newcommand{\bfrac}[2]{\displaystyle\frac{#1}{#2}}
\newcommand{\lp}{\left (}
\newcommand{\rp}{\right )}
\newcommand{\paren}[1]{\lp {#1} \rp}
\newcommand{\half}{\frac{1}{2}}
\newcommand{\llt}{\left <}
\newcommand{\rgt}{\right >}
\newcommand{\abs}[1]{\left |{#1}\right | }
\newcommand{\pdiff}[2]{\bfrac{\partial {#1}}{\partial {#2}}}
\newcommand{\npdiff}[3]{\bfrac{\partial^{#3} {#1}}{\partial {#2}^{#3}}}
\newcommand{\lbrc}{\left \{}
\newcommand{\rbrc}{\right \}}
\newcommand{\W}{\wedge}
\newcommand{\prm}[1]{{#1}'}
\newcommand{\ddt}[1]{\bfrac{d{#1}}{dt}}
\newcommand{\R}{\dagger}
\newcommand{\deriv}[3]{\bfrac{d^{#3}#1}{d{#2}^{#3}}}
\newcommand{\grade}[1]{\left < {#1} \right >}
\newcommand{\f}[2]{{#1}\lp {#2} \rp}
\newcommand{\eval}[2]{\left . {#1} \right |_{#2}}$
"""
SYS_CMD = {'linux2': {'rm': 'rm', 'evince': 'evince', 'null': ' > /dev/null', '&': '&'},
'linux': {'rm': 'rm', 'evince': 'evince', 'null': ' > /dev/null', '&': '&'},
'win32': {'rm': 'del', 'evince': 'start', 'null': ' > NUL', '&': ''},
'darwin': {'rm': 'rm', 'evince': 'open', 'null': ' > /dev/null', '&': '&'}}
[docs]def isinteractive(): #Is ipython running
"""
We will assume that if ipython is running then jupyter notebook is
running.
"""
try:
__IPYTHON__
return True
except NameError:
return False
def ostr(obj, dict_mode=False, indent=True):
return GaPrinter(dict(dict_mode=dict_mode)).doprint(obj)
def find_functions(expr):
f_lst = []
for f in list(expr.atoms(Function)):
if str(f) not in GaPrinter.function_names:
f_lst.append(f)
f_lst += list(expr.atoms(Derivative))
return f_lst
def coef_simplify(expr):
# fcts = find_functions(expr)
# return expr.collect(fcts)
return expr
[docs]def oprint(*args, dict_mode=False):
"""
Debug printing for iterated (list/tuple/dict/set) objects. args is
of form ``(title1, object1, title2, object2, ...)`` and prints::
title1 = object1
title2 = object2
...
If you only wish to print a title set ``object = None``.
"""
if isinstance(args[0], str) or args[0] is None:
titles = args[0::2]
objs = args[1::2]
strs = [
ostr(obj, dict_mode) if obj is not None else None
for obj in objs
]
n = max((
len(title)
for title, s in zip(titles, strs)
if s is not None and '\n' not in s
), default=0)
for title, s in zip(titles, strs):
if s is None:
print(title)
else:
npad = n - len(title)
if '\n' in s:
print(title + ':\n' + s)
else:
print(title + npad * ' ' + ' = ' + s)
else:
for arg in args:
print(ostr(arg, dict_mode))
_ansi_colors = {
'black': '\033[0;30m', 'dark gray': '\033[1;30m',
'red': '\033[0;31m', 'bright red': '\033[1;31m',
'green': '\033[0;32m', 'bright green': '\033[1;32m',
'yellow': '\033[0;33m', 'bright yellow': '\033[1;33m',
'blue': '\033[0;34m', 'bright blue': '\033[1;34m',
'purple': '\033[0;35m', 'bright purple': '\033[1;35m',
'cyan': '\033[0;36m', 'bright cyan': '\033[1;36m',
'bright gray': '\033[0;37m', 'white': '\033[1;37m',
}
_ansi_reset = '\033[0m'
def _apply_ansi_color(color: str, text: str) -> str:
if color is None:
return text
else:
return _ansi_colors.get(color, color) + text + _ansi_reset
[docs]def enhance_print(base='blue', fct='red', deriv='cyan'):
""" Enable ansi color codes in plain-text formatting.
Valid color names are:
{colors}
Pass ``None`` to disable coloring.
"""
GaPrinter.set_global_settings(
function_color=fct,
derivative_color=deriv,
basis_vector_color=base,
)
# patch the docstring using our known color names
enhance_print.__doc__ = enhance_print.__doc__.format(colors='\n '.join(
" - ``{!r}``".format(k) for k in _ansi_colors
))
[docs]def Eprint(*args, **kwargs):
""" Alias for :func:`enhance_print` """
return enhance_print(*args, **kwargs)
[docs]class GaPrinter(StrPrinter):
"""
This subclass of the builtin string printer makes some customizations which
make output a little more readable for GA usage.
The customizations are:
* :class:`~sympy.core.function.Derivative` objects are printed as ``D{x}y``
instead of ``Derivative(y, x)``.
* :class:`~sympy.core.function.Function` objects are printed without
arguments. This is useful for defining fields over
:attr:`~galgebra.ga.Ga.coords`, but sometimes misfires.
* A new ``dict_mode`` setting, which when ``True`` prints :class:`dict`
objects with ``->`` and one entry per line.
* New ANSI color settings:
* ``derivative_color``, for adjusting the color of ``D{x}``.
* ``function_color``, for adjusting the color of argument-less functions.
* ``basis_vector_color``, for adjusting the color of basis vector symbols.
When :mod:`galgebra.printer` is imported, builtin sympy objects are patched
to use this printer for their ``__repr__`` instead of the builtin
:class:`~sympy.printing.str.StrPrinter`. There is currently no way to
disable this patching.
"""
_default_settings = ChainMap({
# if true, print dicts with `->` instead of `:`, one entry per line
"dict_mode": False,
"derivative_color": None,
"function_color": None,
"basis_vector_color": None,
}, StrPrinter._default_settings)
function_names = ('acos', 'acosh', 'acot', 'acoth', 'arg', 'asin', 'asinh',
'atan', 'atan2', 'atanh', 'ceiling', 'conjugate', 'cos',
'cosh', 'cot', 'coth', 'exp', 'floor', 'im', 'log', 're',
'root', 'sin', 'sinh', 'sqrt', 'sign', 'tan', 'tanh', 'Abs')
def _print_Function(self, expr):
name = expr.func.__name__
if expr.func.nargs is not None:
if name in GaPrinter.function_names:
return expr.func.__name__ + "(%s)" % self.stringify(expr.args, ", ")
return _apply_ansi_color(
self._settings["function_color"], "%s" % (name,))
def _print_BasisVectorSymbol(self, expr):
return _apply_ansi_color(
self._settings["basis_vector_color"], self._print_Symbol(expr))
def _print_Derivative(self, expr):
# Break the following to support both py 2 & 3
# function, *diff_args = expr.args
function = expr.args[0]
diff_args = expr.args[1:]
xi = []
ni = []
for x, n in diff_args:
if x in xi:
i = xi.index(x)
ni[i] += n
else:
xi.append(self._print(x))
ni.append(n)
s = 'D'
for x, n in zip(xi, ni):
s += '{' + str(x) + '}'
if n > 1:
s += '^' + str(n)
s += str(self._print(function))
return _apply_ansi_color(self._settings["derivative_color"], s)
def _print_dict(self, expr):
if not self._settings['dict_mode']:
return super()._print_dict(expr)
return '\n'.join(
'{} -> {}'.format(self._print(k), self._print(v))
for k, v in expr.items()
)
# Inheriting from SympyPrintable ensure we take part in interactive printing
# customization
[docs]class GaPrintable(SympyPrintable):
""" Mixin class providing default implementations of printing hooks """
def __ga_print_str__(self):
if GaLatexPrinter.latex_flg:
return GaLatexPrinter().doprint(self)
else:
return GaPrinter().doprint(self)
def __repr__(self):
return GaPrinter().doprint(self)
# Change sympy builtins to use our printer by default.
# We do this because we always have done, and stopping now would break
# compatibility.
if issubclass(Basic, SympyPrintable):
SympyPrintable.__ga_print_str__ = GaPrintable.__ga_print_str__
SympyPrintable.__repr__ = GaPrintable.__repr__
else:
# sympy < 1.7
Basic.__ga_print_str__ = GaPrintable.__ga_print_str__
Basic.__repr__ = GaPrintable.__repr__
MatrixBase.__ga_print_str__ = GaPrintable.__ga_print_str__
MatrixBase.__repr__ = GaPrintable.__repr__
# This is the lesser of two evils. Previously, we overwrote `Basic.__str__` in
# order to customise `print(sympy)`. This broke a bunch of assumptions inside
# sympy, so isn't safe. Instead of clobbering `__str__`, we add a
# `__ga_print_str__` attribute, and have `print` use it if present.
_old_print = builtins.print
@functools.wraps(_old_print)
def _print(*values, **kwargs):
values_new = []
for v in values:
try:
f = type(v).__ga_print_str__
except AttributeError:
values_new.append(v)
else:
values_new.append(f(v))
_old_print(*values_new, **kwargs)
builtins.print = _print
[docs]class GaLatexPrinter(LatexPrinter):
r"""
This subclass of the builtin string printer makes some customizations which
make output a little more readable for GA usage.
The customizations are:
* A new ``omit_partial_derivative_fraction`` setting that affects the
printing of :class:`~sympy.core.function.Derivative` objects, with
possible values:
* ``False``, to use the *sympy* default, :math:`\pdiff{f}{x}`.
* ``True``, to use a condensed notation, :math:`\partial_{x}f`.
* A new ``omit_function_args`` setting which affects the printing of
:class:`~sympy.core.function.Function` objects, with possible values:
* ``False``, to use the sympy default, :math:`{{f}\lp {x,y,z} \rp }`.
* ``True``, to print as :math:`f`. This is similar to the behavior of
:class:`GaPrinter`.
* A change to function printing to allow function names to contain
subscripts and superscripts.
* Use of ``boldsymbol`` instead of ``mathbf`` for bold symbol names.
Note that this printer is not *required* for using GA objects, the base
class printer will work fine too.
"""
# overrides of base class settings, and new settings for our printers
_default_settings = ChainMap({
"mat_str": "array",
"omit_function_args": False,
"omit_partial_derivative_fraction": False,
}, LatexPrinter._default_settings)
latex_flg = False
latex_str = ''
ipy = False
preamble = \
"""
\\pagestyle{empty}
\\usepackage[latin1]{inputenc}
\\usepackage{amsmath}
\\usepackage{amsfonts}
\\usepackage{amssymb}
\\usepackage{amsbsy}
\\usepackage{tensor}
\\usepackage{listings}
\\usepackage{color}
\\usepackage{xcolor}
\\usepackage{bm}
\\usepackage{breqn}
\\definecolor{gray}{rgb}{0.95,0.95,0.95}
\\setlength{\\parindent}{0pt}
\\DeclareMathOperator{\\Tr}{Tr}
\\DeclareMathOperator{\\Adj}{Adj}
\\newcommand{\\bfrac}[2]{\\displaystyle\\frac{#1}{#2}}
\\newcommand{\\lp}{\\left (}
\\newcommand{\\rp}{\\right )}
\\newcommand{\\paren}[1]{\\lp {#1} \\rp}
\\newcommand{\\half}{\\frac{1}{2}}
\\newcommand{\\llt}{\\left <}
\\newcommand{\\rgt}{\\right >}
\\newcommand{\\abs}[1]{\\left |{#1}\\right | }
\\newcommand{\\pdiff}[2]{\\bfrac{\\partial {#1}}{\\partial {#2}}}
\\newcommand{\\lbrc}{\\left \\{}
\\newcommand{\\rbrc}{\\right \\}}
\\newcommand{\\W}{\\wedge}
\\newcommand{\\prm}[1]{{#1}'}
\\newcommand{\\ddt}[1]{\\bfrac{d{#1}}{dt}}
\\newcommand{\\R}{\\dagger}
\\newcommand{\\deriv}[3]{\\bfrac{d^{#3}#1}{d{#2}^{#3}}}
\\newcommand{\\grade}[1]{\\left < {#1} \\right >}
\\newcommand{\\f}[2]{{#1}\\lp{#2}\\rp}
\\newcommand{\\eval}[2]{\\left . {#1} \\right |_{#2}}
\\newcommand{\\Nabla}{\\boldsymbol{\\nabla}}
\\newcommand{\\eb}{\\boldsymbol{e}}
\\usepackage{float}
\\floatstyle{plain} % optionally change the style of the new float
\\newfloat{Code}{H}{myc}
\\lstloadlanguages{Python}
\\begin{document}
"""
postscript = '\\end{document}\n'
macros = '\\newcommand{\\f}[2]{{#1}\\left ({#2}\\right )}'
# Used by _print_Symbol
greek_translated = {'lamda': 'lambda', 'Lamda': 'Lambda'}
other = other_symbols | {'infty'}
special_alphabet = list(reversed(sorted(list(greeks) + list(other), key=len)))
@staticmethod
def redirect():
GaLatexPrinter.latex_flg = True
if GaLatexPrinter.ipy:
pass
else:
GaLatexPrinter.stdout = sys.stdout
sys.stdout = io.StringIO()
@staticmethod
def restore():
if GaLatexPrinter.latex_flg:
if not GaLatexPrinter.ipy:
GaLatexPrinter.latex_str += sys.stdout.getvalue()
GaLatexPrinter.latex_flg = False
if not GaLatexPrinter.ipy:
sys.stdout = GaLatexPrinter.stdout
def _print_Pow(self, expr):
base = self._print(expr.base)
if ('_' in base or '^' in base) and 'cdot' not in base:
mode = True
else:
mode = False
# Treat x**Rational(1, n) as special case
if expr.exp.is_Rational and abs(expr.exp.p) == 1 and expr.exp.q != 1:
#base = self._print(expr.base)
expq = expr.exp.q
if expq == 2:
tex = r"\sqrt{%s}" % base
elif self._settings['itex']:
tex = r"\root{%d}{%s}" % (expq, base)
else:
tex = r"\sqrt[%d]{%s}" % (expq, base)
if expr.exp.is_negative:
return r"\frac{1}{%s}" % tex
else:
return tex
elif self._settings['fold_frac_powers'] \
and expr.exp.is_Rational \
and expr.exp.q != 1:
base, p, q = self._print(expr.base), expr.exp.p, expr.exp.q
if mode:
return r"{\left ( %s \right )}^{%s/%s}" % (base, p, q)
else:
return r"%s^{%s/%s}" % (base, p, q)
elif expr.exp.is_Rational and expr.exp.is_negative and expr.base.is_Function:
# Things like 1/x
return r"\frac{%s}{%s}" % \
(1, self._print(Pow(expr.base, -expr.exp)))
else:
if expr.base.is_Function:
return r"{%s}^{%s}" % (self._print(expr.base), self._print(expr.exp))
else:
if expr.is_commutative and expr.exp == -1:
#solves issue 1030
#As Mul always simplify 1/x to x**-1
#The objective is achieved with this hack
#first we get the latex for -1 * expr,
#which is a Mul expression
tex = self._print(S.NegativeOne * expr).strip()
#the result comes with a minus and a space, so we remove
if tex[:1] == "-":
return tex[1:].strip()
if self._needs_brackets(expr.base):
tex = r"\left(%s\right)^{%s}"
else:
if mode:
tex = r"{\left ( %s \right )}^{%s}"
else:
tex = r"%s^{%s}"
return tex % (self._print(expr.base),
self._print(expr.exp))
def _print_Symbol(self, expr, style='plain'):
def str_symbol(name_str):
def translate(s):
tmp = s
parse_dict = {}
i_sub = 1
for glyph in GaLatexPrinter.special_alphabet:
escaped_glyph = '\\' + glyph
if glyph in tmp:
parse_sym = '????' + str(i_sub)
i_sub += 1
# If this glyph is already escaped, avoid escaping again
translated_glyph = (escaped_glyph + ' ') if escaped_glyph not in tmp else glyph
parse_dict[parse_sym] = translated_glyph
tmp = tmp.replace(glyph, parse_sym)
for parse_sym in parse_dict:
tmp = tmp.replace(parse_sym, parse_dict[parse_sym])
for glyph in GaLatexPrinter.greek_translated:
if glyph in tmp:
tmp = tmp.replace(glyph, GaLatexPrinter.greek_translated[glyph])
return tmp
name, supers, subs = split_super_sub(name_str)
name = translate(name)
if style == 'bold':
name = '\\boldsymbol{' + name + '}'
supers = list(map(translate, supers))
subs = list(map(translate, subs))
# glue all items together:
if len(supers) > 0:
name += "^{%s}" % " ".join(supers)
if len(subs) > 0:
name += "_{%s}" % " ".join(subs)
return name
if expr in self._settings['symbol_names']:
return self._settings['symbol_names'][expr]
return str_symbol(expr.name)
def _print_Function(self, expr, exp=None):
func = expr.func.__name__
name = func
if hasattr(self, '_print_' + func):
return getattr(self, '_print_' + func)(expr, exp)
else:
args = [str(self._print(arg)) for arg in expr.args]
# How inverse trig functions should be displayed, formats are:
# abbreviated: asin, full: arcsin, power: sin^-1
inv_trig_style = self._settings['inv_trig_style']
# If we are dealing with a power-style inverse trig function
inv_trig_power_case = False
# If it is applicable to fold the argument brackets
can_fold_brackets = self._settings['fold_func_brackets'] and \
len(args) == 1 and not self._needs_function_brackets(expr.args[0])
inv_trig_table = ["asin", "acos", "atan", "acot", "acosh", "asinh", "atanh"]
# If the function is an inverse trig function, handle the style
if func in inv_trig_table:
if inv_trig_style == "abbreviated":
func = func
elif inv_trig_style == "full":
func = "arc" + func[1:]
elif inv_trig_style == "power":
func = func[1:]
inv_trig_power_case = True
# Can never fold brackets if we're raised to a power
if exp is not None:
can_fold_brackets = False
if inv_trig_power_case:
if func in accepted_latex_functions:
name = r"\%s^{-1}" % func
else:
name = r"\operatorname{%s}^{-1}" % func
elif exp is not None:
if func in accepted_latex_functions:
name = r"\%s^{%s}" % (func, exp)
else:
name = latex(Symbol(func)) + ' '
if '_' in func or '^' in func:
name = r'{\left ( ' + name + r'\right ) }^{' + exp + '}'
else:
name += '^{' + exp + '}'
else:
if func in accepted_latex_functions:
name = r"\%s" % func
else:
name = latex(Symbol(func)) + ' '
if exp is not None:
if '_' in name or '^' in name:
name = r'\left ( ' + name + r'\right )^{' + exp + '}'
else:
name += '^{' + exp + '}'
if can_fold_brackets:
if func in accepted_latex_functions:
# Wrap argument safely to avoid parse-time conflicts
# with the function name itself
name += r" {%s}"
else:
if not self._settings["omit_function_args"]:
name += r"%s"
else:
if func in accepted_latex_functions or not self._settings["omit_function_args"]:
name += r"{\left (%s \right )}"
if inv_trig_power_case and exp is not None:
name += r"^{%s}" % exp
if func in accepted_latex_functions or not self._settings["omit_function_args"]:
if len(args) == 1:
name = name % args[0]
else:
name = name % ",".join(args)
return name
def _print_Derivative(self, expr):
dim = len(expr.variables)
imax = 1
if dim == 1:
if self._settings["omit_partial_derivative_fraction"]:
tex = r"\partial_{%s}" % self._print(expr.variables[0])
else:
tex = r"\frac{\partial}{\partial %s}" % self._print(expr.variables[0])
else:
multiplicity, i, tex = [], 1, ""
current = expr.variables[0]
for symbol in expr.variables[1:]:
if symbol == current:
i = i + 1
else:
multiplicity.append((current, i))
current, i = symbol, 1
else:
imax = max(imax, i)
multiplicity.append((current, i))
if self._settings["omit_partial_derivative_fraction"]:
tex = ''
for x, i in multiplicity:
if i == 1:
tex += r"\partial_{%s}" % (self._print(x),)
else:
tex += r"\partial^{%i}_{%s}" % (i, self._print(x))
else:
for x, i in multiplicity:
if i == 1:
tex += r"\partial %s" % self._print(x)
else:
tex += r"\partial^{%s} %s" % (i, self._print(x))
tex = r"\frac{\partial^{%s}}{%s} " % (dim, tex)
if isinstance(expr.expr, AssocOp):
s = r"%s\left(%s\right)" % (tex, self._print(expr.expr))
else:
s = r"%s %s" % (tex, self._print(expr.expr))
return s
def _print_Determinant(self, expr):
# sympy `uses |X|` by default, we want `det (X)`
return r"\det\left ( {}\right )".format(self._print(expr.args[0]))
@staticmethod
def latex(expr, **settings):
if not isinstance(expr, list):
return GaLatexPrinter(settings).doprint(expr)
else:
s = '\\begin{align*}'
for x in expr:
s += '\n & ' + latex(x) + ' \\\\'
s += '\n\\end{align*}'
return s
[docs]def latex(expr, **settings) -> str:
"""
Get the latex representation of expr using :class:`GaLatexPrinter`.
Takes the same options as :func:`sympy.printing.latex.latex`; see that
function for more information.
This can be used as the ``latex_printer`` argument to
:func:`~sympy.interactive.printing.init_printing` to make IPython always
use :class:`GaLatexPrinter`.
"""
return GaLatexPrinter(settings).doprint(expr)
[docs]def print_latex(expr, **settings):
"""Prints LaTeX representation of the given expression.
Takes the same settings as :func:`latex`."""
print(latex(expr, **settings))
def _texify(s: str) -> str:
""" Convert python GA operator notation to LaTeX """
repl_pairs = [
(r'\|', r'\cdot '),
(r'\^(?!{)', r'\W '),
(r'\*', ' '),
(r'\brgrad\b', r'\bar{\boldsymbol{\nabla}} '),
(r'\bgrad\b', r'\boldsymbol{\nabla} '),
(r'>>', r' \times '),
(r'<<', r' \bar{\times} '),
(r'<', r'\rfloor '),
(r'>', r'\lfloor '),
]
def repl_func(m):
# only one group will be present, use the corresponding match
return next(
r
for (p, r), g in zip(repl_pairs, m.groups())
if g is not None
)
pattern = '|'.join("({})".format(p) for p, _ in repl_pairs)
return re.sub(pattern, repl_func, s)
[docs]def tex(paper=(14, 11), debug=False, prog=False, pt='10pt'):
r"""
Post processes LaTeX output (see comments below), adds preamble and
postscript.
This postprocessing has two main behaviors:
1. Converting strings on the left hand side of the last ``=`` into TeX.
This translates the ``*``, ``^``, ``|``, ``>``, ``<``, ``<<``, ``>>``,
``grad``, and ``rgrad`` operators of galgebra into the appropriate latex
operators. If there is no ``=`` in the line, no conversion is applied.
2. Wrapping lines of latex into ``equation*`` environments if they are not
already in environments, and moving labels that were prepended outside
``align`` environments inside those environments.
Both behaviors are applied line by line, unless a line starts with the
following text:
``#%`` or ``%``
Disables only behavior 1 for the rest of the line.
``##``
Disables behaviors 1 and 2 until the end of the next line starting with
``##``. This includes processing any of the other special characters,
which will be emitted verbatim.
``#``
Disables behaviors 1 and 2 for the rest of the line.
We assume that if :func:`tex` is called, then :func:`Format` has been called
at the beginning of the program.
"""
latex_str = GaLatexPrinter.latex_str + sys.stdout.getvalue()
GaLatexPrinter.latex_str = ''
GaLatexPrinter.restore()
r"""
Each line in the latex_str is interpreted to be an equation or align
environment. If the line does not begin with '\begin{align*}' then
'begin{equation*}' will be added to the beginning of the line and
'\end{equation*}' to the end of the line.
The latex strings generated by galgebra and sympy expressions for
printing must not contain '\n' except as the final character. Thus
all '\n' must be removed from a compound (not a simple type) expression
and a '\n' added to the end of the string to delimit it when the string
is generated.
"""
latex_lst = latex_str.split('\n')
latex_str = ''
code_flg = False
for latex_line in latex_lst:
if not latex_line:
pass
elif latex_line.startswith('##'):
# a post-processing toggle used by `Print_Function`
code_flg = not code_flg
latex_line = latex_line[2:]
elif code_flg:
pass
elif latex_line.startswith('#') and not latex_line.startswith('#%'):
# do not process this line
latex_line = latex_line[1:]
else:
# two different spellings of "do not process the LHS"
if latex_line.startswith('%'):
latex_line = latex_line[1:]
elif latex_line.startswith('#%'):
latex_line = latex_line[2:]
# otherwise, process it if we can find it
elif '=' in latex_line:
lhs, latex_line = latex_line.rsplit('=', 1)
latex_line = _texify(lhs) + '=' + latex_line
# in either case, perform the environment wrapping
if r'\begin{align*}' in latex_line:
latex_line = r'\begin{align*} ' + latex_line.replace(r'\begin{align*}', '', 1).lstrip()
else:
latex_line = r'\begin{equation*} ' + latex_line.strip() + r' \end{equation*}'
latex_str += latex_line + '\n'
latex_str = latex_str.replace('\n\n', '\n')
if prog:
with open(sys.argv[0], 'r') as prog_file:
prog_str = prog_file.read()
prog_str = '{\\Large \\bf Program:}\\begin{lstlisting}[language=Python,showspaces=false,' + \
'showstringspaces=false]\n' + \
prog_str + '\n\\end{lstlisting}\n {\\Large \\bf Code Output:} \n'
latex_str = prog_str + latex_str
if debug:
print(latex_str)
if paper == 'letter':
paper_size = \
"""
\\documentclass[@10pt@,fleqn]{report}
"""
else:
paper_size = \
"""
\\documentclass[@10pt@,fleqn]{report}
\\usepackage[vcentering]{geometry}
"""
if paper == 'landscape':
paper = [11, 8.5]
paper_size += '\\geometry{papersize={' + str(paper[0]) + \
'in,' + str(paper[1]) + 'in},total={' + str(paper[0] - 1) + \
'in,' + str(paper[1] - 1) + 'in}}\n'
paper_size = paper_size.replace('@10pt@', pt)
latex_str = paper_size + GaLatexPrinter.preamble + latex_str + GaLatexPrinter.postscript
return latex_str
[docs]def xpdf(filename=None, paper=(14, 11), crop=False, png=False, prog=False, debug=False, pt='10pt', pdfprog='pdflatex'):
"""
Post processes LaTeX output (see comments below), adds preamble and
postscript, generates tex file, inputs file to latex, displays resulting
pdf file.
Arg Value Result
pdfprog 'pdflatex' Use pdfprog to generate pdf output, only generate tex if pdfprog is None
crop True Use "pdfcrop" to crop output file (pdfcrop must be installed, linux only)
png True Use "convert" to produce png output (imagemagick must be installed, linux only)
We assume that if xpdf() is called then Format() has been called at the beginning of the program.
"""
sys_cmd = SYS_CMD[sys.platform]
latex_str = tex(paper=paper, debug=debug, prog=prog, pt=pt)
if filename is None:
pyfilename = sys.argv[0]
rootfilename = pyfilename.replace('.py', '')
filename = rootfilename + '.tex'
if debug:
print('latex file =', filename)
latex_file = open(filename, 'w')
latex_file.write(latex_str)
latex_file.close()
latex_str = None
if pdfprog is None:
return
pdflatex = shutil.which(pdfprog)
if debug:
print('pdflatex path =', pdflatex)
if pdfprog is not None:
if debug: # Display latex excution output for debugging purposes
os.system(pdfprog + ' ' + filename[:-4])
else: # Works for Linux don't know about Windows
os.system(pdfprog + ' ' + filename[:-4] + sys_cmd['null'])
print_cmd = sys_cmd['evince'] + ' ' + filename[:-4] + '.pdf ' + sys_cmd['&']
print(print_cmd)
os.system(print_cmd)
eval(input('!!!!Return to continue!!!!\n'))
if debug:
os.system(sys_cmd['rm'] + ' ' + filename[:-4] + '.aux ' + filename[:-4] + '.log')
else:
os.system(sys_cmd['rm'] + ' ' + filename[:-4] + '.aux ' + filename[:-4] + '.log ' + filename[:-4] + '.tex')
if crop:
os.system('pdfcrop ' + filename[:-4] + '.pdf')
os.remove(filename[:-4] + '.pdf')
os.rename(filename[:-4] + '-crop.pdf', filename[:-4] + '.pdf')
if png:
os.system('Pdf2Png ' + filename[:-4])
return
def xdvi(filename=None, debug=False, paper=(14, 11)):
xpdf(filename=filename, paper=paper, crop=False, png=False, prog=False, debug=debug, pt='10pt')
return
def LatexFormat(Fmode=True, Dmode=True, ipy=False):
GaLatexPrinter.set_global_settings(
omit_partial_derivative_fraction=Dmode,
omit_function_args=Fmode
)
GaLatexPrinter.ipy = ipy
GaLatexPrinter.redirect()
return
off_mode = False
def Get_Program(off=False):
global off_mode
off_mode = off
# galgebra 0.5.0
warnings.warn(
"galgebra.printer.Get_Program is deprecated, and exists solely to "
"toggle whether galgebra.printer.Print_Function does anything. If you "
"want to turn off program printing, then just don't call Print_Function!",
DeprecationWarning, stacklevel=2)
[docs]def Print_Function():
""" Print out the source of the current function """
if off_mode:
return
tmp_str = inspect.getsource(inspect.currentframe().f_back)
if GaLatexPrinter.latex_flg:
#print '#Code for '+fct_name
print(r'##\begin{lstlisting}[language=Python,showspaces=false,'
r'showstringspaces=false,backgroundcolor=\color{gray},frame=single]')
print(tmp_str)
print('##\\end{lstlisting}')
print('#Code Output:')
else:
print('\n' + 80 * '*')
#print '\nCode for '+fct_name
print(tmp_str)
print('Code output:\n')
return
_eval_global_dict = {}
_eval_parse_order = []
[docs]def def_prec(gd: dict, op_ord: str = '<>|,^,*') -> None:
"""
This is used with the ``GAeval()`` function to evaluate a string representing a multivector expression with a revised operator precedence.
Parameters
----------
gd :
The ``globals()`` dictionary to lookup variable names in.
op_ord :
The order of operator precedence from high to low with groups of equal precedence separated by commas.
The default precedence, ``'<>|,^,*'``, is that used by Hestenes (:cite:`Hestenes`, p7, :cite:`Doran`, p38).
This means that the ``<``, ``>``, and ``|`` operations have equal
precedence, followed by ``^``, and lastly ``*``.
"""
global _eval_global_dict, _eval_parse_order
op_ord_list = op_ord.split(',')
_parser.validate_op_order(op_ord_list)
_eval_global_dict = gd
_eval_parse_order = op_ord_list
[docs]def GAeval(s: str, pstr: bool = False):
"""
Evaluate a multivector expression string ``s``.
The operator precedence and variable values within the string are
controlled by :func:`def_prec`. The documentation for that function
describes the default precedence.
The implementation works by adding parenthesis to the input string ``s``
according to the requested precedence, and then calling :func:`eval` on the
result.
For example consider where ``X``, ``Y``, ``Z``, and ``W`` are multivectors::
def_prec(globals())
V = GAeval('X|Y^Z*W')
The *sympy* variable ``V`` would evaluate to ``((X|Y)^Z)*W``.
Parameters
----------
s :
The string to evaluate.
pstr :
If ``True``, the values of ``s`` and ``s`` with parenthesis added to
enforce operator precedence are printed.
"""
seval = _parser.parse_line(s, _eval_parse_order)
if pstr:
print(s)
print(seval)
return eval(seval, _eval_global_dict)
def Fmt(obj, fmt=0):
if isinstance(obj, (list, tuple, dict)):
n = len(obj)
if isinstance(obj, list):
ldelim = '['
rdelim = ']'
elif isinstance(obj, dict):
ldelim = r'\{'
rdelim = r'\}'
else:
ldelim = '('
rdelim = ')'
if fmt == 1:
latex_str = r' \left ' + ldelim + r' \begin{array}{' + n*'c' + '} '
for cell in obj:
if isinstance(obj, dict):
#cell.title = None
latex_cell = latex(cell) + ' : ' + latex(obj[cell])
else:
#title = cell.title
#cell.title = None
latex_cell = latex(cell)
latex_cell = latex_cell.replace('\n', ' ')
latex_str += latex_cell + ', & '
#cell.title = title
latex_str = latex_str[:-4]
latex_str += r'\\ \end{array} \right ' + rdelim + ' \n'
else:
latex_str = ''
i = 1
for cell in obj:
#title = cell.title
#cell.title = None
latex_cell = latex(cell)
latex_cell = latex_cell.replace('\n', ' ')
#cell.title = title
if i == 1:
latex_str += r'\begin{array}{c} \left ' + ldelim + r' ' + latex_cell + r', \right. \\ '
elif i == n:
latex_str += r' \left. ' + latex_cell + r'\right ' + rdelim + r' \\ \end{array}'
else:
latex_str += r' ' + latex_cell + r', \\'
i += 1
if isinteractive(): # For Ipython notebook
latex_str = r'\begin{equation*} ' + latex_str + r'\end{equation*}'
return latex_str
else:
return latex_str
elif isinstance(obj, int):
LatexPrinter.set_global_settings(galgebra_mv_fmt=obj)
return
else:
raise TypeError(str(type(obj)) + ' not allowed arg type in Fmt')
class _WithSettings(GaPrintable):
""" Helper class to attach print settings to an object """
def __init__(self, obj, settings: dict = {}):
self._obj = obj
self._settings = settings
def __do_print(self, printer):
# make a copy of the printer with the specified setting applied
new_printer = copy.copy(printer)
new_printer._settings = copy.copy(new_printer._settings)
new_printer._settings.update(self._settings)
return new_printer._print(self._obj)
_latex = _pretty = _sympystr = __do_print
class _FmtResult(GaPrintable):
""" Object returned from .Fmt methods, which can be printed as latex """
def __new__(cls, obj, label: str) -> GaPrintable:
if label is None:
return obj
self = super().__new__(cls)
self._obj = obj
self._label = label
return self
def _latex(self, printer):
return self._label + ' = ' + printer._print(self._obj)
def _sympystr(self, printer):
return self._label + ' = ' + printer._print(self._obj)