Conversion d'une expression numérique python en LaTeX

j'ai besoin de convertir des chaînes avec une syntaxe python valide telle que:

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

et obtenez l'équivalent de LaTeX:

+2^{x+y}$

j'ai essayé la fonction latex de sympy mais elle traite l'expression réelle, plutôt que la forme string de celle-ci:

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

mais pour ce faire, il faut que x et y soient déclarés comme des "symboles".

je veux quelque chose de plus simple, de préférence faisable avec l'analyseur du compilateur module.

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

enfin et surtout, le pourquoi: je dois générer ces extraits de latex pour pouvoir les afficher sur une page Web avec mathjax.

24
demandé sur fccoelho 2010-10-05 23:33:07

5 réponses

Vous pouvez utiliser sympy.latexeval:

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

vous devez toujours déclarer les variables comme des symboles, mais si c'est vraiment un problème, il est beaucoup plus facile d'écrire un analyseur pour faire cela que de tout analyser et de générer le latex à partir de zéro.

13
répondu tom10 2010-10-05 22:04:18

Voici une méthode assez longue mais encore incomplète qui n'implique pas sympy en aucune façon. C'est assez pour couvrir l'exemple de (-b-sqrt(b**2-4*a*c))/(2*a) qui est traduit en \frac{- b - \sqrt{b^{2} - 4 \; a \; c}}{2 \; a} et rend comme

alt text

il crée essentiellement le AST et le parcourt en produisant le latex math qui correspond aux noeuds AST. Ce qui est là devrait donner assez d'idée de la façon de l'étendre dans les endroits où il manque.


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)

16
répondu Geoff Reedy 2010-10-06 21:58:30

vous pouvez utiliser SymPy. Il suffit de passer la chaîne à l' sympify() fonction d'abord, qui la convertira en une expression SymPy valide (c'est-à-dire créer les symboles pour vous, etc.). Donc, vous pourriez faire

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

S() est aussi un raccourci pour sympify(), i.e.,latex(S('1+2**(x+y)')) fonctionne également.

9
répondu asmeurer 2010-11-29 21:45:55

Juste un petit correctif à Geoff Reedy excellente réponse:

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

avant de ne pas ajouter le nouvel article au dict. Maintenant, vous pouvez l'utiliser avec votre expression:

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

et vous pouvez simplifier un peu avant de le convertir au LaTeX:

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

et vous obtenez ceci:

'$- \frac{b + \operatorname{sqrt}\left(- 4 a c + b^{2}\right)}{2 a}$'
4
répondu fccoelho 2010-10-06 11:16:13

pour construire sur la réponse de tom10 vous pouvez définir un dictionnaire qui générera des symboles et l'utiliser lors de l'appel eval:

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

alors si vous utilisez

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

vous ne devriez pas avoir à vous soucier de pré-créer des symboles pour les variables.

3
répondu Geoff Reedy 2010-10-05 22:56:24