将python数值表达式转换为LaTeX

时间:2010-10-05 19:33:07

标签: python math latex

我需要使用有效的python语法转换字符串,例如:

'1+2**(x+y)'

并获得等效的LaTeX:

$1+2^{x+y}$

我尝试过sympy的乳胶功能,但它会处理实际的表达,而不是它的字符串形式:

>>> latex(1+2**(x+y))
'$1 + 2^{x + y}$'
>>> latex('1+2**(x+y)')
'$1+2**(x+y)$'

但是要做到这一点,它需要将x和y声明为“符号”类型。

我想要更直接的东西,最好是使用编译器模块中的解析器。

>>> compiler.parse('1+2**(x+y)')
Module(None, Stmt([Discard(Add((Const(1), Power((Const(2), Add((Name('x'), Name('y'))))))))]))

最后但同样重要的是,为什么:我需要生成那些乳胶snipptes,以便我可以在带有mathjax的网页中显示它们。

5 个答案:

答案 0 :(得分:16)

这是一个相当长但仍然不完整的方法,不涉及任何方式的同情。这足以涵盖(-b-sqrt(b**2-4*a*c))/(2*a)的示例,该示例将转换为\frac{- b - \sqrt{b^{2} - 4 \; a \; c}}{2 \; a}并呈现为

alt text

它基本上创建了AST并且它产生了与AST节点对应的乳胶数学。什么是应该给出足够的想法如何在它缺乏的地方扩展它。


import ast

class LatexVisitor(ast.NodeVisitor):

    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:
            return r'\operatorname{%s}\left(%s\right)' % (func, args)

    def prec_Call(self, n):
        return 1000

    def visit_Name(self, n):
        return n.id

    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

    def visit_Add(self, n):
        return '+'

    def prec_Add(self, n):
        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 '\\operatorname{shiftLeft}'

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

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

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

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

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

    def prec_Invert(self, n):
        return 800

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

    def prec_Not(self, n):
        return 800

    def visit_UAdd(self, n):
        return '+'

    def prec_UAdd(self, n):
        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):
        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

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

答案 1 :(得分:15)

您可以将sympy.latexeval

一起使用
s = "1+2**(x+y)"
sympy.latex(eval(s))   # prints '$1 + {2}^{x + y}$'

你仍然需要将变量声明为符号,但是如果这确实是一个问题,那么编写解析器来解决所有问题比从头开始生成乳胶要容易得多。

答案 2 :(得分:10)

您可以使用SymPy。只需将字符串首先传递给sympify()函数,然后将其转换为有效的SymPy表达式(即为您创建符号等)。所以你可以做到

>>> latex(sympify('1+2**(x+y)'))
1 + 2^{x + y}

S()也是sympify()的快捷方式,即latex(S('1+2**(x+y)'))也有效。

答案 3 :(得分:4)

对Geoff Reedy的一个很好的解决方法:

class GenerateSymbols(defaultdict):
    def __missing__(self, key):
        self[key] = sympy.Symbol(key)
        return self[key]

之前它不会将新项目添加到词典中。现在,您可以将其与表达式一起使用:

d= GenerateSymbols()    
eq = '(-b-sqrt(b**2-4*a*c))/(2*a)'

您可以在将其转换为LaTeX之前进一步简化它:

sympy.latex(sympy.simplify(eval(eq,d)))

你得到了这个:

'$- \\frac{b + \\operatorname{sqrt}\\left(- 4 a c + b^{2}\\right)}{2 a}$'

答案 4 :(得分:3)

要建立在tom10的答案上,您可以定义一个生成符号的字典,并在调用eval时使用它:

from collections import defaultdict
class GenerateSymbols(defaultdict):
  def __missing__(self, key):
    return sympy.Symbol(key)

然后,如果你使用

sympy.latex(eval('1+2**(x+y)',GenerateSymbols()))

您不必担心为变量预先创建符号。