Source code for logic1.theories.Complex.format
"""String and LaTeX formatters for complex ASTs.
"""
from typing import ClassVar
from gmpy2 import mpz
from logic1.theories.Complex.ast import (
Add, AST, ASTVisitor, Conj, _I, Im, Mul, Neg, Pow, Re, Var, Rat)
[docs]
class ReprFormatter(ASTVisitor[str]):
"""Formatter for AST nodes that produces a more human-readable string
representation that is valid Python code and allows for the
reconstruction of the original expression.
>>> from logic1.theories.Complex.ast import *
>>> z = Var('z')
>>> (z**3 + 2 * I).accept(ReprFormatter())
'z**3 + 2 * I'
"""
symbols: ClassVar[dict[type[AST], str]] = {}
"""Mapping of AST node types to their corresponding symbols used in the
string representation. This mapping can be overridden in subclasses to
customize the symbols.
"""
def _omit_mul_symbol(self, ast1: AST, ast2: AST) -> bool:
"""Return :obj:`True` if the multiplication symbol should be omitted
between the two given AST nodes. This method can be overridden in
subclasses to customize the behavior.
>>> from logic1.theories.Complex.ast import *
>>> z = Var('z')
>>> ReprFormatter()._omit_mul_symbol(z, z + 1)
False
"""
return False
[docs]
def visit_rat(self, num: Rat) -> str:
"""Return the string representation of a rational number.
>>> from logic1.theories.Complex.ast import *
>>> ReprFormatter().visit_rat(Rat(mpq(3, 4)))
'3/4'
"""
return str(num.value)
[docs]
def visit_i(self, _: _I) -> str:
"""Return the string representation of the imaginary unit.
>>> from logic1.theories.Complex.ast import *
>>> ReprFormatter().visit_i(I)
'I'
"""
return self.symbols.get(_I, 'I')
[docs]
def visit_var(self, var: Var) -> str:
"""Return the string representation of a variable.
>>> from logic1.theories.Complex.ast import *
>>> z = Var('z')
>>> ReprFormatter().visit_var(z)
'z'
"""
return var.name
[docs]
def visit_add(self, add: Add) -> str:
"""Return the string representation of an addition.
>>> from logic1.theories.Complex.ast import *
>>> z = Var('z')
>>> ReprFormatter().visit_add(z + 1 - I)
'z + 1 - I'
"""
symbol_plus = self.symbols.get(Add, '+')
symbol_minus = self.symbols.get(Neg, '-')
result = []
for i, arg in enumerate(add.args):
if i > 0:
if isinstance(arg, Neg):
result.append(symbol_minus)
arg = arg.arg
else:
result.append(symbol_plus)
if i > 0 and isinstance(arg, Neg):
result.append(f'({arg.accept(self)})')
else:
result.append(arg.accept(self))
return " ".join(result)
[docs]
def visit_mul(self, mul: Mul) -> str:
"""Return the string representation of a multiplication.
>>> from logic1.theories.Complex.ast import *
>>> z = Var('z')
>>> ReprFormatter().visit_mul(z * (z + 1))
'z * (z + 1)'
"""
symbol = self.symbols.get(Mul, '*')
factors = []
result = []
for i, arg in enumerate(mul.args):
factors.append(arg)
if len(factors) > 1 and not self._omit_mul_symbol(factors[-2], factors[-1]):
result.append(symbol)
if isinstance(arg, Add) or (i > 0 and isinstance(arg, Neg)):
result.append(f'({arg.accept(self)})')
else:
result.append(arg.accept(self))
return f" ".join(result)
[docs]
def visit_pow(self, pow: Pow) -> str:
"""Return the string representation of a power.
>>> from logic1.theories.Complex.ast import *
>>> z = Var('z')
>>> ReprFormatter().visit_pow(z**2)
'z**2'
"""
symbol = self.symbols.get(Pow, '**')
if isinstance(pow.base, (Add, Mul, Neg, Pow, Conj)):
return f'({pow.base.accept(self)}){symbol}{pow.exponent}'
return f'{pow.base.accept(self)}{symbol}{pow.exponent}'
[docs]
def visit_neg(self, neg: Neg) -> str:
"""Return the string representation of a negation.
>>> from logic1.theories.Complex.ast import *
>>> z = Var('z')
>>> ReprFormatter().visit_neg(-z)
'-z'
"""
symbol = self.symbols.get(Neg, '-')
if isinstance(neg.arg, (Add, Mul)):
return f'{symbol}({neg.arg.accept(self)})'
return f'{symbol}{neg.arg.accept(self)}'
[docs]
def visit_conj(self, conj: Conj) -> str:
"""Return the string representation of a conjugation.
>>> from logic1.theories.Complex.ast import *
>>> z = Var('z')
>>> ReprFormatter().visit_conj(~z)
'~z'
"""
symbol = self.symbols.get(Conj, '~')
if isinstance(conj.arg, (Add, Mul)):
return f'{symbol}({conj.arg.accept(self)})'
return f'{symbol}{conj.arg.accept(self)}'
[docs]
def visit_re(self, re: Re) -> str:
"""Return the string representation of a real part.
>>> from logic1.theories.Complex.ast import *
>>> z = Var('z')
>>> ReprFormatter().visit_re(Re(z))
'Re(z)'
"""
symbol = self.symbols.get(Re, 'Re')
return f'{symbol}({re.arg.accept(self)})'
[docs]
def visit_im(self, im: Im) -> str:
"""Return the string representation of an imaginary part.
>>> from logic1.theories.Complex.ast import *
>>> z = Var('z')
>>> ReprFormatter().visit_im(Im(z))
'Im(z)'
"""
symbol = self.symbols.get(Im, 'Im')
return f'{symbol}({im.arg.accept(self)})'
[docs]
class StrFormatter(ReprFormatter):
"""Formatter for AST nodes that produces a more human-readable string
representation but does not necessarily allow the reconstruction
of the original expression.
>>> from logic1.theories.Complex.ast import *
>>> z = Var('z')
>>> (z**3 + 2 * I).accept(StrFormatter())
'z^3 + 2 * i'
"""
symbols = {
_I: 'i',
Pow: '^'
}
"""Custom mapping of AST node types to their corresponding symbols."""
[docs]
class LatexFormatter(ReprFormatter):
"""Formatter for AST nodes that produces a LaTeX representation.
>>> from logic1.theories.Complex.ast import *
>>> z = Var('z')
>>> (z**3 + 2 * I).accept(LatexFormatter())
'z^{3} + 2 i'
"""
symbols = {
_I: 'i',
Mul: '\\cdot',
Re: '\\Re',
Im: '\\Im',
}
"""Custom mapping of AST node types to their corresponding symbols."""
def _omit_mul_symbol(self, ast1: AST, ast2: AST) -> bool:
"""Return :obj:`True` if the multiplication symbol should be omitted
between the two given AST nodes.
>>> from logic1.theories.Complex.ast import *
>>> z = Var('z')
>>> LatexFormatter()._omit_mul_symbol(z, z + 1)
False
>>> LatexFormatter()._omit_mul_symbol(Rat(mpq(2)), I)
True
"""
while isinstance(ast1, (Conj, Pow)):
if isinstance(ast1, Conj):
ast1 = ast1.arg
else:
ast1 = ast1.base
while isinstance(ast2, (Conj, Pow)):
if isinstance(ast2, Conj):
ast2 = ast2.arg
else:
ast2 = ast2.base
if isinstance(ast1, (Var, Im, Re)) and isinstance(ast2, (Var, Im, Re)):
return True
if isinstance(ast1, Rat) and isinstance(ast2, _I):
return True
return False
[docs]
def visit_rat(self, num: Rat) -> str:
"""Return the LaTeX representation of a rational number as integer or
fraction.
>>> from logic1.theories.Complex.ast import *
>>> LatexFormatter().visit_rat(Rat(mpq(2)))
'2'
>>> LatexFormatter().visit_rat(Rat(mpq(3, 4)))
'\\\\frac{3}{4}'
"""
a = num.value.numerator
b = num.value.denominator
if a == mpz(0) or b == mpz(1):
return str(a)
else:
return f'\\frac{{{str(a)}}}{{{str(b)}}}'
[docs]
def visit_var(self, var: Var) -> str:
"""Return the LaTeX representation of a variable.
>>> from logic1.theories.Complex.ast import *
>>> LatexFormatter().visit_var(Var('z'))
'z'
>>> LatexFormatter().visit_var(Var('z1'))
'z_{1}'
>>> LatexFormatter().visit_var(Var('z_re'))
'z_{re}'
"""
def format_name(name: str) -> str:
return name if len(name) == 1 else f'\\mathrm{{{name}}}'
if "_" in var.name:
base, *indices = var.name.split("_")
grouped = "_".join(f'{{{idx}}}' for idx in indices)
return f'{format_name(base)}_{grouped}'
else:
base = var.name.rstrip('0123456789')
index = var.name[len(base):]
if index:
return f'{format_name(base)}_{{{str(index)}}}'
else:
return format_name(base)
[docs]
def visit_conj(self, conj: Conj) -> str:
"""Return the LaTeX representation of a conjugation.
>>> from logic1.theories.Complex.ast import *
>>> z = Var('z')
>>> LatexFormatter().visit_conj(~z)
'\\\\overline{z}'
"""
return f'\\overline{{{conj.arg.accept(self)}}}'
[docs]
def visit_pow(self, pow: Pow) -> str:
"""Return the LaTeX representation of a power.
>>> from logic1.theories.Complex.ast import *
>>> z = Var('z')
>>> LatexFormatter().visit_pow(z**2)
'z^{2}'
"""
if isinstance(pow.base, (Add, Mul, Neg, Pow)):
return f'({pow.base.accept(self)})^{{{pow.exponent}}}'
return f'{pow.base.accept(self)}^{{{pow.exponent}}}'