# [IPython-User] tex magic: parsing calculations to tex

Johan Beke johanbeke@hotmail....
Sat Aug 25 04:51:24 CDT 2012

Hi all,

As I said before, I would like to have numerical input formulas parsed to tex. I've written a simple magic function 'tex' which does the trick for numerical expressions. For the moment it needs to be without assignment.

The code:

import ast
from IPython.core.display import Latex
from IPython.core.magic import (Magics, magics_class, line_magic,
cell_magic, line_cell_magic)

@magics_class
class PrettyPrint(Magics):
@line_magic
def tex(self, line):
#stmt = compile(line,'<string>','exec')
#exec stmt
result = eval(line)
return Latex("$$"+self.py2tex(line)+" = "+str(result)+"$$")

def py2tex(self, expr):
pt = ast.parse(expr)
return LatexVisitor().visit(pt.body[0].value)

class LatexVisitor(ast.NodeVisitor):
# source: http://stackoverflow.com/questions/3867028/converting-a-python-numeric-expression-to-latex
greekLetters = {'Alpha': '\\Alpha',
'Beta': '\\Beta',
'Chi': '\\Chi',
'Delta': '\\Delta',
'Epsilon': '\\Epsilon',
'Eta': '\\Eta',
'Gamma': '\\Gamma',
'Iota': '\\Iota',
'Kappa': '\\Kappa',
'Lambda': '\\Lambda',
'Mu': '\\Mu',
'Nu': '\\Nu',
'Omega': '\\Omega',
'Phi': '\\Phi',
'Pi': '\\Pi',
'Psi': '\\Psi',
'Rho': '\\Rho',
'Sigma': '\\Sigma',
'Tau': '\\Tau',
'Theta': '\\Theta',
'Upsilon': '\\Upsilon',
'Xi': '\\Xi',
'Zeta': '\\Zeta',
'alpha': '\\alpha',
'beta': '\\beta',
'chi': '\\chi',
'delta': '\\delta',
'epsilon': '\\epsilon',
'eta': '\\eta',
'gamma': '\\gamma',
'i': '\\varphi',
'iota': '\\iota',
'kappa': '\\kappa',
'lambda': '\\lambda',
'mu': '\\mu',
'nu': '\\nu',
'omega': '\\omega',
'phi': '\\phi',
'pi': '\\pi',
'psi': '\\psi',
'rho': '\\rho',
'sigma': '\\sigma',
'tau': '\\tau',
'theta': '\\theta',
'upsilon': '\\upsilon',
'varepsilon': '\\varepsilon',
'varkappa': '\\varkappa',
'varphi': '\\varphi',
'varpi': '\\varpi',
'varrho': '\\varrho',
'varsigma': '\\varsigma',
'vartheta': '\\vartheta',
'xi': '\\xi',
'zeta': '\\zeta'}
functions = {'arccos': '\\arccos',
'arcsin': '\\arcsin',
'arctan': '\\arctan',
'cos': '\\cos',
'cosh': '\\cosh',
'cot': '\\cot',
'coth': '\\coth',
'csc': '\\csc',
'ln': '\\ln',
'log': '\\log',
'max': '\\max',
'min': '\\min',
'sec': '\\sec',
'sin': '\\sin',
'sinh': '\\sinh',
'tan': '\\tan',
'tanh': '\\tanh'}
def prec(self, n):
return getattr(self, 'prec_'+n.__class__.__name__, getattr(self, 'generic_prec'))(n)

def visit_Call(self, n):
func = self.visit(n.func)
args = ', '.join(map(self.visit, n.args))
if func == 'sqrt':
return '\sqrt{%s}' % args
else:
# parse know LaTeX functions
if self.functions.has_key(func):
return r'%s\left(%s\right)' % (self.functions[func], args)
else:
return r'\mbox{%s}\left(%s\right)' % (func, args)

def prec_Call(self, n):
return 1000

def visit_Name(self, n):
#parse greek letters
name = n.id.split("_")
for i in range(len(name)):
if self.greekLetters.has_key(name[i]):
name[i] = self.greekLetters[name[i]]

#parse underscore and comma
if len(name)>1:
return name[0]+"_{"+','.join(name[1:])+"}"
return name[0]

def prec_Name(self, n):
return 1000

def visit_UnaryOp(self, n):
if self.prec(n.op) > self.prec(n.operand):
return r'%s \left(%s\right)' % (self.visit(n.op), self.visit(n.operand))
else:
return r'%s %s' % (self.visit(n.op), self.visit(n.operand))

def prec_UnaryOp(self, n):
return self.prec(n.op)

def visit_BinOp(self, n):
if self.prec(n.op) > self.prec(n.left):
left = r'\left(%s\right)' % self.visit(n.left)
else:
left = self.visit(n.left)
if self.prec(n.op) > self.prec(n.right):
right = r'\left(%s\right)' % self.visit(n.right)
else:
right = self.visit(n.right)
if isinstance(n.op, ast.Div):
return r'\frac{%s}{%s}' % (self.visit(n.left), self.visit(n.right))
elif isinstance(n.op, ast.FloorDiv):
return r'\left\lfloor\frac{%s}{%s}\right\rfloor' % (self.visit(n.left), self.visit(n.right))
elif isinstance(n.op, ast.Pow):
return r'%s^{%s}' % (left, self.visit(n.right))
else:
return r'%s %s %s' % (left, self.visit(n.op), right)

def prec_BinOp(self, n):
return self.prec(n.op)

def visit_Sub(self, n):
return '-'

def prec_Sub(self, n):
return 300

return '+'

return 300

def visit_Mult(self, n):
return '\\;'

def prec_Mult(self, n):
return 400

def visit_Mod(self, n):
return '\\bmod'

def prec_Mod(self, n):
return 500

def prec_Pow(self, n):
return 700

def prec_Div(self, n):
return 400

def prec_FloorDiv(self, n):

return 400

def visit_LShift(self, n):
return '\\mbox{shiftLeft}'

def visit_RShift(self, n):
return '\\mbox{shiftRight}'

def visit_BitOr(self, n):
return '\\mbox{or}'

def visit_BitXor(self, n):
return '\\mbox{xor}'

def visit_BitAnd(self, n):
return '\\mbox{and}'

def visit_Invert(self, n):
return '\\mbox{invert}'

def prec_Invert(self, n):
return 800

def visit_Not(self, n):
return '\\neg'

def prec_Not(self, n):
return 800

return '+'

return 800

def visit_USub(self, n):
return '-'

def prec_USub(self, n):
return 800
def visit_Num(self, n):
return str(n.n)

def prec_Num(self, n):
return 1000

def generic_visit(self, n):
#walk ???
if isinstance(n, ast.AST):
return r'' % (n.__class__.__name__, ', '.join(map(self.visit, [getattr(n, f) for f in n._fields])))
else:
return str(n)

def generic_prec(self, n):
return 0

get_ipython().register_magics(PrettyPrint)

I've modified some code I found on stackoverflow. I don't know if it can be legally included in the ipython distribution. However, I can have a workaround with regex and string functions. Had some code but it is more complicated than this.

identifiers for variables are parsed a bit different. The first occurrence of _ is the real LaTeX subscript. Next occurrences will give a comma.