Spaces:
Sleeping
Sleeping
""" | |
A Printer which converts an expression into its LaTeX equivalent. | |
""" | |
from __future__ import annotations | |
from typing import Any, Callable, TYPE_CHECKING | |
import itertools | |
from sympy.core import Add, Float, Mod, Mul, Number, S, Symbol, Expr | |
from sympy.core.alphabets import greeks | |
from sympy.core.containers import Tuple | |
from sympy.core.function import Function, AppliedUndef, Derivative | |
from sympy.core.operations import AssocOp | |
from sympy.core.power import Pow | |
from sympy.core.sorting import default_sort_key | |
from sympy.core.sympify import SympifyError | |
from sympy.logic.boolalg import true, BooleanTrue, BooleanFalse | |
# sympy.printing imports | |
from sympy.printing.precedence import precedence_traditional | |
from sympy.printing.printer import Printer, print_function | |
from sympy.printing.conventions import split_super_sub, requires_partial | |
from sympy.printing.precedence import precedence, PRECEDENCE | |
from mpmath.libmp.libmpf import prec_to_dps, to_str as mlib_to_str | |
from sympy.utilities.iterables import has_variety, sift | |
import re | |
if TYPE_CHECKING: | |
from sympy.tensor.array import NDimArray | |
from sympy.vector.basisdependent import BasisDependent | |
# Hand-picked functions which can be used directly in both LaTeX and MathJax | |
# Complete list at | |
# https://docs.mathjax.org/en/latest/tex.html#supported-latex-commands | |
# This variable only contains those functions which SymPy uses. | |
accepted_latex_functions = ['arcsin', 'arccos', 'arctan', 'sin', 'cos', 'tan', | |
'sinh', 'cosh', 'tanh', 'sqrt', 'ln', 'log', 'sec', | |
'csc', 'cot', 'coth', 're', 'im', 'frac', 'root', | |
'arg', | |
] | |
tex_greek_dictionary = { | |
'Alpha': r'\mathrm{A}', | |
'Beta': r'\mathrm{B}', | |
'Gamma': r'\Gamma', | |
'Delta': r'\Delta', | |
'Epsilon': r'\mathrm{E}', | |
'Zeta': r'\mathrm{Z}', | |
'Eta': r'\mathrm{H}', | |
'Theta': r'\Theta', | |
'Iota': r'\mathrm{I}', | |
'Kappa': r'\mathrm{K}', | |
'Lambda': r'\Lambda', | |
'Mu': r'\mathrm{M}', | |
'Nu': r'\mathrm{N}', | |
'Xi': r'\Xi', | |
'omicron': 'o', | |
'Omicron': r'\mathrm{O}', | |
'Pi': r'\Pi', | |
'Rho': r'\mathrm{P}', | |
'Sigma': r'\Sigma', | |
'Tau': r'\mathrm{T}', | |
'Upsilon': r'\Upsilon', | |
'Phi': r'\Phi', | |
'Chi': r'\mathrm{X}', | |
'Psi': r'\Psi', | |
'Omega': r'\Omega', | |
'lamda': r'\lambda', | |
'Lamda': r'\Lambda', | |
'khi': r'\chi', | |
'Khi': r'\mathrm{X}', | |
'varepsilon': r'\varepsilon', | |
'varkappa': r'\varkappa', | |
'varphi': r'\varphi', | |
'varpi': r'\varpi', | |
'varrho': r'\varrho', | |
'varsigma': r'\varsigma', | |
'vartheta': r'\vartheta', | |
} | |
other_symbols = {'aleph', 'beth', 'daleth', 'gimel', 'ell', 'eth', 'hbar', | |
'hslash', 'mho', 'wp'} | |
# Variable name modifiers | |
modifier_dict: dict[str, Callable[[str], str]] = { | |
# Accents | |
'mathring': lambda s: r'\mathring{'+s+r'}', | |
'ddddot': lambda s: r'\ddddot{'+s+r'}', | |
'dddot': lambda s: r'\dddot{'+s+r'}', | |
'ddot': lambda s: r'\ddot{'+s+r'}', | |
'dot': lambda s: r'\dot{'+s+r'}', | |
'check': lambda s: r'\check{'+s+r'}', | |
'breve': lambda s: r'\breve{'+s+r'}', | |
'acute': lambda s: r'\acute{'+s+r'}', | |
'grave': lambda s: r'\grave{'+s+r'}', | |
'tilde': lambda s: r'\tilde{'+s+r'}', | |
'hat': lambda s: r'\hat{'+s+r'}', | |
'bar': lambda s: r'\bar{'+s+r'}', | |
'vec': lambda s: r'\vec{'+s+r'}', | |
'prime': lambda s: "{"+s+"}'", | |
'prm': lambda s: "{"+s+"}'", | |
# Faces | |
'bold': lambda s: r'\boldsymbol{'+s+r'}', | |
'bm': lambda s: r'\boldsymbol{'+s+r'}', | |
'cal': lambda s: r'\mathcal{'+s+r'}', | |
'scr': lambda s: r'\mathscr{'+s+r'}', | |
'frak': lambda s: r'\mathfrak{'+s+r'}', | |
# Brackets | |
'norm': lambda s: r'\left\|{'+s+r'}\right\|', | |
'avg': lambda s: r'\left\langle{'+s+r'}\right\rangle', | |
'abs': lambda s: r'\left|{'+s+r'}\right|', | |
'mag': lambda s: r'\left|{'+s+r'}\right|', | |
} | |
greek_letters_set = frozenset(greeks) | |
_between_two_numbers_p = ( | |
re.compile(r'[0-9][} ]*$'), # search | |
re.compile(r'(\d|\\frac{\d+}{\d+})'), # match | |
) | |
def latex_escape(s: str) -> str: | |
""" | |
Escape a string such that latex interprets it as plaintext. | |
We cannot use verbatim easily with mathjax, so escaping is easier. | |
Rules from https://tex.stackexchange.com/a/34586/41112. | |
""" | |
s = s.replace('\\', r'\textbackslash') | |
for c in '&%$#_{}': | |
s = s.replace(c, '\\' + c) | |
s = s.replace('~', r'\textasciitilde') | |
s = s.replace('^', r'\textasciicircum') | |
return s | |
class LatexPrinter(Printer): | |
printmethod = "_latex" | |
_default_settings: dict[str, Any] = { | |
"full_prec": False, | |
"fold_frac_powers": False, | |
"fold_func_brackets": False, | |
"fold_short_frac": None, | |
"inv_trig_style": "abbreviated", | |
"itex": False, | |
"ln_notation": False, | |
"long_frac_ratio": None, | |
"mat_delim": "[", | |
"mat_str": None, | |
"mode": "plain", | |
"mul_symbol": None, | |
"order": None, | |
"symbol_names": {}, | |
"root_notation": True, | |
"mat_symbol_style": "plain", | |
"imaginary_unit": "i", | |
"gothic_re_im": False, | |
"decimal_separator": "period", | |
"perm_cyclic": True, | |
"parenthesize_super": True, | |
"min": None, | |
"max": None, | |
"diff_operator": "d", | |
"adjoint_style": "dagger", | |
} | |
def __init__(self, settings=None): | |
Printer.__init__(self, settings) | |
if 'mode' in self._settings: | |
valid_modes = ['inline', 'plain', 'equation', | |
'equation*'] | |
if self._settings['mode'] not in valid_modes: | |
raise ValueError("'mode' must be one of 'inline', 'plain', " | |
"'equation' or 'equation*'") | |
if self._settings['fold_short_frac'] is None and \ | |
self._settings['mode'] == 'inline': | |
self._settings['fold_short_frac'] = True | |
mul_symbol_table = { | |
None: r" ", | |
"ldot": r" \,.\, ", | |
"dot": r" \cdot ", | |
"times": r" \times " | |
} | |
try: | |
self._settings['mul_symbol_latex'] = \ | |
mul_symbol_table[self._settings['mul_symbol']] | |
except KeyError: | |
self._settings['mul_symbol_latex'] = \ | |
self._settings['mul_symbol'] | |
try: | |
self._settings['mul_symbol_latex_numbers'] = \ | |
mul_symbol_table[self._settings['mul_symbol'] or 'dot'] | |
except KeyError: | |
if (self._settings['mul_symbol'].strip() in | |
['', ' ', '\\', '\\,', '\\:', '\\;', '\\quad']): | |
self._settings['mul_symbol_latex_numbers'] = \ | |
mul_symbol_table['dot'] | |
else: | |
self._settings['mul_symbol_latex_numbers'] = \ | |
self._settings['mul_symbol'] | |
self._delim_dict = {'(': ')', '[': ']'} | |
imaginary_unit_table = { | |
None: r"i", | |
"i": r"i", | |
"ri": r"\mathrm{i}", | |
"ti": r"\text{i}", | |
"j": r"j", | |
"rj": r"\mathrm{j}", | |
"tj": r"\text{j}", | |
} | |
imag_unit = self._settings['imaginary_unit'] | |
self._settings['imaginary_unit_latex'] = imaginary_unit_table.get(imag_unit, imag_unit) | |
diff_operator_table = { | |
None: r"d", | |
"d": r"d", | |
"rd": r"\mathrm{d}", | |
"td": r"\text{d}", | |
} | |
diff_operator = self._settings['diff_operator'] | |
self._settings["diff_operator_latex"] = diff_operator_table.get(diff_operator, diff_operator) | |
def _add_parens(self, s) -> str: | |
return r"\left({}\right)".format(s) | |
# TODO: merge this with the above, which requires a lot of test changes | |
def _add_parens_lspace(self, s) -> str: | |
return r"\left( {}\right)".format(s) | |
def parenthesize(self, item, level, is_neg=False, strict=False) -> str: | |
prec_val = precedence_traditional(item) | |
if is_neg and strict: | |
return self._add_parens(self._print(item)) | |
if (prec_val < level) or ((not strict) and prec_val <= level): | |
return self._add_parens(self._print(item)) | |
else: | |
return self._print(item) | |
def parenthesize_super(self, s): | |
""" | |
Protect superscripts in s | |
If the parenthesize_super option is set, protect with parentheses, else | |
wrap in braces. | |
""" | |
if "^" in s: | |
if self._settings['parenthesize_super']: | |
return self._add_parens(s) | |
else: | |
return "{{{}}}".format(s) | |
return s | |
def doprint(self, expr) -> str: | |
tex = Printer.doprint(self, expr) | |
if self._settings['mode'] == 'plain': | |
return tex | |
elif self._settings['mode'] == 'inline': | |
return r"$%s$" % tex | |
elif self._settings['itex']: | |
return r"$$%s$$" % tex | |
else: | |
env_str = self._settings['mode'] | |
return r"\begin{%s}%s\end{%s}" % (env_str, tex, env_str) | |
def _needs_brackets(self, expr) -> bool: | |
""" | |
Returns True if the expression needs to be wrapped in brackets when | |
printed, False otherwise. For example: a + b => True; a => False; | |
10 => False; -10 => True. | |
""" | |
return not ((expr.is_Integer and expr.is_nonnegative) | |
or (expr.is_Atom and (expr is not S.NegativeOne | |
and expr.is_Rational is False))) | |
def _needs_function_brackets(self, expr) -> bool: | |
""" | |
Returns True if the expression needs to be wrapped in brackets when | |
passed as an argument to a function, False otherwise. This is a more | |
liberal version of _needs_brackets, in that many expressions which need | |
to be wrapped in brackets when added/subtracted/raised to a power do | |
not need them when passed to a function. Such an example is a*b. | |
""" | |
if not self._needs_brackets(expr): | |
return False | |
else: | |
# Muls of the form a*b*c... can be folded | |
if expr.is_Mul and not self._mul_is_clean(expr): | |
return True | |
# Pows which don't need brackets can be folded | |
elif expr.is_Pow and not self._pow_is_clean(expr): | |
return True | |
# Add and Function always need brackets | |
elif expr.is_Add or expr.is_Function: | |
return True | |
else: | |
return False | |
def _needs_mul_brackets(self, expr, first=False, last=False) -> bool: | |
""" | |
Returns True if the expression needs to be wrapped in brackets when | |
printed as part of a Mul, False otherwise. This is True for Add, | |
but also for some container objects that would not need brackets | |
when appearing last in a Mul, e.g. an Integral. ``last=True`` | |
specifies that this expr is the last to appear in a Mul. | |
``first=True`` specifies that this expr is the first to appear in | |
a Mul. | |
""" | |
from sympy.concrete.products import Product | |
from sympy.concrete.summations import Sum | |
from sympy.integrals.integrals import Integral | |
if expr.is_Mul: | |
if not first and expr.could_extract_minus_sign(): | |
return True | |
elif precedence_traditional(expr) < PRECEDENCE["Mul"]: | |
return True | |
elif expr.is_Relational: | |
return True | |
if expr.is_Piecewise: | |
return True | |
if any(expr.has(x) for x in (Mod,)): | |
return True | |
if (not last and | |
any(expr.has(x) for x in (Integral, Product, Sum))): | |
return True | |
return False | |
def _needs_add_brackets(self, expr) -> bool: | |
""" | |
Returns True if the expression needs to be wrapped in brackets when | |
printed as part of an Add, False otherwise. This is False for most | |
things. | |
""" | |
if expr.is_Relational: | |
return True | |
if any(expr.has(x) for x in (Mod,)): | |
return True | |
if expr.is_Add: | |
return True | |
return False | |
def _mul_is_clean(self, expr) -> bool: | |
for arg in expr.args: | |
if arg.is_Function: | |
return False | |
return True | |
def _pow_is_clean(self, expr) -> bool: | |
return not self._needs_brackets(expr.base) | |
def _do_exponent(self, expr: str, exp): | |
if exp is not None: | |
return r"\left(%s\right)^{%s}" % (expr, exp) | |
else: | |
return expr | |
def _print_Basic(self, expr): | |
name = self._deal_with_super_sub(expr.__class__.__name__) | |
if expr.args: | |
ls = [self._print(o) for o in expr.args] | |
s = r"\operatorname{{{}}}\left({}\right)" | |
return s.format(name, ", ".join(ls)) | |
else: | |
return r"\text{{{}}}".format(name) | |
def _print_bool(self, e: bool | BooleanTrue | BooleanFalse): | |
return r"\text{%s}" % e | |
_print_BooleanTrue = _print_bool | |
_print_BooleanFalse = _print_bool | |
def _print_NoneType(self, e): | |
return r"\text{%s}" % e | |
def _print_Add(self, expr, order=None): | |
terms = self._as_ordered_terms(expr, order=order) | |
tex = "" | |
for i, term in enumerate(terms): | |
if i == 0: | |
pass | |
elif term.could_extract_minus_sign(): | |
tex += " - " | |
term = -term | |
else: | |
tex += " + " | |
term_tex = self._print(term) | |
if self._needs_add_brackets(term): | |
term_tex = r"\left(%s\right)" % term_tex | |
tex += term_tex | |
return tex | |
def _print_Cycle(self, expr): | |
from sympy.combinatorics.permutations import Permutation | |
if expr.size == 0: | |
return r"\left( \right)" | |
expr = Permutation(expr) | |
expr_perm = expr.cyclic_form | |
siz = expr.size | |
if expr.array_form[-1] == siz - 1: | |
expr_perm = expr_perm + [[siz - 1]] | |
term_tex = '' | |
for i in expr_perm: | |
term_tex += str(i).replace(',', r"\;") | |
term_tex = term_tex.replace('[', r"\left( ") | |
term_tex = term_tex.replace(']', r"\right)") | |
return term_tex | |
def _print_Permutation(self, expr): | |
from sympy.combinatorics.permutations import Permutation | |
from sympy.utilities.exceptions import sympy_deprecation_warning | |
perm_cyclic = Permutation.print_cyclic | |
if perm_cyclic is not None: | |
sympy_deprecation_warning( | |
f""" | |
Setting Permutation.print_cyclic is deprecated. Instead use | |
init_printing(perm_cyclic={perm_cyclic}). | |
""", | |
deprecated_since_version="1.6", | |
active_deprecations_target="deprecated-permutation-print_cyclic", | |
stacklevel=8, | |
) | |
else: | |
perm_cyclic = self._settings.get("perm_cyclic", True) | |
if perm_cyclic: | |
return self._print_Cycle(expr) | |
if expr.size == 0: | |
return r"\left( \right)" | |
lower = [self._print(arg) for arg in expr.array_form] | |
upper = [self._print(arg) for arg in range(len(lower))] | |
row1 = " & ".join(upper) | |
row2 = " & ".join(lower) | |
mat = r" \\ ".join((row1, row2)) | |
return r"\begin{pmatrix} %s \end{pmatrix}" % mat | |
def _print_AppliedPermutation(self, expr): | |
perm, var = expr.args | |
return r"\sigma_{%s}(%s)" % (self._print(perm), self._print(var)) | |
def _print_Float(self, expr): | |
# Based off of that in StrPrinter | |
dps = prec_to_dps(expr._prec) | |
strip = False if self._settings['full_prec'] else True | |
low = self._settings["min"] if "min" in self._settings else None | |
high = self._settings["max"] if "max" in self._settings else None | |
str_real = mlib_to_str(expr._mpf_, dps, strip_zeros=strip, min_fixed=low, max_fixed=high) | |
# Must always have a mul symbol (as 2.5 10^{20} just looks odd) | |
# thus we use the number separator | |
separator = self._settings['mul_symbol_latex_numbers'] | |
if 'e' in str_real: | |
(mant, exp) = str_real.split('e') | |
if exp[0] == '+': | |
exp = exp[1:] | |
if self._settings['decimal_separator'] == 'comma': | |
mant = mant.replace('.','{,}') | |
return r"%s%s10^{%s}" % (mant, separator, exp) | |
elif str_real == "+inf": | |
return r"\infty" | |
elif str_real == "-inf": | |
return r"- \infty" | |
else: | |
if self._settings['decimal_separator'] == 'comma': | |
str_real = str_real.replace('.','{,}') | |
return str_real | |
def _print_Cross(self, expr): | |
vec1 = expr._expr1 | |
vec2 = expr._expr2 | |
return r"%s \times %s" % (self.parenthesize(vec1, PRECEDENCE['Mul']), | |
self.parenthesize(vec2, PRECEDENCE['Mul'])) | |
def _print_Curl(self, expr): | |
vec = expr._expr | |
return r"\nabla\times %s" % self.parenthesize(vec, PRECEDENCE['Mul']) | |
def _print_Divergence(self, expr): | |
vec = expr._expr | |
return r"\nabla\cdot %s" % self.parenthesize(vec, PRECEDENCE['Mul']) | |
def _print_Dot(self, expr): | |
vec1 = expr._expr1 | |
vec2 = expr._expr2 | |
return r"%s \cdot %s" % (self.parenthesize(vec1, PRECEDENCE['Mul']), | |
self.parenthesize(vec2, PRECEDENCE['Mul'])) | |
def _print_Gradient(self, expr): | |
func = expr._expr | |
return r"\nabla %s" % self.parenthesize(func, PRECEDENCE['Mul']) | |
def _print_Laplacian(self, expr): | |
func = expr._expr | |
return r"\Delta %s" % self.parenthesize(func, PRECEDENCE['Mul']) | |
def _print_Mul(self, expr: Expr): | |
from sympy.simplify import fraction | |
separator: str = self._settings['mul_symbol_latex'] | |
numbersep: str = self._settings['mul_symbol_latex_numbers'] | |
def convert(expr) -> str: | |
if not expr.is_Mul: | |
return str(self._print(expr)) | |
else: | |
if self.order not in ('old', 'none'): | |
args = expr.as_ordered_factors() | |
else: | |
args = list(expr.args) | |
# If there are quantities or prefixes, append them at the back. | |
units, nonunits = sift(args, lambda x: (hasattr(x, "_scale_factor") or hasattr(x, "is_physical_constant")) or | |
(isinstance(x, Pow) and | |
hasattr(x.base, "is_physical_constant")), binary=True) | |
prefixes, units = sift(units, lambda x: hasattr(x, "_scale_factor"), binary=True) | |
return convert_args(nonunits + prefixes + units) | |
def convert_args(args) -> str: | |
_tex = last_term_tex = "" | |
for i, term in enumerate(args): | |
term_tex = self._print(term) | |
if not (hasattr(term, "_scale_factor") or hasattr(term, "is_physical_constant")): | |
if self._needs_mul_brackets(term, first=(i == 0), | |
last=(i == len(args) - 1)): | |
term_tex = r"\left(%s\right)" % term_tex | |
if _between_two_numbers_p[0].search(last_term_tex) and \ | |
_between_two_numbers_p[1].match(term_tex): | |
# between two numbers | |
_tex += numbersep | |
elif _tex: | |
_tex += separator | |
elif _tex: | |
_tex += separator | |
_tex += term_tex | |
last_term_tex = term_tex | |
return _tex | |
# Check for unevaluated Mul. In this case we need to make sure the | |
# identities are visible, multiple Rational factors are not combined | |
# etc so we display in a straight-forward form that fully preserves all | |
# args and their order. | |
# XXX: _print_Pow calls this routine with instances of Pow... | |
if isinstance(expr, Mul): | |
args = expr.args | |
if args[0] is S.One or any(isinstance(arg, Number) for arg in args[1:]): | |
return convert_args(args) | |
include_parens = False | |
if expr.could_extract_minus_sign(): | |
expr = -expr | |
tex = "- " | |
if expr.is_Add: | |
tex += "(" | |
include_parens = True | |
else: | |
tex = "" | |
numer, denom = fraction(expr, exact=True) | |
if denom is S.One and Pow(1, -1, evaluate=False) not in expr.args: | |
# use the original expression here, since fraction() may have | |
# altered it when producing numer and denom | |
tex += convert(expr) | |
else: | |
snumer = convert(numer) | |
sdenom = convert(denom) | |
ldenom = len(sdenom.split()) | |
ratio = self._settings['long_frac_ratio'] | |
if self._settings['fold_short_frac'] and ldenom <= 2 and \ | |
"^" not in sdenom: | |
# handle short fractions | |
if self._needs_mul_brackets(numer, last=False): | |
tex += r"\left(%s\right) / %s" % (snumer, sdenom) | |
else: | |
tex += r"%s / %s" % (snumer, sdenom) | |
elif ratio is not None and \ | |
len(snumer.split()) > ratio*ldenom: | |
# handle long fractions | |
if self._needs_mul_brackets(numer, last=True): | |
tex += r"\frac{1}{%s}%s\left(%s\right)" \ | |
% (sdenom, separator, snumer) | |
elif numer.is_Mul: | |
# split a long numerator | |
a = S.One | |
b = S.One | |
for x in numer.args: | |
if self._needs_mul_brackets(x, last=False) or \ | |
len(convert(a*x).split()) > ratio*ldenom or \ | |
(b.is_commutative is x.is_commutative is False): | |
b *= x | |
else: | |
a *= x | |
if self._needs_mul_brackets(b, last=True): | |
tex += r"\frac{%s}{%s}%s\left(%s\right)" \ | |
% (convert(a), sdenom, separator, convert(b)) | |
else: | |
tex += r"\frac{%s}{%s}%s%s" \ | |
% (convert(a), sdenom, separator, convert(b)) | |
else: | |
tex += r"\frac{1}{%s}%s%s" % (sdenom, separator, snumer) | |
else: | |
tex += r"\frac{%s}{%s}" % (snumer, sdenom) | |
if include_parens: | |
tex += ")" | |
return tex | |
def _print_AlgebraicNumber(self, expr): | |
if expr.is_aliased: | |
return self._print(expr.as_poly().as_expr()) | |
else: | |
return self._print(expr.as_expr()) | |
def _print_PrimeIdeal(self, expr): | |
p = self._print(expr.p) | |
if expr.is_inert: | |
return rf'\left({p}\right)' | |
alpha = self._print(expr.alpha.as_expr()) | |
return rf'\left({p}, {alpha}\right)' | |
def _print_Pow(self, expr: Pow): | |
# Treat x**Rational(1,n) as special case | |
if expr.exp.is_Rational: | |
p: int = expr.exp.p # type: ignore | |
q: int = expr.exp.q # type: ignore | |
if abs(p) == 1 and q != 1 and self._settings['root_notation']: | |
base = self._print(expr.base) | |
if q == 2: | |
tex = r"\sqrt{%s}" % base | |
elif self._settings['itex']: | |
tex = r"\root{%d}{%s}" % (q, base) | |
else: | |
tex = r"\sqrt[%d]{%s}" % (q, base) | |
if expr.exp.is_negative: | |
return r"\frac{1}{%s}" % tex | |
else: | |
return tex | |
elif self._settings['fold_frac_powers'] and q != 1: | |
base = self.parenthesize(expr.base, PRECEDENCE['Pow']) | |
# issue #12886: add parentheses for superscripts raised to powers | |
if expr.base.is_Symbol: | |
base = self.parenthesize_super(base) | |
if expr.base.is_Function: | |
return self._print(expr.base, exp="%s/%s" % (p, q)) | |
return r"%s^{%s/%s}" % (base, p, q) | |
elif expr.exp.is_negative and expr.base.is_commutative: | |
# special case for 1^(-x), issue 9216 | |
if expr.base == 1: | |
return r"%s^{%s}" % (expr.base, expr.exp) | |
# special case for (1/x)^(-y) and (-1/-x)^(-y), issue 20252 | |
if expr.base.is_Rational: | |
base_p: int = expr.base.p # type: ignore | |
base_q: int = expr.base.q # type: ignore | |
if base_p * base_q == abs(base_q): | |
if expr.exp == -1: | |
return r"\frac{1}{\frac{%s}{%s}}" % (base_p, base_q) | |
else: | |
return r"\frac{1}{(\frac{%s}{%s})^{%s}}" % (base_p, base_q, abs(expr.exp)) | |
# things like 1/x | |
return self._print_Mul(expr) | |
if expr.base.is_Function: | |
return self._print(expr.base, exp=self._print(expr.exp)) | |
tex = r"%s^{%s}" | |
return self._helper_print_standard_power(expr, tex) | |
def _helper_print_standard_power(self, expr, template: str) -> str: | |
exp = self._print(expr.exp) | |
# issue #12886: add parentheses around superscripts raised | |
# to powers | |
base = self.parenthesize(expr.base, PRECEDENCE['Pow']) | |
if expr.base.is_Symbol: | |
base = self.parenthesize_super(base) | |
elif expr.base.is_Float: | |
base = r"{%s}" % base | |
elif (isinstance(expr.base, Derivative) | |
and base.startswith(r'\left(') | |
and re.match(r'\\left\(\\d?d?dot', base) | |
and base.endswith(r'\right)')): | |
# don't use parentheses around dotted derivative | |
base = base[6: -7] # remove outermost added parens | |
return template % (base, exp) | |
def _print_UnevaluatedExpr(self, expr): | |
return self._print(expr.args[0]) | |
def _print_Sum(self, expr): | |
if len(expr.limits) == 1: | |
tex = r"\sum_{%s=%s}^{%s} " % \ | |
tuple([self._print(i) for i in expr.limits[0]]) | |
else: | |
def _format_ineq(l): | |
return r"%s \leq %s \leq %s" % \ | |
tuple([self._print(s) for s in (l[1], l[0], l[2])]) | |
tex = r"\sum_{\substack{%s}} " % \ | |
str.join('\\\\', [_format_ineq(l) for l in expr.limits]) | |
if isinstance(expr.function, Add): | |
tex += r"\left(%s\right)" % self._print(expr.function) | |
else: | |
tex += self._print(expr.function) | |
return tex | |
def _print_Product(self, expr): | |
if len(expr.limits) == 1: | |
tex = r"\prod_{%s=%s}^{%s} " % \ | |
tuple([self._print(i) for i in expr.limits[0]]) | |
else: | |
def _format_ineq(l): | |
return r"%s \leq %s \leq %s" % \ | |
tuple([self._print(s) for s in (l[1], l[0], l[2])]) | |
tex = r"\prod_{\substack{%s}} " % \ | |
str.join('\\\\', [_format_ineq(l) for l in expr.limits]) | |
if isinstance(expr.function, Add): | |
tex += r"\left(%s\right)" % self._print(expr.function) | |
else: | |
tex += self._print(expr.function) | |
return tex | |
def _print_BasisDependent(self, expr: 'BasisDependent'): | |
from sympy.vector import Vector | |
o1: list[str] = [] | |
if expr == expr.zero: | |
return expr.zero._latex_form | |
if isinstance(expr, Vector): | |
items = expr.separate().items() | |
else: | |
items = [(0, expr)] | |
for system, vect in items: | |
inneritems = list(vect.components.items()) | |
inneritems.sort(key=lambda x: x[0].__str__()) | |
for k, v in inneritems: | |
if v == 1: | |
o1.append(' + ' + k._latex_form) | |
elif v == -1: | |
o1.append(' - ' + k._latex_form) | |
else: | |
arg_str = r'\left(' + self._print(v) + r'\right)' | |
o1.append(' + ' + arg_str + k._latex_form) | |
outstr = (''.join(o1)) | |
if outstr[1] != '-': | |
outstr = outstr[3:] | |
else: | |
outstr = outstr[1:] | |
return outstr | |
def _print_Indexed(self, expr): | |
tex_base = self._print(expr.base) | |
tex = '{'+tex_base+'}'+'_{%s}' % ','.join( | |
map(self._print, expr.indices)) | |
return tex | |
def _print_IndexedBase(self, expr): | |
return self._print(expr.label) | |
def _print_Idx(self, expr): | |
label = self._print(expr.label) | |
if expr.upper is not None: | |
upper = self._print(expr.upper) | |
if expr.lower is not None: | |
lower = self._print(expr.lower) | |
else: | |
lower = self._print(S.Zero) | |
interval = '{lower}\\mathrel{{..}}\\nobreak {upper}'.format( | |
lower = lower, upper = upper) | |
return '{{{label}}}_{{{interval}}}'.format( | |
label = label, interval = interval) | |
#if no bounds are defined this just prints the label | |
return label | |
def _print_Derivative(self, expr): | |
if requires_partial(expr.expr): | |
diff_symbol = r'\partial' | |
else: | |
diff_symbol = self._settings["diff_operator_latex"] | |
tex = "" | |
dim = 0 | |
for x, num in reversed(expr.variable_count): | |
dim += num | |
if num == 1: | |
tex += r"%s %s" % (diff_symbol, self._print(x)) | |
else: | |
tex += r"%s %s^{%s}" % (diff_symbol, | |
self.parenthesize_super(self._print(x)), | |
self._print(num)) | |
if dim == 1: | |
tex = r"\frac{%s}{%s}" % (diff_symbol, tex) | |
else: | |
tex = r"\frac{%s^{%s}}{%s}" % (diff_symbol, self._print(dim), tex) | |
if any(i.could_extract_minus_sign() for i in expr.args): | |
return r"%s %s" % (tex, self.parenthesize(expr.expr, | |
PRECEDENCE["Mul"], | |
is_neg=True, | |
strict=True)) | |
return r"%s %s" % (tex, self.parenthesize(expr.expr, | |
PRECEDENCE["Mul"], | |
is_neg=False, | |
strict=True)) | |
def _print_Subs(self, subs): | |
expr, old, new = subs.args | |
latex_expr = self._print(expr) | |
latex_old = (self._print(e) for e in old) | |
latex_new = (self._print(e) for e in new) | |
latex_subs = r'\\ '.join( | |
e[0] + '=' + e[1] for e in zip(latex_old, latex_new)) | |
return r'\left. %s \right|_{\substack{ %s }}' % (latex_expr, | |
latex_subs) | |
def _print_Integral(self, expr): | |
tex, symbols = "", [] | |
diff_symbol = self._settings["diff_operator_latex"] | |
# Only up to \iiiint exists | |
if len(expr.limits) <= 4 and all(len(lim) == 1 for lim in expr.limits): | |
# Use len(expr.limits)-1 so that syntax highlighters don't think | |
# \" is an escaped quote | |
tex = r"\i" + "i"*(len(expr.limits) - 1) + "nt" | |
symbols = [r"\, %s%s" % (diff_symbol, self._print(symbol[0])) | |
for symbol in expr.limits] | |
else: | |
for lim in reversed(expr.limits): | |
symbol = lim[0] | |
tex += r"\int" | |
if len(lim) > 1: | |
if self._settings['mode'] != 'inline' \ | |
and not self._settings['itex']: | |
tex += r"\limits" | |
if len(lim) == 3: | |
tex += "_{%s}^{%s}" % (self._print(lim[1]), | |
self._print(lim[2])) | |
if len(lim) == 2: | |
tex += "^{%s}" % (self._print(lim[1])) | |
symbols.insert(0, r"\, %s%s" % (diff_symbol, self._print(symbol))) | |
return r"%s %s%s" % (tex, self.parenthesize(expr.function, | |
PRECEDENCE["Mul"], | |
is_neg=any(i.could_extract_minus_sign() for i in expr.args), | |
strict=True), | |
"".join(symbols)) | |
def _print_Limit(self, expr): | |
e, z, z0, dir = expr.args | |
tex = r"\lim_{%s \to " % self._print(z) | |
if str(dir) == '+-' or z0 in (S.Infinity, S.NegativeInfinity): | |
tex += r"%s}" % self._print(z0) | |
else: | |
tex += r"%s^%s}" % (self._print(z0), self._print(dir)) | |
if isinstance(e, AssocOp): | |
return r"%s\left(%s\right)" % (tex, self._print(e)) | |
else: | |
return r"%s %s" % (tex, self._print(e)) | |
def _hprint_Function(self, func: str) -> str: | |
r''' | |
Logic to decide how to render a function to latex | |
- if it is a recognized latex name, use the appropriate latex command | |
- if it is a single letter, excluding sub- and superscripts, just use that letter | |
- if it is a longer name, then put \operatorname{} around it and be | |
mindful of undercores in the name | |
''' | |
func = self._deal_with_super_sub(func) | |
superscriptidx = func.find("^") | |
subscriptidx = func.find("_") | |
if func in accepted_latex_functions: | |
name = r"\%s" % func | |
elif len(func) == 1 or func.startswith('\\') or subscriptidx == 1 or superscriptidx == 1: | |
name = func | |
else: | |
if superscriptidx > 0 and subscriptidx > 0: | |
name = r"\operatorname{%s}%s" %( | |
func[:min(subscriptidx,superscriptidx)], | |
func[min(subscriptidx,superscriptidx):]) | |
elif superscriptidx > 0: | |
name = r"\operatorname{%s}%s" %( | |
func[:superscriptidx], | |
func[superscriptidx:]) | |
elif subscriptidx > 0: | |
name = r"\operatorname{%s}%s" %( | |
func[:subscriptidx], | |
func[subscriptidx:]) | |
else: | |
name = r"\operatorname{%s}" % func | |
return name | |
def _print_Function(self, expr: Function, exp=None) -> str: | |
r''' | |
Render functions to LaTeX, handling functions that LaTeX knows about | |
e.g., sin, cos, ... by using the proper LaTeX command (\sin, \cos, ...). | |
For single-letter function names, render them as regular LaTeX math | |
symbols. For multi-letter function names that LaTeX does not know | |
about, (e.g., Li, sech) use \operatorname{} so that the function name | |
is rendered in Roman font and LaTeX handles spacing properly. | |
expr is the expression involving the function | |
exp is an exponent | |
''' | |
func = expr.func.__name__ | |
if hasattr(self, '_print_' + func) and \ | |
not isinstance(expr, AppliedUndef): | |
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", | |
"acsc", "asec", "acot", | |
"asinh", "acosh", "atanh", | |
"acsch", "asech", "acoth", | |
] | |
# If the function is an inverse trig function, handle the style | |
if func in inv_trig_table: | |
if inv_trig_style == "abbreviated": | |
pass | |
elif inv_trig_style == "full": | |
func = ("ar" if func[-1] == "h" else "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: | |
func_tex = self._hprint_Function(func) | |
func_tex = self.parenthesize_super(func_tex) | |
name = r'%s^{%s}' % (func_tex, exp) | |
else: | |
name = self._hprint_Function(func) | |
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: | |
name += r"%s" | |
else: | |
name += r"{\left(%s \right)}" | |
if inv_trig_power_case and exp is not None: | |
name += r"^{%s}" % exp | |
return name % ",".join(args) | |
def _print_UndefinedFunction(self, expr): | |
return self._hprint_Function(str(expr)) | |
def _print_ElementwiseApplyFunction(self, expr): | |
return r"{%s}_{\circ}\left({%s}\right)" % ( | |
self._print(expr.function), | |
self._print(expr.expr), | |
) | |
def _special_function_classes(self): | |
from sympy.functions.special.tensor_functions import KroneckerDelta | |
from sympy.functions.special.gamma_functions import gamma, lowergamma | |
from sympy.functions.special.beta_functions import beta | |
from sympy.functions.special.delta_functions import DiracDelta | |
from sympy.functions.special.error_functions import Chi | |
return {KroneckerDelta: r'\delta', | |
gamma: r'\Gamma', | |
lowergamma: r'\gamma', | |
beta: r'\operatorname{B}', | |
DiracDelta: r'\delta', | |
Chi: r'\operatorname{Chi}'} | |
def _print_FunctionClass(self, expr): | |
for cls in self._special_function_classes: | |
if issubclass(expr, cls) and expr.__name__ == cls.__name__: | |
return self._special_function_classes[cls] | |
return self._hprint_Function(str(expr)) | |
def _print_Lambda(self, expr): | |
symbols, expr = expr.args | |
if len(symbols) == 1: | |
symbols = self._print(symbols[0]) | |
else: | |
symbols = self._print(tuple(symbols)) | |
tex = r"\left( %s \mapsto %s \right)" % (symbols, self._print(expr)) | |
return tex | |
def _print_IdentityFunction(self, expr): | |
return r"\left( x \mapsto x \right)" | |
def _hprint_variadic_function(self, expr, exp=None) -> str: | |
args = sorted(expr.args, key=default_sort_key) | |
texargs = [r"%s" % self._print(symbol) for symbol in args] | |
tex = r"\%s\left(%s\right)" % (str(expr.func).lower(), | |
", ".join(texargs)) | |
if exp is not None: | |
return r"%s^{%s}" % (tex, exp) | |
else: | |
return tex | |
_print_Min = _print_Max = _hprint_variadic_function | |
def _print_floor(self, expr, exp=None): | |
tex = r"\left\lfloor{%s}\right\rfloor" % self._print(expr.args[0]) | |
if exp is not None: | |
return r"%s^{%s}" % (tex, exp) | |
else: | |
return tex | |
def _print_ceiling(self, expr, exp=None): | |
tex = r"\left\lceil{%s}\right\rceil" % self._print(expr.args[0]) | |
if exp is not None: | |
return r"%s^{%s}" % (tex, exp) | |
else: | |
return tex | |
def _print_log(self, expr, exp=None): | |
if not self._settings["ln_notation"]: | |
tex = r"\log{\left(%s \right)}" % self._print(expr.args[0]) | |
else: | |
tex = r"\ln{\left(%s \right)}" % self._print(expr.args[0]) | |
if exp is not None: | |
return r"%s^{%s}" % (tex, exp) | |
else: | |
return tex | |
def _print_Abs(self, expr, exp=None): | |
tex = r"\left|{%s}\right|" % self._print(expr.args[0]) | |
if exp is not None: | |
return r"%s^{%s}" % (tex, exp) | |
else: | |
return tex | |
def _print_re(self, expr, exp=None): | |
if self._settings['gothic_re_im']: | |
tex = r"\Re{%s}" % self.parenthesize(expr.args[0], PRECEDENCE['Atom']) | |
else: | |
tex = r"\operatorname{{re}}{{{}}}".format(self.parenthesize(expr.args[0], PRECEDENCE['Atom'])) | |
return self._do_exponent(tex, exp) | |
def _print_im(self, expr, exp=None): | |
if self._settings['gothic_re_im']: | |
tex = r"\Im{%s}" % self.parenthesize(expr.args[0], PRECEDENCE['Atom']) | |
else: | |
tex = r"\operatorname{{im}}{{{}}}".format(self.parenthesize(expr.args[0], PRECEDENCE['Atom'])) | |
return self._do_exponent(tex, exp) | |
def _print_Not(self, e): | |
from sympy.logic.boolalg import (Equivalent, Implies) | |
if isinstance(e.args[0], Equivalent): | |
return self._print_Equivalent(e.args[0], r"\not\Leftrightarrow") | |
if isinstance(e.args[0], Implies): | |
return self._print_Implies(e.args[0], r"\not\Rightarrow") | |
if (e.args[0].is_Boolean): | |
return r"\neg \left(%s\right)" % self._print(e.args[0]) | |
else: | |
return r"\neg %s" % self._print(e.args[0]) | |
def _print_LogOp(self, args, char): | |
arg = args[0] | |
if arg.is_Boolean and not arg.is_Not: | |
tex = r"\left(%s\right)" % self._print(arg) | |
else: | |
tex = r"%s" % self._print(arg) | |
for arg in args[1:]: | |
if arg.is_Boolean and not arg.is_Not: | |
tex += r" %s \left(%s\right)" % (char, self._print(arg)) | |
else: | |
tex += r" %s %s" % (char, self._print(arg)) | |
return tex | |
def _print_And(self, e): | |
args = sorted(e.args, key=default_sort_key) | |
return self._print_LogOp(args, r"\wedge") | |
def _print_Or(self, e): | |
args = sorted(e.args, key=default_sort_key) | |
return self._print_LogOp(args, r"\vee") | |
def _print_Xor(self, e): | |
args = sorted(e.args, key=default_sort_key) | |
return self._print_LogOp(args, r"\veebar") | |
def _print_Implies(self, e, altchar=None): | |
return self._print_LogOp(e.args, altchar or r"\Rightarrow") | |
def _print_Equivalent(self, e, altchar=None): | |
args = sorted(e.args, key=default_sort_key) | |
return self._print_LogOp(args, altchar or r"\Leftrightarrow") | |
def _print_conjugate(self, expr, exp=None): | |
tex = r"\overline{%s}" % self._print(expr.args[0]) | |
if exp is not None: | |
return r"%s^{%s}" % (tex, exp) | |
else: | |
return tex | |
def _print_polar_lift(self, expr, exp=None): | |
func = r"\operatorname{polar\_lift}" | |
arg = r"{\left(%s \right)}" % self._print(expr.args[0]) | |
if exp is not None: | |
return r"%s^{%s}%s" % (func, exp, arg) | |
else: | |
return r"%s%s" % (func, arg) | |
def _print_ExpBase(self, expr, exp=None): | |
# TODO should exp_polar be printed differently? | |
# what about exp_polar(0), exp_polar(1)? | |
tex = r"e^{%s}" % self._print(expr.args[0]) | |
return self._do_exponent(tex, exp) | |
def _print_Exp1(self, expr, exp=None): | |
return "e" | |
def _print_elliptic_k(self, expr, exp=None): | |
tex = r"\left(%s\right)" % self._print(expr.args[0]) | |
if exp is not None: | |
return r"K^{%s}%s" % (exp, tex) | |
else: | |
return r"K%s" % tex | |
def _print_elliptic_f(self, expr, exp=None): | |
tex = r"\left(%s\middle| %s\right)" % \ | |
(self._print(expr.args[0]), self._print(expr.args[1])) | |
if exp is not None: | |
return r"F^{%s}%s" % (exp, tex) | |
else: | |
return r"F%s" % tex | |
def _print_elliptic_e(self, expr, exp=None): | |
if len(expr.args) == 2: | |
tex = r"\left(%s\middle| %s\right)" % \ | |
(self._print(expr.args[0]), self._print(expr.args[1])) | |
else: | |
tex = r"\left(%s\right)" % self._print(expr.args[0]) | |
if exp is not None: | |
return r"E^{%s}%s" % (exp, tex) | |
else: | |
return r"E%s" % tex | |
def _print_elliptic_pi(self, expr, exp=None): | |
if len(expr.args) == 3: | |
tex = r"\left(%s; %s\middle| %s\right)" % \ | |
(self._print(expr.args[0]), self._print(expr.args[1]), | |
self._print(expr.args[2])) | |
else: | |
tex = r"\left(%s\middle| %s\right)" % \ | |
(self._print(expr.args[0]), self._print(expr.args[1])) | |
if exp is not None: | |
return r"\Pi^{%s}%s" % (exp, tex) | |
else: | |
return r"\Pi%s" % tex | |
def _print_beta(self, expr, exp=None): | |
x = expr.args[0] | |
# Deal with unevaluated single argument beta | |
y = expr.args[0] if len(expr.args) == 1 else expr.args[1] | |
tex = rf"\left({x}, {y}\right)" | |
if exp is not None: | |
return r"\operatorname{B}^{%s}%s" % (exp, tex) | |
else: | |
return r"\operatorname{B}%s" % tex | |
def _print_betainc(self, expr, exp=None, operator='B'): | |
largs = [self._print(arg) for arg in expr.args] | |
tex = r"\left(%s, %s\right)" % (largs[0], largs[1]) | |
if exp is not None: | |
return r"\operatorname{%s}_{(%s, %s)}^{%s}%s" % (operator, largs[2], largs[3], exp, tex) | |
else: | |
return r"\operatorname{%s}_{(%s, %s)}%s" % (operator, largs[2], largs[3], tex) | |
def _print_betainc_regularized(self, expr, exp=None): | |
return self._print_betainc(expr, exp, operator='I') | |
def _print_uppergamma(self, expr, exp=None): | |
tex = r"\left(%s, %s\right)" % (self._print(expr.args[0]), | |
self._print(expr.args[1])) | |
if exp is not None: | |
return r"\Gamma^{%s}%s" % (exp, tex) | |
else: | |
return r"\Gamma%s" % tex | |
def _print_lowergamma(self, expr, exp=None): | |
tex = r"\left(%s, %s\right)" % (self._print(expr.args[0]), | |
self._print(expr.args[1])) | |
if exp is not None: | |
return r"\gamma^{%s}%s" % (exp, tex) | |
else: | |
return r"\gamma%s" % tex | |
def _hprint_one_arg_func(self, expr, exp=None) -> str: | |
tex = r"\left(%s\right)" % self._print(expr.args[0]) | |
if exp is not None: | |
return r"%s^{%s}%s" % (self._print(expr.func), exp, tex) | |
else: | |
return r"%s%s" % (self._print(expr.func), tex) | |
_print_gamma = _hprint_one_arg_func | |
def _print_Chi(self, expr, exp=None): | |
tex = r"\left(%s\right)" % self._print(expr.args[0]) | |
if exp is not None: | |
return r"\operatorname{Chi}^{%s}%s" % (exp, tex) | |
else: | |
return r"\operatorname{Chi}%s" % tex | |
def _print_expint(self, expr, exp=None): | |
tex = r"\left(%s\right)" % self._print(expr.args[1]) | |
nu = self._print(expr.args[0]) | |
if exp is not None: | |
return r"\operatorname{E}_{%s}^{%s}%s" % (nu, exp, tex) | |
else: | |
return r"\operatorname{E}_{%s}%s" % (nu, tex) | |
def _print_fresnels(self, expr, exp=None): | |
tex = r"\left(%s\right)" % self._print(expr.args[0]) | |
if exp is not None: | |
return r"S^{%s}%s" % (exp, tex) | |
else: | |
return r"S%s" % tex | |
def _print_fresnelc(self, expr, exp=None): | |
tex = r"\left(%s\right)" % self._print(expr.args[0]) | |
if exp is not None: | |
return r"C^{%s}%s" % (exp, tex) | |
else: | |
return r"C%s" % tex | |
def _print_subfactorial(self, expr, exp=None): | |
tex = r"!%s" % self.parenthesize(expr.args[0], PRECEDENCE["Func"]) | |
if exp is not None: | |
return r"\left(%s\right)^{%s}" % (tex, exp) | |
else: | |
return tex | |
def _print_factorial(self, expr, exp=None): | |
tex = r"%s!" % self.parenthesize(expr.args[0], PRECEDENCE["Func"]) | |
if exp is not None: | |
return r"%s^{%s}" % (tex, exp) | |
else: | |
return tex | |
def _print_factorial2(self, expr, exp=None): | |
tex = r"%s!!" % self.parenthesize(expr.args[0], PRECEDENCE["Func"]) | |
if exp is not None: | |
return r"%s^{%s}" % (tex, exp) | |
else: | |
return tex | |
def _print_binomial(self, expr, exp=None): | |
tex = r"{\binom{%s}{%s}}" % (self._print(expr.args[0]), | |
self._print(expr.args[1])) | |
if exp is not None: | |
return r"%s^{%s}" % (tex, exp) | |
else: | |
return tex | |
def _print_RisingFactorial(self, expr, exp=None): | |
n, k = expr.args | |
base = r"%s" % self.parenthesize(n, PRECEDENCE['Func']) | |
tex = r"{%s}^{\left(%s\right)}" % (base, self._print(k)) | |
return self._do_exponent(tex, exp) | |
def _print_FallingFactorial(self, expr, exp=None): | |
n, k = expr.args | |
sub = r"%s" % self.parenthesize(k, PRECEDENCE['Func']) | |
tex = r"{\left(%s\right)}_{%s}" % (self._print(n), sub) | |
return self._do_exponent(tex, exp) | |
def _hprint_BesselBase(self, expr, exp, sym: str) -> str: | |
tex = r"%s" % (sym) | |
need_exp = False | |
if exp is not None: | |
if tex.find('^') == -1: | |
tex = r"%s^{%s}" % (tex, exp) | |
else: | |
need_exp = True | |
tex = r"%s_{%s}\left(%s\right)" % (tex, self._print(expr.order), | |
self._print(expr.argument)) | |
if need_exp: | |
tex = self._do_exponent(tex, exp) | |
return tex | |
def _hprint_vec(self, vec) -> str: | |
if not vec: | |
return "" | |
s = "" | |
for i in vec[:-1]: | |
s += "%s, " % self._print(i) | |
s += self._print(vec[-1]) | |
return s | |
def _print_besselj(self, expr, exp=None): | |
return self._hprint_BesselBase(expr, exp, 'J') | |
def _print_besseli(self, expr, exp=None): | |
return self._hprint_BesselBase(expr, exp, 'I') | |
def _print_besselk(self, expr, exp=None): | |
return self._hprint_BesselBase(expr, exp, 'K') | |
def _print_bessely(self, expr, exp=None): | |
return self._hprint_BesselBase(expr, exp, 'Y') | |
def _print_yn(self, expr, exp=None): | |
return self._hprint_BesselBase(expr, exp, 'y') | |
def _print_jn(self, expr, exp=None): | |
return self._hprint_BesselBase(expr, exp, 'j') | |
def _print_hankel1(self, expr, exp=None): | |
return self._hprint_BesselBase(expr, exp, 'H^{(1)}') | |
def _print_hankel2(self, expr, exp=None): | |
return self._hprint_BesselBase(expr, exp, 'H^{(2)}') | |
def _print_hn1(self, expr, exp=None): | |
return self._hprint_BesselBase(expr, exp, 'h^{(1)}') | |
def _print_hn2(self, expr, exp=None): | |
return self._hprint_BesselBase(expr, exp, 'h^{(2)}') | |
def _hprint_airy(self, expr, exp=None, notation="") -> str: | |
tex = r"\left(%s\right)" % self._print(expr.args[0]) | |
if exp is not None: | |
return r"%s^{%s}%s" % (notation, exp, tex) | |
else: | |
return r"%s%s" % (notation, tex) | |
def _hprint_airy_prime(self, expr, exp=None, notation="") -> str: | |
tex = r"\left(%s\right)" % self._print(expr.args[0]) | |
if exp is not None: | |
return r"{%s^\prime}^{%s}%s" % (notation, exp, tex) | |
else: | |
return r"%s^\prime%s" % (notation, tex) | |
def _print_airyai(self, expr, exp=None): | |
return self._hprint_airy(expr, exp, 'Ai') | |
def _print_airybi(self, expr, exp=None): | |
return self._hprint_airy(expr, exp, 'Bi') | |
def _print_airyaiprime(self, expr, exp=None): | |
return self._hprint_airy_prime(expr, exp, 'Ai') | |
def _print_airybiprime(self, expr, exp=None): | |
return self._hprint_airy_prime(expr, exp, 'Bi') | |
def _print_hyper(self, expr, exp=None): | |
tex = r"{{}_{%s}F_{%s}\left(\begin{matrix} %s \\ %s \end{matrix}" \ | |
r"\middle| {%s} \right)}" % \ | |
(self._print(len(expr.ap)), self._print(len(expr.bq)), | |
self._hprint_vec(expr.ap), self._hprint_vec(expr.bq), | |
self._print(expr.argument)) | |
if exp is not None: | |
tex = r"{%s}^{%s}" % (tex, exp) | |
return tex | |
def _print_meijerg(self, expr, exp=None): | |
tex = r"{G_{%s, %s}^{%s, %s}\left(\begin{matrix} %s & %s \\" \ | |
r"%s & %s \end{matrix} \middle| {%s} \right)}" % \ | |
(self._print(len(expr.ap)), self._print(len(expr.bq)), | |
self._print(len(expr.bm)), self._print(len(expr.an)), | |
self._hprint_vec(expr.an), self._hprint_vec(expr.aother), | |
self._hprint_vec(expr.bm), self._hprint_vec(expr.bother), | |
self._print(expr.argument)) | |
if exp is not None: | |
tex = r"{%s}^{%s}" % (tex, exp) | |
return tex | |
def _print_dirichlet_eta(self, expr, exp=None): | |
tex = r"\left(%s\right)" % self._print(expr.args[0]) | |
if exp is not None: | |
return r"\eta^{%s}%s" % (exp, tex) | |
return r"\eta%s" % tex | |
def _print_zeta(self, expr, exp=None): | |
if len(expr.args) == 2: | |
tex = r"\left(%s, %s\right)" % tuple(map(self._print, expr.args)) | |
else: | |
tex = r"\left(%s\right)" % self._print(expr.args[0]) | |
if exp is not None: | |
return r"\zeta^{%s}%s" % (exp, tex) | |
return r"\zeta%s" % tex | |
def _print_stieltjes(self, expr, exp=None): | |
if len(expr.args) == 2: | |
tex = r"_{%s}\left(%s\right)" % tuple(map(self._print, expr.args)) | |
else: | |
tex = r"_{%s}" % self._print(expr.args[0]) | |
if exp is not None: | |
return r"\gamma%s^{%s}" % (tex, exp) | |
return r"\gamma%s" % tex | |
def _print_lerchphi(self, expr, exp=None): | |
tex = r"\left(%s, %s, %s\right)" % tuple(map(self._print, expr.args)) | |
if exp is None: | |
return r"\Phi%s" % tex | |
return r"\Phi^{%s}%s" % (exp, tex) | |
def _print_polylog(self, expr, exp=None): | |
s, z = map(self._print, expr.args) | |
tex = r"\left(%s\right)" % z | |
if exp is None: | |
return r"\operatorname{Li}_{%s}%s" % (s, tex) | |
return r"\operatorname{Li}_{%s}^{%s}%s" % (s, exp, tex) | |
def _print_jacobi(self, expr, exp=None): | |
n, a, b, x = map(self._print, expr.args) | |
tex = r"P_{%s}^{\left(%s,%s\right)}\left(%s\right)" % (n, a, b, x) | |
if exp is not None: | |
tex = r"\left(" + tex + r"\right)^{%s}" % (exp) | |
return tex | |
def _print_gegenbauer(self, expr, exp=None): | |
n, a, x = map(self._print, expr.args) | |
tex = r"C_{%s}^{\left(%s\right)}\left(%s\right)" % (n, a, x) | |
if exp is not None: | |
tex = r"\left(" + tex + r"\right)^{%s}" % (exp) | |
return tex | |
def _print_chebyshevt(self, expr, exp=None): | |
n, x = map(self._print, expr.args) | |
tex = r"T_{%s}\left(%s\right)" % (n, x) | |
if exp is not None: | |
tex = r"\left(" + tex + r"\right)^{%s}" % (exp) | |
return tex | |
def _print_chebyshevu(self, expr, exp=None): | |
n, x = map(self._print, expr.args) | |
tex = r"U_{%s}\left(%s\right)" % (n, x) | |
if exp is not None: | |
tex = r"\left(" + tex + r"\right)^{%s}" % (exp) | |
return tex | |
def _print_legendre(self, expr, exp=None): | |
n, x = map(self._print, expr.args) | |
tex = r"P_{%s}\left(%s\right)" % (n, x) | |
if exp is not None: | |
tex = r"\left(" + tex + r"\right)^{%s}" % (exp) | |
return tex | |
def _print_assoc_legendre(self, expr, exp=None): | |
n, a, x = map(self._print, expr.args) | |
tex = r"P_{%s}^{\left(%s\right)}\left(%s\right)" % (n, a, x) | |
if exp is not None: | |
tex = r"\left(" + tex + r"\right)^{%s}" % (exp) | |
return tex | |
def _print_hermite(self, expr, exp=None): | |
n, x = map(self._print, expr.args) | |
tex = r"H_{%s}\left(%s\right)" % (n, x) | |
if exp is not None: | |
tex = r"\left(" + tex + r"\right)^{%s}" % (exp) | |
return tex | |
def _print_laguerre(self, expr, exp=None): | |
n, x = map(self._print, expr.args) | |
tex = r"L_{%s}\left(%s\right)" % (n, x) | |
if exp is not None: | |
tex = r"\left(" + tex + r"\right)^{%s}" % (exp) | |
return tex | |
def _print_assoc_laguerre(self, expr, exp=None): | |
n, a, x = map(self._print, expr.args) | |
tex = r"L_{%s}^{\left(%s\right)}\left(%s\right)" % (n, a, x) | |
if exp is not None: | |
tex = r"\left(" + tex + r"\right)^{%s}" % (exp) | |
return tex | |
def _print_Ynm(self, expr, exp=None): | |
n, m, theta, phi = map(self._print, expr.args) | |
tex = r"Y_{%s}^{%s}\left(%s,%s\right)" % (n, m, theta, phi) | |
if exp is not None: | |
tex = r"\left(" + tex + r"\right)^{%s}" % (exp) | |
return tex | |
def _print_Znm(self, expr, exp=None): | |
n, m, theta, phi = map(self._print, expr.args) | |
tex = r"Z_{%s}^{%s}\left(%s,%s\right)" % (n, m, theta, phi) | |
if exp is not None: | |
tex = r"\left(" + tex + r"\right)^{%s}" % (exp) | |
return tex | |
def __print_mathieu_functions(self, character, args, prime=False, exp=None): | |
a, q, z = map(self._print, args) | |
sup = r"^{\prime}" if prime else "" | |
exp = "" if not exp else "^{%s}" % exp | |
return r"%s%s\left(%s, %s, %s\right)%s" % (character, sup, a, q, z, exp) | |
def _print_mathieuc(self, expr, exp=None): | |
return self.__print_mathieu_functions("C", expr.args, exp=exp) | |
def _print_mathieus(self, expr, exp=None): | |
return self.__print_mathieu_functions("S", expr.args, exp=exp) | |
def _print_mathieucprime(self, expr, exp=None): | |
return self.__print_mathieu_functions("C", expr.args, prime=True, exp=exp) | |
def _print_mathieusprime(self, expr, exp=None): | |
return self.__print_mathieu_functions("S", expr.args, prime=True, exp=exp) | |
def _print_Rational(self, expr): | |
if expr.q != 1: | |
sign = "" | |
p = expr.p | |
if expr.p < 0: | |
sign = "- " | |
p = -p | |
if self._settings['fold_short_frac']: | |
return r"%s%d / %d" % (sign, p, expr.q) | |
return r"%s\frac{%d}{%d}" % (sign, p, expr.q) | |
else: | |
return self._print(expr.p) | |
def _print_Order(self, expr): | |
s = self._print(expr.expr) | |
if expr.point and any(p != S.Zero for p in expr.point) or \ | |
len(expr.variables) > 1: | |
s += '; ' | |
if len(expr.variables) > 1: | |
s += self._print(expr.variables) | |
elif expr.variables: | |
s += self._print(expr.variables[0]) | |
s += r'\rightarrow ' | |
if len(expr.point) > 1: | |
s += self._print(expr.point) | |
else: | |
s += self._print(expr.point[0]) | |
return r"O\left(%s\right)" % s | |
def _print_Symbol(self, expr: Symbol, style='plain'): | |
name: str = self._settings['symbol_names'].get(expr) | |
if name is not None: | |
return name | |
return self._deal_with_super_sub(expr.name, style=style) | |
_print_RandomSymbol = _print_Symbol | |
def _deal_with_super_sub(self, string: str, style='plain') -> str: | |
if '{' in string: | |
name, supers, subs = string, [], [] | |
else: | |
name, supers, subs = split_super_sub(string) | |
name = translate(name) | |
supers = [translate(sup) for sup in supers] | |
subs = [translate(sub) for sub in subs] | |
# apply the style only to the name | |
if style == 'bold': | |
name = "\\mathbf{{{}}}".format(name) | |
# glue all items together: | |
if supers: | |
name += "^{%s}" % " ".join(supers) | |
if subs: | |
name += "_{%s}" % " ".join(subs) | |
return name | |
def _print_Relational(self, expr): | |
if self._settings['itex']: | |
gt = r"\gt" | |
lt = r"\lt" | |
else: | |
gt = ">" | |
lt = "<" | |
charmap = { | |
"==": "=", | |
">": gt, | |
"<": lt, | |
">=": r"\geq", | |
"<=": r"\leq", | |
"!=": r"\neq", | |
} | |
return "%s %s %s" % (self._print(expr.lhs), | |
charmap[expr.rel_op], self._print(expr.rhs)) | |
def _print_Piecewise(self, expr): | |
ecpairs = [r"%s & \text{for}\: %s" % (self._print(e), self._print(c)) | |
for e, c in expr.args[:-1]] | |
if expr.args[-1].cond == true: | |
ecpairs.append(r"%s & \text{otherwise}" % | |
self._print(expr.args[-1].expr)) | |
else: | |
ecpairs.append(r"%s & \text{for}\: %s" % | |
(self._print(expr.args[-1].expr), | |
self._print(expr.args[-1].cond))) | |
tex = r"\begin{cases} %s \end{cases}" | |
return tex % r" \\".join(ecpairs) | |
def _print_matrix_contents(self, expr): | |
lines = [] | |
for line in range(expr.rows): # horrible, should be 'rows' | |
lines.append(" & ".join([self._print(i) for i in expr[line, :]])) | |
mat_str = self._settings['mat_str'] | |
if mat_str is None: | |
if self._settings['mode'] == 'inline': | |
mat_str = 'smallmatrix' | |
else: | |
if (expr.cols <= 10) is True: | |
mat_str = 'matrix' | |
else: | |
mat_str = 'array' | |
out_str = r'\begin{%MATSTR%}%s\end{%MATSTR%}' | |
out_str = out_str.replace('%MATSTR%', mat_str) | |
if mat_str == 'array': | |
out_str = out_str.replace('%s', '{' + 'c'*expr.cols + '}%s') | |
return out_str % r"\\".join(lines) | |
def _print_MatrixBase(self, expr): | |
out_str = self._print_matrix_contents(expr) | |
if self._settings['mat_delim']: | |
left_delim = self._settings['mat_delim'] | |
right_delim = self._delim_dict[left_delim] | |
out_str = r'\left' + left_delim + out_str + \ | |
r'\right' + right_delim | |
return out_str | |
def _print_MatrixElement(self, expr): | |
matrix_part = self.parenthesize(expr.parent, PRECEDENCE['Atom'], strict=True) | |
index_part = f"{self._print(expr.i)},{self._print(expr.j)}" | |
return f"{{{matrix_part}}}_{{{index_part}}}" | |
def _print_MatrixSlice(self, expr): | |
def latexslice(x, dim): | |
x = list(x) | |
if x[2] == 1: | |
del x[2] | |
if x[0] == 0: | |
x[0] = None | |
if x[1] == dim: | |
x[1] = None | |
return ':'.join(self._print(xi) if xi is not None else '' for xi in x) | |
return (self.parenthesize(expr.parent, PRECEDENCE["Atom"], strict=True) + r'\left[' + | |
latexslice(expr.rowslice, expr.parent.rows) + ', ' + | |
latexslice(expr.colslice, expr.parent.cols) + r'\right]') | |
def _print_BlockMatrix(self, expr): | |
return self._print(expr.blocks) | |
def _print_Transpose(self, expr): | |
mat = expr.arg | |
from sympy.matrices import MatrixSymbol, BlockMatrix | |
if (not isinstance(mat, MatrixSymbol) and | |
not isinstance(mat, BlockMatrix) and mat.is_MatrixExpr): | |
return r"\left(%s\right)^{T}" % self._print(mat) | |
else: | |
s = self.parenthesize(mat, precedence_traditional(expr), True) | |
if '^' in s: | |
return r"\left(%s\right)^{T}" % s | |
else: | |
return "%s^{T}" % s | |
def _print_Trace(self, expr): | |
mat = expr.arg | |
return r"\operatorname{tr}\left(%s \right)" % self._print(mat) | |
def _print_Adjoint(self, expr): | |
style_to_latex = { | |
"dagger" : r"\dagger", | |
"star" : r"\ast", | |
"hermitian": r"\mathsf{H}" | |
} | |
adjoint_style = style_to_latex.get(self._settings["adjoint_style"], r"\dagger") | |
mat = expr.arg | |
from sympy.matrices import MatrixSymbol, BlockMatrix | |
if (not isinstance(mat, MatrixSymbol) and | |
not isinstance(mat, BlockMatrix) and mat.is_MatrixExpr): | |
return r"\left(%s\right)^{%s}" % (self._print(mat), adjoint_style) | |
else: | |
s = self.parenthesize(mat, precedence_traditional(expr), True) | |
if '^' in s: | |
return r"\left(%s\right)^{%s}" % (s, adjoint_style) | |
else: | |
return r"%s^{%s}" % (s, adjoint_style) | |
def _print_MatMul(self, expr): | |
from sympy import MatMul | |
# Parenthesize nested MatMul but not other types of Mul objects: | |
parens = lambda x: self._print(x) if isinstance(x, Mul) and not isinstance(x, MatMul) else \ | |
self.parenthesize(x, precedence_traditional(expr), False) | |
args = list(expr.args) | |
if expr.could_extract_minus_sign(): | |
if args[0] == -1: | |
args = args[1:] | |
else: | |
args[0] = -args[0] | |
return '- ' + ' '.join(map(parens, args)) | |
else: | |
return ' '.join(map(parens, args)) | |
def _print_Determinant(self, expr): | |
mat = expr.arg | |
if mat.is_MatrixExpr: | |
from sympy.matrices.expressions.blockmatrix import BlockMatrix | |
if isinstance(mat, BlockMatrix): | |
return r"\left|{%s}\right|" % self._print_matrix_contents(mat.blocks) | |
return r"\left|{%s}\right|" % self._print(mat) | |
return r"\left|{%s}\right|" % self._print_matrix_contents(mat) | |
def _print_Mod(self, expr, exp=None): | |
if exp is not None: | |
return r'\left(%s \bmod %s\right)^{%s}' % \ | |
(self.parenthesize(expr.args[0], PRECEDENCE['Mul'], | |
strict=True), | |
self.parenthesize(expr.args[1], PRECEDENCE['Mul'], | |
strict=True), | |
exp) | |
return r'%s \bmod %s' % (self.parenthesize(expr.args[0], | |
PRECEDENCE['Mul'], | |
strict=True), | |
self.parenthesize(expr.args[1], | |
PRECEDENCE['Mul'], | |
strict=True)) | |
def _print_HadamardProduct(self, expr): | |
args = expr.args | |
prec = PRECEDENCE['Pow'] | |
parens = self.parenthesize | |
return r' \circ '.join( | |
(parens(arg, prec, strict=True) for arg in args)) | |
def _print_HadamardPower(self, expr): | |
if precedence_traditional(expr.exp) < PRECEDENCE["Mul"]: | |
template = r"%s^{\circ \left({%s}\right)}" | |
else: | |
template = r"%s^{\circ {%s}}" | |
return self._helper_print_standard_power(expr, template) | |
def _print_KroneckerProduct(self, expr): | |
args = expr.args | |
prec = PRECEDENCE['Pow'] | |
parens = self.parenthesize | |
return r' \otimes '.join( | |
(parens(arg, prec, strict=True) for arg in args)) | |
def _print_MatPow(self, expr): | |
base, exp = expr.base, expr.exp | |
from sympy.matrices import MatrixSymbol | |
if not isinstance(base, MatrixSymbol) and base.is_MatrixExpr: | |
return "\\left(%s\\right)^{%s}" % (self._print(base), | |
self._print(exp)) | |
else: | |
base_str = self._print(base) | |
if '^' in base_str: | |
return r"\left(%s\right)^{%s}" % (base_str, self._print(exp)) | |
else: | |
return "%s^{%s}" % (base_str, self._print(exp)) | |
def _print_MatrixSymbol(self, expr): | |
return self._print_Symbol(expr, style=self._settings[ | |
'mat_symbol_style']) | |
def _print_ZeroMatrix(self, Z): | |
return "0" if self._settings[ | |
'mat_symbol_style'] == 'plain' else r"\mathbf{0}" | |
def _print_OneMatrix(self, O): | |
return "1" if self._settings[ | |
'mat_symbol_style'] == 'plain' else r"\mathbf{1}" | |
def _print_Identity(self, I): | |
return r"\mathbb{I}" if self._settings[ | |
'mat_symbol_style'] == 'plain' else r"\mathbf{I}" | |
def _print_PermutationMatrix(self, P): | |
perm_str = self._print(P.args[0]) | |
return "P_{%s}" % perm_str | |
def _print_NDimArray(self, expr: NDimArray): | |
if expr.rank() == 0: | |
return self._print(expr[()]) | |
mat_str = self._settings['mat_str'] | |
if mat_str is None: | |
if self._settings['mode'] == 'inline': | |
mat_str = 'smallmatrix' | |
else: | |
if (expr.rank() == 0) or (expr.shape[-1] <= 10): | |
mat_str = 'matrix' | |
else: | |
mat_str = 'array' | |
block_str = r'\begin{%MATSTR%}%s\end{%MATSTR%}' | |
block_str = block_str.replace('%MATSTR%', mat_str) | |
if mat_str == 'array': | |
block_str = block_str.replace('%s', '{' + 'c'*expr.shape[0] + '}%s') | |
if self._settings['mat_delim']: | |
left_delim: str = self._settings['mat_delim'] | |
right_delim = self._delim_dict[left_delim] | |
block_str = r'\left' + left_delim + block_str + \ | |
r'\right' + right_delim | |
if expr.rank() == 0: | |
return block_str % "" | |
level_str: list[list[str]] = [[] for i in range(expr.rank() + 1)] | |
shape_ranges = [list(range(i)) for i in expr.shape] | |
for outer_i in itertools.product(*shape_ranges): | |
level_str[-1].append(self._print(expr[outer_i])) | |
even = True | |
for back_outer_i in range(expr.rank()-1, -1, -1): | |
if len(level_str[back_outer_i+1]) < expr.shape[back_outer_i]: | |
break | |
if even: | |
level_str[back_outer_i].append( | |
r" & ".join(level_str[back_outer_i+1])) | |
else: | |
level_str[back_outer_i].append( | |
block_str % (r"\\".join(level_str[back_outer_i+1]))) | |
if len(level_str[back_outer_i+1]) == 1: | |
level_str[back_outer_i][-1] = r"\left[" + \ | |
level_str[back_outer_i][-1] + r"\right]" | |
even = not even | |
level_str[back_outer_i+1] = [] | |
out_str = level_str[0][0] | |
if expr.rank() % 2 == 1: | |
out_str = block_str % out_str | |
return out_str | |
def _printer_tensor_indices(self, name, indices, index_map: dict): | |
out_str = self._print(name) | |
last_valence = None | |
prev_map = None | |
for index in indices: | |
new_valence = index.is_up | |
if ((index in index_map) or prev_map) and \ | |
last_valence == new_valence: | |
out_str += "," | |
if last_valence != new_valence: | |
if last_valence is not None: | |
out_str += "}" | |
if index.is_up: | |
out_str += "{}^{" | |
else: | |
out_str += "{}_{" | |
out_str += self._print(index.args[0]) | |
if index in index_map: | |
out_str += "=" | |
out_str += self._print(index_map[index]) | |
prev_map = True | |
else: | |
prev_map = False | |
last_valence = new_valence | |
if last_valence is not None: | |
out_str += "}" | |
return out_str | |
def _print_Tensor(self, expr): | |
name = expr.args[0].args[0] | |
indices = expr.get_indices() | |
return self._printer_tensor_indices(name, indices, {}) | |
def _print_TensorElement(self, expr): | |
name = expr.expr.args[0].args[0] | |
indices = expr.expr.get_indices() | |
index_map = expr.index_map | |
return self._printer_tensor_indices(name, indices, index_map) | |
def _print_TensMul(self, expr): | |
# prints expressions like "A(a)", "3*A(a)", "(1+x)*A(a)" | |
sign, args = expr._get_args_for_traditional_printer() | |
return sign + "".join( | |
[self.parenthesize(arg, precedence(expr)) for arg in args] | |
) | |
def _print_TensAdd(self, expr): | |
a = [] | |
args = expr.args | |
for x in args: | |
a.append(self.parenthesize(x, precedence(expr))) | |
a.sort() | |
s = ' + '.join(a) | |
s = s.replace('+ -', '- ') | |
return s | |
def _print_TensorIndex(self, expr): | |
return "{}%s{%s}" % ( | |
"^" if expr.is_up else "_", | |
self._print(expr.args[0]) | |
) | |
def _print_PartialDerivative(self, expr): | |
if len(expr.variables) == 1: | |
return r"\frac{\partial}{\partial {%s}}{%s}" % ( | |
self._print(expr.variables[0]), | |
self.parenthesize(expr.expr, PRECEDENCE["Mul"], False) | |
) | |
else: | |
return r"\frac{\partial^{%s}}{%s}{%s}" % ( | |
len(expr.variables), | |
" ".join([r"\partial {%s}" % self._print(i) for i in expr.variables]), | |
self.parenthesize(expr.expr, PRECEDENCE["Mul"], False) | |
) | |
def _print_ArraySymbol(self, expr): | |
return self._print(expr.name) | |
def _print_ArrayElement(self, expr): | |
return "{{%s}_{%s}}" % ( | |
self.parenthesize(expr.name, PRECEDENCE["Func"], True), | |
", ".join([f"{self._print(i)}" for i in expr.indices])) | |
def _print_UniversalSet(self, expr): | |
return r"\mathbb{U}" | |
def _print_frac(self, expr, exp=None): | |
if exp is None: | |
return r"\operatorname{frac}{\left(%s\right)}" % self._print(expr.args[0]) | |
else: | |
return r"\operatorname{frac}{\left(%s\right)}^{%s}" % ( | |
self._print(expr.args[0]), exp) | |
def _print_tuple(self, expr): | |
if self._settings['decimal_separator'] == 'comma': | |
sep = ";" | |
elif self._settings['decimal_separator'] == 'period': | |
sep = "," | |
else: | |
raise ValueError('Unknown Decimal Separator') | |
if len(expr) == 1: | |
# 1-tuple needs a trailing separator | |
return self._add_parens_lspace(self._print(expr[0]) + sep) | |
else: | |
return self._add_parens_lspace( | |
(sep + r" \ ").join([self._print(i) for i in expr])) | |
def _print_TensorProduct(self, expr): | |
elements = [self._print(a) for a in expr.args] | |
return r' \otimes '.join(elements) | |
def _print_WedgeProduct(self, expr): | |
elements = [self._print(a) for a in expr.args] | |
return r' \wedge '.join(elements) | |
def _print_Tuple(self, expr): | |
return self._print_tuple(expr) | |
def _print_list(self, expr): | |
if self._settings['decimal_separator'] == 'comma': | |
return r"\left[ %s\right]" % \ | |
r"; \ ".join([self._print(i) for i in expr]) | |
elif self._settings['decimal_separator'] == 'period': | |
return r"\left[ %s\right]" % \ | |
r", \ ".join([self._print(i) for i in expr]) | |
else: | |
raise ValueError('Unknown Decimal Separator') | |
def _print_dict(self, d): | |
keys = sorted(d.keys(), key=default_sort_key) | |
items = [] | |
for key in keys: | |
val = d[key] | |
items.append("%s : %s" % (self._print(key), self._print(val))) | |
return r"\left\{ %s\right\}" % r", \ ".join(items) | |
def _print_Dict(self, expr): | |
return self._print_dict(expr) | |
def _print_DiracDelta(self, expr, exp=None): | |
if len(expr.args) == 1 or expr.args[1] == 0: | |
tex = r"\delta\left(%s\right)" % self._print(expr.args[0]) | |
else: | |
tex = r"\delta^{\left( %s \right)}\left( %s \right)" % ( | |
self._print(expr.args[1]), self._print(expr.args[0])) | |
if exp: | |
tex = r"\left(%s\right)^{%s}" % (tex, exp) | |
return tex | |
def _print_SingularityFunction(self, expr, exp=None): | |
shift = self._print(expr.args[0] - expr.args[1]) | |
power = self._print(expr.args[2]) | |
tex = r"{\left\langle %s \right\rangle}^{%s}" % (shift, power) | |
if exp is not None: | |
tex = r"{\left({\langle %s \rangle}^{%s}\right)}^{%s}" % (shift, power, exp) | |
return tex | |
def _print_Heaviside(self, expr, exp=None): | |
pargs = ', '.join(self._print(arg) for arg in expr.pargs) | |
tex = r"\theta\left(%s\right)" % pargs | |
if exp: | |
tex = r"\left(%s\right)^{%s}" % (tex, exp) | |
return tex | |
def _print_KroneckerDelta(self, expr, exp=None): | |
i = self._print(expr.args[0]) | |
j = self._print(expr.args[1]) | |
if expr.args[0].is_Atom and expr.args[1].is_Atom: | |
tex = r'\delta_{%s %s}' % (i, j) | |
else: | |
tex = r'\delta_{%s, %s}' % (i, j) | |
if exp is not None: | |
tex = r'\left(%s\right)^{%s}' % (tex, exp) | |
return tex | |
def _print_LeviCivita(self, expr, exp=None): | |
indices = map(self._print, expr.args) | |
if all(x.is_Atom for x in expr.args): | |
tex = r'\varepsilon_{%s}' % " ".join(indices) | |
else: | |
tex = r'\varepsilon_{%s}' % ", ".join(indices) | |
if exp: | |
tex = r'\left(%s\right)^{%s}' % (tex, exp) | |
return tex | |
def _print_RandomDomain(self, d): | |
if hasattr(d, 'as_boolean'): | |
return '\\text{Domain: }' + self._print(d.as_boolean()) | |
elif hasattr(d, 'set'): | |
return ('\\text{Domain: }' + self._print(d.symbols) + ' \\in ' + | |
self._print(d.set)) | |
elif hasattr(d, 'symbols'): | |
return '\\text{Domain on }' + self._print(d.symbols) | |
else: | |
return self._print(None) | |
def _print_FiniteSet(self, s): | |
items = sorted(s.args, key=default_sort_key) | |
return self._print_set(items) | |
def _print_set(self, s): | |
items = sorted(s, key=default_sort_key) | |
if self._settings['decimal_separator'] == 'comma': | |
items = "; ".join(map(self._print, items)) | |
elif self._settings['decimal_separator'] == 'period': | |
items = ", ".join(map(self._print, items)) | |
else: | |
raise ValueError('Unknown Decimal Separator') | |
return r"\left\{%s\right\}" % items | |
_print_frozenset = _print_set | |
def _print_Range(self, s): | |
def _print_symbolic_range(): | |
# Symbolic Range that cannot be resolved | |
if s.args[0] == 0: | |
if s.args[2] == 1: | |
cont = self._print(s.args[1]) | |
else: | |
cont = ", ".join(self._print(arg) for arg in s.args) | |
else: | |
if s.args[2] == 1: | |
cont = ", ".join(self._print(arg) for arg in s.args[:2]) | |
else: | |
cont = ", ".join(self._print(arg) for arg in s.args) | |
return(f"\\text{{Range}}\\left({cont}\\right)") | |
dots = object() | |
if s.start.is_infinite and s.stop.is_infinite: | |
if s.step.is_positive: | |
printset = dots, -1, 0, 1, dots | |
else: | |
printset = dots, 1, 0, -1, dots | |
elif s.start.is_infinite: | |
printset = dots, s[-1] - s.step, s[-1] | |
elif s.stop.is_infinite: | |
it = iter(s) | |
printset = next(it), next(it), dots | |
elif s.is_empty is not None: | |
if (s.size < 4) == True: | |
printset = tuple(s) | |
elif s.is_iterable: | |
it = iter(s) | |
printset = next(it), next(it), dots, s[-1] | |
else: | |
return _print_symbolic_range() | |
else: | |
return _print_symbolic_range() | |
return (r"\left\{" + | |
r", ".join(self._print(el) if el is not dots else r'\ldots' for el in printset) + | |
r"\right\}") | |
def __print_number_polynomial(self, expr, letter, exp=None): | |
if len(expr.args) == 2: | |
if exp is not None: | |
return r"%s_{%s}^{%s}\left(%s\right)" % (letter, | |
self._print(expr.args[0]), exp, | |
self._print(expr.args[1])) | |
return r"%s_{%s}\left(%s\right)" % (letter, | |
self._print(expr.args[0]), self._print(expr.args[1])) | |
tex = r"%s_{%s}" % (letter, self._print(expr.args[0])) | |
if exp is not None: | |
tex = r"%s^{%s}" % (tex, exp) | |
return tex | |
def _print_bernoulli(self, expr, exp=None): | |
return self.__print_number_polynomial(expr, "B", exp) | |
def _print_genocchi(self, expr, exp=None): | |
return self.__print_number_polynomial(expr, "G", exp) | |
def _print_bell(self, expr, exp=None): | |
if len(expr.args) == 3: | |
tex1 = r"B_{%s, %s}" % (self._print(expr.args[0]), | |
self._print(expr.args[1])) | |
tex2 = r"\left(%s\right)" % r", ".join(self._print(el) for | |
el in expr.args[2]) | |
if exp is not None: | |
tex = r"%s^{%s}%s" % (tex1, exp, tex2) | |
else: | |
tex = tex1 + tex2 | |
return tex | |
return self.__print_number_polynomial(expr, "B", exp) | |
def _print_fibonacci(self, expr, exp=None): | |
return self.__print_number_polynomial(expr, "F", exp) | |
def _print_lucas(self, expr, exp=None): | |
tex = r"L_{%s}" % self._print(expr.args[0]) | |
if exp is not None: | |
tex = r"%s^{%s}" % (tex, exp) | |
return tex | |
def _print_tribonacci(self, expr, exp=None): | |
return self.__print_number_polynomial(expr, "T", exp) | |
def _print_mobius(self, expr, exp=None): | |
if exp is None: | |
return r'\mu\left(%s\right)' % self._print(expr.args[0]) | |
return r'\mu^{%s}\left(%s\right)' % (exp, self._print(expr.args[0])) | |
def _print_SeqFormula(self, s): | |
dots = object() | |
if len(s.start.free_symbols) > 0 or len(s.stop.free_symbols) > 0: | |
return r"\left\{%s\right\}_{%s=%s}^{%s}" % ( | |
self._print(s.formula), | |
self._print(s.variables[0]), | |
self._print(s.start), | |
self._print(s.stop) | |
) | |
if s.start is S.NegativeInfinity: | |
stop = s.stop | |
printset = (dots, s.coeff(stop - 3), s.coeff(stop - 2), | |
s.coeff(stop - 1), s.coeff(stop)) | |
elif s.stop is S.Infinity or s.length > 4: | |
printset = s[:4] | |
printset.append(dots) | |
else: | |
printset = tuple(s) | |
return (r"\left[" + | |
r", ".join(self._print(el) if el is not dots else r'\ldots' for el in printset) + | |
r"\right]") | |
_print_SeqPer = _print_SeqFormula | |
_print_SeqAdd = _print_SeqFormula | |
_print_SeqMul = _print_SeqFormula | |
def _print_Interval(self, i): | |
if i.start == i.end: | |
return r"\left\{%s\right\}" % self._print(i.start) | |
else: | |
if i.left_open: | |
left = '(' | |
else: | |
left = '[' | |
if i.right_open: | |
right = ')' | |
else: | |
right = ']' | |
return r"\left%s%s, %s\right%s" % \ | |
(left, self._print(i.start), self._print(i.end), right) | |
def _print_AccumulationBounds(self, i): | |
return r"\left\langle %s, %s\right\rangle" % \ | |
(self._print(i.min), self._print(i.max)) | |
def _print_Union(self, u): | |
prec = precedence_traditional(u) | |
args_str = [self.parenthesize(i, prec) for i in u.args] | |
return r" \cup ".join(args_str) | |
def _print_Complement(self, u): | |
prec = precedence_traditional(u) | |
args_str = [self.parenthesize(i, prec) for i in u.args] | |
return r" \setminus ".join(args_str) | |
def _print_Intersection(self, u): | |
prec = precedence_traditional(u) | |
args_str = [self.parenthesize(i, prec) for i in u.args] | |
return r" \cap ".join(args_str) | |
def _print_SymmetricDifference(self, u): | |
prec = precedence_traditional(u) | |
args_str = [self.parenthesize(i, prec) for i in u.args] | |
return r" \triangle ".join(args_str) | |
def _print_ProductSet(self, p): | |
prec = precedence_traditional(p) | |
if len(p.sets) >= 1 and not has_variety(p.sets): | |
return self.parenthesize(p.sets[0], prec) + "^{%d}" % len(p.sets) | |
return r" \times ".join( | |
self.parenthesize(set, prec) for set in p.sets) | |
def _print_EmptySet(self, e): | |
return r"\emptyset" | |
def _print_Naturals(self, n): | |
return r"\mathbb{N}" | |
def _print_Naturals0(self, n): | |
return r"\mathbb{N}_0" | |
def _print_Integers(self, i): | |
return r"\mathbb{Z}" | |
def _print_Rationals(self, i): | |
return r"\mathbb{Q}" | |
def _print_Reals(self, i): | |
return r"\mathbb{R}" | |
def _print_Complexes(self, i): | |
return r"\mathbb{C}" | |
def _print_ImageSet(self, s): | |
expr = s.lamda.expr | |
sig = s.lamda.signature | |
xys = ((self._print(x), self._print(y)) for x, y in zip(sig, s.base_sets)) | |
xinys = r", ".join(r"%s \in %s" % xy for xy in xys) | |
return r"\left\{%s\; \middle|\; %s\right\}" % (self._print(expr), xinys) | |
def _print_ConditionSet(self, s): | |
vars_print = ', '.join([self._print(var) for var in Tuple(s.sym)]) | |
if s.base_set is S.UniversalSet: | |
return r"\left\{%s\; \middle|\; %s \right\}" % \ | |
(vars_print, self._print(s.condition)) | |
return r"\left\{%s\; \middle|\; %s \in %s \wedge %s \right\}" % ( | |
vars_print, | |
vars_print, | |
self._print(s.base_set), | |
self._print(s.condition)) | |
def _print_PowerSet(self, expr): | |
arg_print = self._print(expr.args[0]) | |
return r"\mathcal{{P}}\left({}\right)".format(arg_print) | |
def _print_ComplexRegion(self, s): | |
vars_print = ', '.join([self._print(var) for var in s.variables]) | |
return r"\left\{%s\; \middle|\; %s \in %s \right\}" % ( | |
self._print(s.expr), | |
vars_print, | |
self._print(s.sets)) | |
def _print_Contains(self, e): | |
return r"%s \in %s" % tuple(self._print(a) for a in e.args) | |
def _print_FourierSeries(self, s): | |
if s.an.formula is S.Zero and s.bn.formula is S.Zero: | |
return self._print(s.a0) | |
return self._print_Add(s.truncate()) + r' + \ldots' | |
def _print_FormalPowerSeries(self, s): | |
return self._print_Add(s.infinite) | |
def _print_FiniteField(self, expr): | |
return r"\mathbb{F}_{%s}" % expr.mod | |
def _print_IntegerRing(self, expr): | |
return r"\mathbb{Z}" | |
def _print_RationalField(self, expr): | |
return r"\mathbb{Q}" | |
def _print_RealField(self, expr): | |
return r"\mathbb{R}" | |
def _print_ComplexField(self, expr): | |
return r"\mathbb{C}" | |
def _print_PolynomialRing(self, expr): | |
domain = self._print(expr.domain) | |
symbols = ", ".join(map(self._print, expr.symbols)) | |
return r"%s\left[%s\right]" % (domain, symbols) | |
def _print_FractionField(self, expr): | |
domain = self._print(expr.domain) | |
symbols = ", ".join(map(self._print, expr.symbols)) | |
return r"%s\left(%s\right)" % (domain, symbols) | |
def _print_PolynomialRingBase(self, expr): | |
domain = self._print(expr.domain) | |
symbols = ", ".join(map(self._print, expr.symbols)) | |
inv = "" | |
if not expr.is_Poly: | |
inv = r"S_<^{-1}" | |
return r"%s%s\left[%s\right]" % (inv, domain, symbols) | |
def _print_Poly(self, poly): | |
cls = poly.__class__.__name__ | |
terms = [] | |
for monom, coeff in poly.terms(): | |
s_monom = '' | |
for i, exp in enumerate(monom): | |
if exp > 0: | |
if exp == 1: | |
s_monom += self._print(poly.gens[i]) | |
else: | |
s_monom += self._print(pow(poly.gens[i], exp)) | |
if coeff.is_Add: | |
if s_monom: | |
s_coeff = r"\left(%s\right)" % self._print(coeff) | |
else: | |
s_coeff = self._print(coeff) | |
else: | |
if s_monom: | |
if coeff is S.One: | |
terms.extend(['+', s_monom]) | |
continue | |
if coeff is S.NegativeOne: | |
terms.extend(['-', s_monom]) | |
continue | |
s_coeff = self._print(coeff) | |
if not s_monom: | |
s_term = s_coeff | |
else: | |
s_term = s_coeff + " " + s_monom | |
if s_term.startswith('-'): | |
terms.extend(['-', s_term[1:]]) | |
else: | |
terms.extend(['+', s_term]) | |
if terms[0] in ('-', '+'): | |
modifier = terms.pop(0) | |
if modifier == '-': | |
terms[0] = '-' + terms[0] | |
expr = ' '.join(terms) | |
gens = list(map(self._print, poly.gens)) | |
domain = "domain=%s" % self._print(poly.get_domain()) | |
args = ", ".join([expr] + gens + [domain]) | |
if cls in accepted_latex_functions: | |
tex = r"\%s {\left(%s \right)}" % (cls, args) | |
else: | |
tex = r"\operatorname{%s}{\left( %s \right)}" % (cls, args) | |
return tex | |
def _print_ComplexRootOf(self, root): | |
cls = root.__class__.__name__ | |
if cls == "ComplexRootOf": | |
cls = "CRootOf" | |
expr = self._print(root.expr) | |
index = root.index | |
if cls in accepted_latex_functions: | |
return r"\%s {\left(%s, %d\right)}" % (cls, expr, index) | |
else: | |
return r"\operatorname{%s} {\left(%s, %d\right)}" % (cls, expr, | |
index) | |
def _print_RootSum(self, expr): | |
cls = expr.__class__.__name__ | |
args = [self._print(expr.expr)] | |
if expr.fun is not S.IdentityFunction: | |
args.append(self._print(expr.fun)) | |
if cls in accepted_latex_functions: | |
return r"\%s {\left(%s\right)}" % (cls, ", ".join(args)) | |
else: | |
return r"\operatorname{%s} {\left(%s\right)}" % (cls, | |
", ".join(args)) | |
def _print_OrdinalOmega(self, expr): | |
return r"\omega" | |
def _print_OmegaPower(self, expr): | |
exp, mul = expr.args | |
if mul != 1: | |
if exp != 1: | |
return r"{} \omega^{{{}}}".format(mul, exp) | |
else: | |
return r"{} \omega".format(mul) | |
else: | |
if exp != 1: | |
return r"\omega^{{{}}}".format(exp) | |
else: | |
return r"\omega" | |
def _print_Ordinal(self, expr): | |
return " + ".join([self._print(arg) for arg in expr.args]) | |
def _print_PolyElement(self, poly): | |
mul_symbol = self._settings['mul_symbol_latex'] | |
return poly.str(self, PRECEDENCE, "{%s}^{%d}", mul_symbol) | |
def _print_FracElement(self, frac): | |
if frac.denom == 1: | |
return self._print(frac.numer) | |
else: | |
numer = self._print(frac.numer) | |
denom = self._print(frac.denom) | |
return r"\frac{%s}{%s}" % (numer, denom) | |
def _print_euler(self, expr, exp=None): | |
m, x = (expr.args[0], None) if len(expr.args) == 1 else expr.args | |
tex = r"E_{%s}" % self._print(m) | |
if exp is not None: | |
tex = r"%s^{%s}" % (tex, exp) | |
if x is not None: | |
tex = r"%s\left(%s\right)" % (tex, self._print(x)) | |
return tex | |
def _print_catalan(self, expr, exp=None): | |
tex = r"C_{%s}" % self._print(expr.args[0]) | |
if exp is not None: | |
tex = r"%s^{%s}" % (tex, 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 self._print_UnifiedTransform(expr, 'M') | |
def _print_InverseMellinTransform(self, expr): | |
return self._print_UnifiedTransform(expr, 'M', True) | |
def _print_LaplaceTransform(self, expr): | |
return self._print_UnifiedTransform(expr, 'L') | |
def _print_InverseLaplaceTransform(self, expr): | |
return self._print_UnifiedTransform(expr, 'L', True) | |
def _print_FourierTransform(self, expr): | |
return self._print_UnifiedTransform(expr, 'F') | |
def _print_InverseFourierTransform(self, expr): | |
return self._print_UnifiedTransform(expr, 'F', True) | |
def _print_SineTransform(self, expr): | |
return self._print_UnifiedTransform(expr, 'SIN') | |
def _print_InverseSineTransform(self, expr): | |
return self._print_UnifiedTransform(expr, 'SIN', True) | |
def _print_CosineTransform(self, expr): | |
return self._print_UnifiedTransform(expr, 'COS') | |
def _print_InverseCosineTransform(self, expr): | |
return self._print_UnifiedTransform(expr, 'COS', True) | |
def _print_DMP(self, p): | |
try: | |
if p.ring is not None: | |
# TODO incorporate order | |
return self._print(p.ring.to_sympy(p)) | |
except SympifyError: | |
pass | |
return self._print(repr(p)) | |
def _print_DMF(self, p): | |
return self._print_DMP(p) | |
def _print_Object(self, object): | |
return self._print(Symbol(object.name)) | |
def _print_LambertW(self, expr, exp=None): | |
arg0 = self._print(expr.args[0]) | |
exp = r"^{%s}" % (exp,) if exp is not None else "" | |
if len(expr.args) == 1: | |
result = r"W%s\left(%s\right)" % (exp, arg0) | |
else: | |
arg1 = self._print(expr.args[1]) | |
result = "W{0}_{{{1}}}\\left({2}\\right)".format(exp, arg1, arg0) | |
return result | |
def _print_Expectation(self, expr): | |
return r"\operatorname{{E}}\left[{}\right]".format(self._print(expr.args[0])) | |
def _print_Variance(self, expr): | |
return r"\operatorname{{Var}}\left({}\right)".format(self._print(expr.args[0])) | |
def _print_Covariance(self, expr): | |
return r"\operatorname{{Cov}}\left({}\right)".format(", ".join(self._print(arg) for arg in expr.args)) | |
def _print_Probability(self, expr): | |
return r"\operatorname{{P}}\left({}\right)".format(self._print(expr.args[0])) | |
def _print_Morphism(self, morphism): | |
domain = self._print(morphism.domain) | |
codomain = self._print(morphism.codomain) | |
return "%s\\rightarrow %s" % (domain, codomain) | |
def _print_TransferFunction(self, expr): | |
num, den = self._print(expr.num), self._print(expr.den) | |
return r"\frac{%s}{%s}" % (num, den) | |
def _print_Series(self, expr): | |
args = list(expr.args) | |
parens = lambda x: self.parenthesize(x, precedence_traditional(expr), | |
False) | |
return ' '.join(map(parens, args)) | |
def _print_MIMOSeries(self, expr): | |
from sympy.physics.control.lti import MIMOParallel | |
args = list(expr.args)[::-1] | |
parens = lambda x: self.parenthesize(x, precedence_traditional(expr), | |
False) if isinstance(x, MIMOParallel) else self._print(x) | |
return r"\cdot".join(map(parens, args)) | |
def _print_Parallel(self, expr): | |
return ' + '.join(map(self._print, expr.args)) | |
def _print_MIMOParallel(self, expr): | |
return ' + '.join(map(self._print, expr.args)) | |
def _print_Feedback(self, expr): | |
from sympy.physics.control import TransferFunction, Series | |
num, tf = expr.sys1, TransferFunction(1, 1, expr.var) | |
num_arg_list = list(num.args) if isinstance(num, Series) else [num] | |
den_arg_list = list(expr.sys2.args) if \ | |
isinstance(expr.sys2, Series) else [expr.sys2] | |
den_term_1 = tf | |
if isinstance(num, Series) and isinstance(expr.sys2, Series): | |
den_term_2 = Series(*num_arg_list, *den_arg_list) | |
elif isinstance(num, Series) and isinstance(expr.sys2, TransferFunction): | |
if expr.sys2 == tf: | |
den_term_2 = Series(*num_arg_list) | |
else: | |
den_term_2 = tf, Series(*num_arg_list, expr.sys2) | |
elif isinstance(num, TransferFunction) and isinstance(expr.sys2, Series): | |
if num == tf: | |
den_term_2 = Series(*den_arg_list) | |
else: | |
den_term_2 = Series(num, *den_arg_list) | |
else: | |
if num == tf: | |
den_term_2 = Series(*den_arg_list) | |
elif expr.sys2 == tf: | |
den_term_2 = Series(*num_arg_list) | |
else: | |
den_term_2 = Series(*num_arg_list, *den_arg_list) | |
numer = self._print(num) | |
denom_1 = self._print(den_term_1) | |
denom_2 = self._print(den_term_2) | |
_sign = "+" if expr.sign == -1 else "-" | |
return r"\frac{%s}{%s %s %s}" % (numer, denom_1, _sign, denom_2) | |
def _print_MIMOFeedback(self, expr): | |
from sympy.physics.control import MIMOSeries | |
inv_mat = self._print(MIMOSeries(expr.sys2, expr.sys1)) | |
sys1 = self._print(expr.sys1) | |
_sign = "+" if expr.sign == -1 else "-" | |
return r"\left(I_{\tau} %s %s\right)^{-1} \cdot %s" % (_sign, inv_mat, sys1) | |
def _print_TransferFunctionMatrix(self, expr): | |
mat = self._print(expr._expr_mat) | |
return r"%s_\tau" % mat | |
def _print_DFT(self, expr): | |
return r"\text{{{}}}_{{{}}}".format(expr.__class__.__name__, expr.n) | |
_print_IDFT = _print_DFT | |
def _print_NamedMorphism(self, morphism): | |
pretty_name = self._print(Symbol(morphism.name)) | |
pretty_morphism = self._print_Morphism(morphism) | |
return "%s:%s" % (pretty_name, pretty_morphism) | |
def _print_IdentityMorphism(self, morphism): | |
from sympy.categories import NamedMorphism | |
return self._print_NamedMorphism(NamedMorphism( | |
morphism.domain, morphism.codomain, "id")) | |
def _print_CompositeMorphism(self, morphism): | |
# All components of the morphism have names and it is thus | |
# possible to build the name of the composite. | |
component_names_list = [self._print(Symbol(component.name)) for | |
component in morphism.components] | |
component_names_list.reverse() | |
component_names = "\\circ ".join(component_names_list) + ":" | |
pretty_morphism = self._print_Morphism(morphism) | |
return component_names + pretty_morphism | |
def _print_Category(self, morphism): | |
return r"\mathbf{{{}}}".format(self._print(Symbol(morphism.name))) | |
def _print_Diagram(self, diagram): | |
if not diagram.premises: | |
# This is an empty diagram. | |
return self._print(S.EmptySet) | |
latex_result = self._print(diagram.premises) | |
if diagram.conclusions: | |
latex_result += "\\Longrightarrow %s" % \ | |
self._print(diagram.conclusions) | |
return latex_result | |
def _print_DiagramGrid(self, grid): | |
latex_result = "\\begin{array}{%s}\n" % ("c" * grid.width) | |
for i in range(grid.height): | |
for j in range(grid.width): | |
if grid[i, j]: | |
latex_result += latex(grid[i, j]) | |
latex_result += " " | |
if j != grid.width - 1: | |
latex_result += "& " | |
if i != grid.height - 1: | |
latex_result += "\\\\" | |
latex_result += "\n" | |
latex_result += "\\end{array}\n" | |
return latex_result | |
def _print_FreeModule(self, M): | |
return '{{{}}}^{{{}}}'.format(self._print(M.ring), self._print(M.rank)) | |
def _print_FreeModuleElement(self, m): | |
# Print as row vector for convenience, for now. | |
return r"\left[ {} \right]".format(",".join( | |
'{' + self._print(x) + '}' for x in m)) | |
def _print_SubModule(self, m): | |
gens = [[self._print(m.ring.to_sympy(x)) for x in g] for g in m.gens] | |
curly = lambda o: r"{" + o + r"}" | |
square = lambda o: r"\left[ " + o + r" \right]" | |
gens_latex = ",".join(curly(square(",".join(curly(x) for x in g))) for g in gens) | |
return r"\left\langle {} \right\rangle".format(gens_latex) | |
def _print_SubQuotientModule(self, m): | |
gens_latex = ",".join(["{" + self._print(g) + "}" for g in m.gens]) | |
return r"\left\langle {} \right\rangle".format(gens_latex) | |
def _print_ModuleImplementedIdeal(self, m): | |
gens = [m.ring.to_sympy(x) for [x] in m._module.gens] | |
gens_latex = ",".join('{' + self._print(x) + '}' for x in gens) | |
return r"\left\langle {} \right\rangle".format(gens_latex) | |
def _print_Quaternion(self, expr): | |
# TODO: This expression is potentially confusing, | |
# shall we print it as `Quaternion( ... )`? | |
s = [self.parenthesize(i, PRECEDENCE["Mul"], strict=True) | |
for i in expr.args] | |
a = [s[0]] + [i+" "+j for i, j in zip(s[1:], "ijk")] | |
return " + ".join(a) | |
def _print_QuotientRing(self, R): | |
# TODO nicer fractions for few generators... | |
return r"\frac{{{}}}{{{}}}".format(self._print(R.ring), | |
self._print(R.base_ideal)) | |
def _print_QuotientRingElement(self, x): | |
x_latex = self._print(x.ring.to_sympy(x)) | |
return r"{{{}}} + {{{}}}".format(x_latex, | |
self._print(x.ring.base_ideal)) | |
def _print_QuotientModuleElement(self, m): | |
data = [m.module.ring.to_sympy(x) for x in m.data] | |
data_latex = r"\left[ {} \right]".format(",".join( | |
'{' + self._print(x) + '}' for x in data)) | |
return r"{{{}}} + {{{}}}".format(data_latex, | |
self._print(m.module.killed_module)) | |
def _print_QuotientModule(self, M): | |
# TODO nicer fractions for few generators... | |
return r"\frac{{{}}}{{{}}}".format(self._print(M.base), | |
self._print(M.killed_module)) | |
def _print_MatrixHomomorphism(self, h): | |
return r"{{{}}} : {{{}}} \to {{{}}}".format(self._print(h._sympy_matrix()), | |
self._print(h.domain), self._print(h.codomain)) | |
def _print_Manifold(self, manifold): | |
string = manifold.name.name | |
if '{' in string: | |
name, supers, subs = string, [], [] | |
else: | |
name, supers, subs = split_super_sub(string) | |
name = translate(name) | |
supers = [translate(sup) for sup in supers] | |
subs = [translate(sub) for sub in subs] | |
name = r'\text{%s}' % name | |
if supers: | |
name += "^{%s}" % " ".join(supers) | |
if subs: | |
name += "_{%s}" % " ".join(subs) | |
return name | |
def _print_Patch(self, patch): | |
return r'\text{%s}_{%s}' % (self._print(patch.name), self._print(patch.manifold)) | |
def _print_CoordSystem(self, coordsys): | |
return r'\text{%s}^{\text{%s}}_{%s}' % ( | |
self._print(coordsys.name), self._print(coordsys.patch.name), self._print(coordsys.manifold) | |
) | |
def _print_CovarDerivativeOp(self, cvd): | |
return r'\mathbb{\nabla}_{%s}' % self._print(cvd._wrt) | |
def _print_BaseScalarField(self, field): | |
string = field._coord_sys.symbols[field._index].name | |
return r'\mathbf{{{}}}'.format(self._print(Symbol(string))) | |
def _print_BaseVectorField(self, field): | |
string = field._coord_sys.symbols[field._index].name | |
return r'\partial_{{{}}}'.format(self._print(Symbol(string))) | |
def _print_Differential(self, diff): | |
field = diff._form_field | |
if hasattr(field, '_coord_sys'): | |
string = field._coord_sys.symbols[field._index].name | |
return r'\operatorname{{d}}{}'.format(self._print(Symbol(string))) | |
else: | |
string = self._print(field) | |
return r'\operatorname{{d}}\left({}\right)'.format(string) | |
def _print_Tr(self, p): | |
# TODO: Handle indices | |
contents = self._print(p.args[0]) | |
return r'\operatorname{{tr}}\left({}\right)'.format(contents) | |
def _print_totient(self, expr, exp=None): | |
if exp is not None: | |
return r'\left(\phi\left(%s\right)\right)^{%s}' % \ | |
(self._print(expr.args[0]), exp) | |
return r'\phi\left(%s\right)' % self._print(expr.args[0]) | |
def _print_reduced_totient(self, expr, exp=None): | |
if exp is not None: | |
return r'\left(\lambda\left(%s\right)\right)^{%s}' % \ | |
(self._print(expr.args[0]), exp) | |
return r'\lambda\left(%s\right)' % self._print(expr.args[0]) | |
def _print_divisor_sigma(self, expr, exp=None): | |
if len(expr.args) == 2: | |
tex = r"_%s\left(%s\right)" % tuple(map(self._print, | |
(expr.args[1], expr.args[0]))) | |
else: | |
tex = r"\left(%s\right)" % self._print(expr.args[0]) | |
if exp is not None: | |
return r"\sigma^{%s}%s" % (exp, tex) | |
return r"\sigma%s" % tex | |
def _print_udivisor_sigma(self, expr, exp=None): | |
if len(expr.args) == 2: | |
tex = r"_%s\left(%s\right)" % tuple(map(self._print, | |
(expr.args[1], expr.args[0]))) | |
else: | |
tex = r"\left(%s\right)" % self._print(expr.args[0]) | |
if exp is not None: | |
return r"\sigma^*^{%s}%s" % (exp, tex) | |
return r"\sigma^*%s" % tex | |
def _print_primenu(self, expr, exp=None): | |
if exp is not None: | |
return r'\left(\nu\left(%s\right)\right)^{%s}' % \ | |
(self._print(expr.args[0]), exp) | |
return r'\nu\left(%s\right)' % self._print(expr.args[0]) | |
def _print_primeomega(self, expr, exp=None): | |
if exp is not None: | |
return r'\left(\Omega\left(%s\right)\right)^{%s}' % \ | |
(self._print(expr.args[0]), exp) | |
return r'\Omega\left(%s\right)' % self._print(expr.args[0]) | |
def _print_Str(self, s): | |
return str(s.name) | |
def _print_float(self, expr): | |
return self._print(Float(expr)) | |
def _print_int(self, expr): | |
return str(expr) | |
def _print_mpz(self, expr): | |
return str(expr) | |
def _print_mpq(self, expr): | |
return str(expr) | |
def _print_fmpz(self, expr): | |
return str(expr) | |
def _print_fmpq(self, expr): | |
return str(expr) | |
def _print_Predicate(self, expr): | |
return r"\operatorname{{Q}}_{{\text{{{}}}}}".format(latex_escape(str(expr.name))) | |
def _print_AppliedPredicate(self, expr): | |
pred = expr.function | |
args = expr.arguments | |
pred_latex = self._print(pred) | |
args_latex = ', '.join([self._print(a) for a in args]) | |
return '%s(%s)' % (pred_latex, args_latex) | |
def emptyPrinter(self, expr): | |
# default to just printing as monospace, like would normally be shown | |
s = super().emptyPrinter(expr) | |
return r"\mathtt{\text{%s}}" % latex_escape(s) | |
def translate(s: str) -> str: | |
r''' | |
Check for a modifier ending the string. If present, convert the | |
modifier to latex and translate the rest recursively. | |
Given a description of a Greek letter or other special character, | |
return the appropriate latex. | |
Let everything else pass as given. | |
>>> from sympy.printing.latex import translate | |
>>> translate('alphahatdotprime') | |
"{\\dot{\\hat{\\alpha}}}'" | |
''' | |
# Process the rest | |
tex = tex_greek_dictionary.get(s) | |
if tex: | |
return tex | |
elif s.lower() in greek_letters_set: | |
return "\\" + s.lower() | |
elif s in other_symbols: | |
return "\\" + s | |
else: | |
# Process modifiers, if any, and recurse | |
for key in sorted(modifier_dict.keys(), key=len, reverse=True): | |
if s.lower().endswith(key) and len(s) > len(key): | |
return modifier_dict[key](translate(s[:-len(key)])) | |
return s | |
def latex(expr, **settings): | |
r"""Convert the given expression to LaTeX string representation. | |
Parameters | |
========== | |
full_prec: boolean, optional | |
If set to True, a floating point number is printed with full precision. | |
fold_frac_powers : boolean, optional | |
Emit ``^{p/q}`` instead of ``^{\frac{p}{q}}`` for fractional powers. | |
fold_func_brackets : boolean, optional | |
Fold function brackets where applicable. | |
fold_short_frac : boolean, optional | |
Emit ``p / q`` instead of ``\frac{p}{q}`` when the denominator is | |
simple enough (at most two terms and no powers). The default value is | |
``True`` for inline mode, ``False`` otherwise. | |
inv_trig_style : string, optional | |
How inverse trig functions should be displayed. Can be one of | |
``'abbreviated'``, ``'full'``, or ``'power'``. Defaults to | |
``'abbreviated'``. | |
itex : boolean, optional | |
Specifies if itex-specific syntax is used, including emitting | |
``$$...$$``. | |
ln_notation : boolean, optional | |
If set to ``True``, ``\ln`` is used instead of default ``\log``. | |
long_frac_ratio : float or None, optional | |
The allowed ratio of the width of the numerator to the width of the | |
denominator before the printer breaks off long fractions. If ``None`` | |
(the default value), long fractions are not broken up. | |
mat_delim : string, optional | |
The delimiter to wrap around matrices. Can be one of ``'['``, ``'('``, | |
or the empty string ``''``. Defaults to ``'['``. | |
mat_str : string, optional | |
Which matrix environment string to emit. ``'smallmatrix'``, | |
``'matrix'``, ``'array'``, etc. Defaults to ``'smallmatrix'`` for | |
inline mode, ``'matrix'`` for matrices of no more than 10 columns, and | |
``'array'`` otherwise. | |
mode: string, optional | |
Specifies how the generated code will be delimited. ``mode`` can be one | |
of ``'plain'``, ``'inline'``, ``'equation'`` or ``'equation*'``. If | |
``mode`` is set to ``'plain'``, then the resulting code will not be | |
delimited at all (this is the default). If ``mode`` is set to | |
``'inline'`` then inline LaTeX ``$...$`` will be used. If ``mode`` is | |
set to ``'equation'`` or ``'equation*'``, the resulting code will be | |
enclosed in the ``equation`` or ``equation*`` environment (remember to | |
import ``amsmath`` for ``equation*``), unless the ``itex`` option is | |
set. In the latter case, the ``$$...$$`` syntax is used. | |
mul_symbol : string or None, optional | |
The symbol to use for multiplication. Can be one of ``None``, | |
``'ldot'``, ``'dot'``, or ``'times'``. | |
order: string, optional | |
Any of the supported monomial orderings (currently ``'lex'``, | |
``'grlex'``, or ``'grevlex'``), ``'old'``, and ``'none'``. This | |
parameter does nothing for `~.Mul` objects. Setting order to ``'old'`` | |
uses the compatibility ordering for ``~.Add`` defined in Printer. For | |
very large expressions, set the ``order`` keyword to ``'none'`` if | |
speed is a concern. | |
symbol_names : dictionary of strings mapped to symbols, optional | |
Dictionary of symbols and the custom strings they should be emitted as. | |
root_notation : boolean, optional | |
If set to ``False``, exponents of the form 1/n are printed in fractonal | |
form. Default is ``True``, to print exponent in root form. | |
mat_symbol_style : string, optional | |
Can be either ``'plain'`` (default) or ``'bold'``. If set to | |
``'bold'``, 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 ``r`` or ``t`` in front gives ``\mathrm`` | |
or ``\text``, so ``'ri'`` leads to ``\mathrm{i}`` which gives | |
`\mathrm{i}`. | |
gothic_re_im : boolean, optional | |
If set to ``True``, `\Re` and `\Im` is used for ``re`` and ``im``, respectively. | |
The default is ``False`` leading to `\operatorname{re}` and `\operatorname{im}`. | |
decimal_separator : string, optional | |
Specifies what separator to use to separate the whole and fractional parts of a | |
floating point number as in `2.5` for the default, ``period`` or `2{,}5` | |
when ``comma`` is specified. Lists, sets, and tuple are printed with semicolon | |
separating the elements when ``comma`` is chosen. For example, [1; 2; 3] when | |
``comma`` is chosen and [1,2,3] for when ``period`` is chosen. | |
parenthesize_super : boolean, optional | |
If set to ``False``, superscripted expressions will not be parenthesized when | |
powered. Default is ``True``, which parenthesizes the expression when powered. | |
min: Integer or None, optional | |
Sets the lower bound for the exponent to print floating point numbers in | |
fixed-point format. | |
max: Integer or None, optional | |
Sets the upper bound for the exponent to print floating point numbers in | |
fixed-point format. | |
diff_operator: string, optional | |
String to use for differential operator. Default is ``'d'``, to print in italic | |
form. ``'rd'``, ``'td'`` are shortcuts for ``\mathrm{d}`` and ``\text{d}``. | |
adjoint_style: string, optional | |
String to use for the adjoint symbol. Defined options are ``'dagger'`` | |
(default),``'star'``, and ``'hermitian'``. | |
Notes | |
===== | |
Not using a print statement for printing, results in double backslashes for | |
latex commands since that's the way Python escapes backslashes in strings. | |
>>> from sympy import latex, Rational | |
>>> from sympy.abc import tau | |
>>> latex((2*tau)**Rational(7,2)) | |
'8 \\sqrt{2} \\tau^{\\frac{7}{2}}' | |
>>> print(latex((2*tau)**Rational(7,2))) | |
8 \sqrt{2} \tau^{\frac{7}{2}} | |
Examples | |
======== | |
>>> from sympy import latex, pi, sin, asin, Integral, Matrix, Rational, log | |
>>> from sympy.abc import x, y, mu, r, tau | |
Basic usage: | |
>>> print(latex((2*tau)**Rational(7,2))) | |
8 \sqrt{2} \tau^{\frac{7}{2}} | |
``mode`` and ``itex`` options: | |
>>> print(latex((2*mu)**Rational(7,2), mode='plain')) | |
8 \sqrt{2} \mu^{\frac{7}{2}} | |
>>> print(latex((2*tau)**Rational(7,2), mode='inline')) | |
$8 \sqrt{2} \tau^{7 / 2}$ | |
>>> print(latex((2*mu)**Rational(7,2), mode='equation*')) | |
\begin{equation*}8 \sqrt{2} \mu^{\frac{7}{2}}\end{equation*} | |
>>> print(latex((2*mu)**Rational(7,2), mode='equation')) | |
\begin{equation}8 \sqrt{2} \mu^{\frac{7}{2}}\end{equation} | |
>>> print(latex((2*mu)**Rational(7,2), mode='equation', itex=True)) | |
$$8 \sqrt{2} \mu^{\frac{7}{2}}$$ | |
>>> print(latex((2*mu)**Rational(7,2), mode='plain')) | |
8 \sqrt{2} \mu^{\frac{7}{2}} | |
>>> print(latex((2*tau)**Rational(7,2), mode='inline')) | |
$8 \sqrt{2} \tau^{7 / 2}$ | |
>>> print(latex((2*mu)**Rational(7,2), mode='equation*')) | |
\begin{equation*}8 \sqrt{2} \mu^{\frac{7}{2}}\end{equation*} | |
>>> print(latex((2*mu)**Rational(7,2), mode='equation')) | |
\begin{equation}8 \sqrt{2} \mu^{\frac{7}{2}}\end{equation} | |
>>> print(latex((2*mu)**Rational(7,2), mode='equation', itex=True)) | |
$$8 \sqrt{2} \mu^{\frac{7}{2}}$$ | |
Fraction options: | |
>>> print(latex((2*tau)**Rational(7,2), fold_frac_powers=True)) | |
8 \sqrt{2} \tau^{7/2} | |
>>> print(latex((2*tau)**sin(Rational(7,2)))) | |
\left(2 \tau\right)^{\sin{\left(\frac{7}{2} \right)}} | |
>>> print(latex((2*tau)**sin(Rational(7,2)), fold_func_brackets=True)) | |
\left(2 \tau\right)^{\sin {\frac{7}{2}}} | |
>>> print(latex(3*x**2/y)) | |
\frac{3 x^{2}}{y} | |
>>> print(latex(3*x**2/y, fold_short_frac=True)) | |
3 x^{2} / y | |
>>> print(latex(Integral(r, r)/2/pi, long_frac_ratio=2)) | |
\frac{\int r\, dr}{2 \pi} | |
>>> print(latex(Integral(r, r)/2/pi, long_frac_ratio=0)) | |
\frac{1}{2 \pi} \int r\, dr | |
Multiplication options: | |
>>> print(latex((2*tau)**sin(Rational(7,2)), mul_symbol="times")) | |
\left(2 \times \tau\right)^{\sin{\left(\frac{7}{2} \right)}} | |
Trig options: | |
>>> print(latex(asin(Rational(7,2)))) | |
\operatorname{asin}{\left(\frac{7}{2} \right)} | |
>>> print(latex(asin(Rational(7,2)), inv_trig_style="full")) | |
\arcsin{\left(\frac{7}{2} \right)} | |
>>> print(latex(asin(Rational(7,2)), inv_trig_style="power")) | |
\sin^{-1}{\left(\frac{7}{2} \right)} | |
Matrix options: | |
>>> print(latex(Matrix(2, 1, [x, y]))) | |
\left[\begin{matrix}x\\y\end{matrix}\right] | |
>>> print(latex(Matrix(2, 1, [x, y]), mat_str = "array")) | |
\left[\begin{array}{c}x\\y\end{array}\right] | |
>>> print(latex(Matrix(2, 1, [x, y]), mat_delim="(")) | |
\left(\begin{matrix}x\\y\end{matrix}\right) | |
Custom printing of symbols: | |
>>> print(latex(x**2, symbol_names={x: 'x_i'})) | |
x_i^{2} | |
Logarithms: | |
>>> print(latex(log(10))) | |
\log{\left(10 \right)} | |
>>> print(latex(log(10), ln_notation=True)) | |
\ln{\left(10 \right)} | |
``latex()`` also supports the builtin container types :class:`list`, | |
:class:`tuple`, and :class:`dict`: | |
>>> print(latex([2/x, y], mode='inline')) | |
$\left[ 2 / x, \ y\right]$ | |
Unsupported types are rendered as monospaced plaintext: | |
>>> print(latex(int)) | |
\mathtt{\text{<class 'int'>}} | |
>>> print(latex("plain % text")) | |
\mathtt{\text{plain \% text}} | |
See :ref:`printer_method_example` for an example of how to override | |
this behavior for your own types by implementing ``_latex``. | |
.. versionchanged:: 1.7.0 | |
Unsupported types no longer have their ``str`` representation treated as valid latex. | |
""" | |
return LatexPrinter(settings).doprint(expr) | |
def print_latex(expr, **settings): | |
"""Prints LaTeX representation of the given expression. Takes the same | |
settings as ``latex()``.""" | |
print(latex(expr, **settings)) | |
def multiline_latex(lhs, rhs, terms_per_line=1, environment="align*", use_dots=False, **settings): | |
r""" | |
This function generates a LaTeX equation with a multiline right-hand side | |
in an ``align*``, ``eqnarray`` or ``IEEEeqnarray`` environment. | |
Parameters | |
========== | |
lhs : Expr | |
Left-hand side of equation | |
rhs : Expr | |
Right-hand side of equation | |
terms_per_line : integer, optional | |
Number of terms per line to print. Default is 1. | |
environment : "string", optional | |
Which LaTeX wnvironment to use for the output. Options are "align*" | |
(default), "eqnarray", and "IEEEeqnarray". | |
use_dots : boolean, optional | |
If ``True``, ``\\dots`` is added to the end of each line. Default is ``False``. | |
Examples | |
======== | |
>>> from sympy import multiline_latex, symbols, sin, cos, exp, log, I | |
>>> x, y, alpha = symbols('x y alpha') | |
>>> expr = sin(alpha*y) + exp(I*alpha) - cos(log(y)) | |
>>> print(multiline_latex(x, expr)) | |
\begin{align*} | |
x = & e^{i \alpha} \\ | |
& + \sin{\left(\alpha y \right)} \\ | |
& - \cos{\left(\log{\left(y \right)} \right)} | |
\end{align*} | |
Using at most two terms per line: | |
>>> print(multiline_latex(x, expr, 2)) | |
\begin{align*} | |
x = & e^{i \alpha} + \sin{\left(\alpha y \right)} \\ | |
& - \cos{\left(\log{\left(y \right)} \right)} | |
\end{align*} | |
Using ``eqnarray`` and dots: | |
>>> print(multiline_latex(x, expr, terms_per_line=2, environment="eqnarray", use_dots=True)) | |
\begin{eqnarray} | |
x & = & e^{i \alpha} + \sin{\left(\alpha y \right)} \dots\nonumber\\ | |
& & - \cos{\left(\log{\left(y \right)} \right)} | |
\end{eqnarray} | |
Using ``IEEEeqnarray``: | |
>>> print(multiline_latex(x, expr, environment="IEEEeqnarray")) | |
\begin{IEEEeqnarray}{rCl} | |
x & = & e^{i \alpha} \nonumber\\ | |
& & + \sin{\left(\alpha y \right)} \nonumber\\ | |
& & - \cos{\left(\log{\left(y \right)} \right)} | |
\end{IEEEeqnarray} | |
Notes | |
===== | |
All optional parameters from ``latex`` can also be used. | |
""" | |
# Based on code from https://github.com/sympy/sympy/issues/3001 | |
l = LatexPrinter(**settings) | |
if environment == "eqnarray": | |
result = r'\begin{eqnarray}' + '\n' | |
first_term = '& = &' | |
nonumber = r'\nonumber' | |
end_term = '\n\\end{eqnarray}' | |
doubleet = True | |
elif environment == "IEEEeqnarray": | |
result = r'\begin{IEEEeqnarray}{rCl}' + '\n' | |
first_term = '& = &' | |
nonumber = r'\nonumber' | |
end_term = '\n\\end{IEEEeqnarray}' | |
doubleet = True | |
elif environment == "align*": | |
result = r'\begin{align*}' + '\n' | |
first_term = '= &' | |
nonumber = '' | |
end_term = '\n\\end{align*}' | |
doubleet = False | |
else: | |
raise ValueError("Unknown environment: {}".format(environment)) | |
dots = '' | |
if use_dots: | |
dots=r'\dots' | |
terms = rhs.as_ordered_terms() | |
n_terms = len(terms) | |
term_count = 1 | |
for i in range(n_terms): | |
term = terms[i] | |
term_start = '' | |
term_end = '' | |
sign = '+' | |
if term_count > terms_per_line: | |
if doubleet: | |
term_start = '& & ' | |
else: | |
term_start = '& ' | |
term_count = 1 | |
if term_count == terms_per_line: | |
# End of line | |
if i < n_terms-1: | |
# There are terms remaining | |
term_end = dots + nonumber + r'\\' + '\n' | |
else: | |
term_end = '' | |
if term.as_ordered_factors()[0] == -1: | |
term = -1*term | |
sign = r'-' | |
if i == 0: # beginning | |
if sign == '+': | |
sign = '' | |
result += r'{:s} {:s}{:s} {:s} {:s}'.format(l.doprint(lhs), | |
first_term, sign, l.doprint(term), term_end) | |
else: | |
result += r'{:s}{:s} {:s} {:s}'.format(term_start, sign, | |
l.doprint(term), term_end) | |
term_count += 1 | |
result += end_term | |
return result | |