Преобразование числового выражения Python в LaTeX - PullRequest
24 голосов
/ 05 октября 2010

Мне нужно преобразовать строки с правильным синтаксисом 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'))))))))]))

И последнее, но не менее важное: почему мне нужно сгенерировать эти латексные фрагменты, чтобы я мог показать их на веб-странице с mathjax.

Ответы [ 5 ]

16 голосов
/ 06 октября 2010

Вот довольно длинный, но все еще неполный метод, который ни в коем случае не подразумевает симпатию.Достаточно покрыть пример (-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)

16 голосов
/ 06 октября 2010

Вы можете использовать sympy.latex с eval:

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

Вам по-прежнему нужно объявлять переменные как символы, но если это действительно проблема, гораздо проще написать парсер, чтобы это сделать, чем анализировать все и генерировать латекс с нуля.

10 голосов
/ 30 ноября 2010

Вы можете использовать SymPy.Просто передайте строку сначала функции sympify(), которая преобразует ее в допустимое выражение SymPy (т. Е. Создает символы для вас и т. Д.).Таким образом, вы могли бы сделать

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

S() также ярлык для sympify(), то есть latex(S('1+2**(x+y)')) также работает.

4 голосов
/ 06 октября 2010

Небольшое исправление Джеффа Риди, отличный ответ:

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}$'
3 голосов
/ 06 октября 2010

Чтобы построить ответ 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()))

вам не нужно беспокоиться о предварительном создании символов для переменных.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...