将 Python 数值表达式转换为 LaTeX

发布于 2024-09-25 21:34:48 字数 798 浏览 4 评论 0原文

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

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

并获得等效的LaTeX:

$1+2^{x+y}$

我已经尝试过SymPy的latex函数,但它处理实际的表达式,而不是它的字符串形式:

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

但甚至这样做,它需要将 xy 声明为类型“symbols”。

我想要更简单的东西,最好可以使用编译器模块中的解析器来实现。

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

最后但并非最不重要的一点是,为什么:我需要生成这些 LaTeX 片段,以便我可以使用 MathJax 在网页中显示它们。

I need to convert strings with valid Python syntax such as:

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

and get the equivalent LaTeX:

$1+2^{x+y}$

I have tried SymPy's latex function but it processes actual expression, rather than the string form of it:

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

but to even do this, it requires x and y to be declared as type "symbols".

I want something more straightforward, preferably doable with the parser from the compiler module.

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

Last but not least, the why: I need to generate those LaTeX snippets so that I can show them in a webpage with MathJax.

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

but to even do this, it requires x and y to be declared as type "symbols".

I want something more straightforward, preferably doable with the parser from the compiler module.


Last but not least, the why: I need to generate those LaTeX snippets so that I can show them in a webpage with MathJax.

but to even do this, it requires x and y to be declared as type "symbols".

I want something more straightforward, preferably doable with the parser from the compiler module.

Last but not least, the why: I need to generate those LaTeX snippets so that I can show them in a webpage with MathJax.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

墨落成白 2024-10-02 21:34:48

这是一个相当长但仍然不完整的方法,不以任何方式涉及 sympy。这足以涵盖 (-b-sqrt(b**2-4*a*c))/(2*a) 的示例,该示例被翻译为 \frac{- b - \sqrt{b^{2} - 4 \;一个\; 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)

Here's a rather long but still incomplete method that doesn't involve sympy in any way. It's enough to cover the example of (-b-sqrt(b**2-4*a*c))/(2*a) which gets translated to \frac{- b - \sqrt{b^{2} - 4 \; a \; c}}{2 \; a} and renders as

alt text

It basically creates the AST and walks it producing the latex math the corresponds to the AST nodes. What's there should give enough of an idea how to extend it in the places it's lacking.


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)

你是我的挚爱i 2024-10-02 21:34:48

您可以将 sympy.latexeval 一起使用:

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

您仍然必须将变量声明为符号,但如果这确实是一个问题,那么编写解析器会更容易这比解析所有内容并从头开始生成乳胶要好。

您仍然必须将变量声明为符号,但如果这确实是一个问题,那么编写解析器会更容易这比解析所有内容并从头开始生成乳胶要好。

You can use sympy.latex with eval:

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

You still have to declare the variables as symbols, but if this is really a problem, it's much easier to write a parser to do this than to parse everything and generate the latex from scratch.

You still have to declare the variables as symbols, but if this is really a problem, it's much easier to write a parser to do this than to parse everything and generate the latex from scratch.

青芜 2024-10-02 21:34:48

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

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

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

You can use SymPy. Just pass the string to the sympify() function first, which will convert it to a valid SymPy expression (i.e., create the Symbols for you, etc.). So you could do

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

S() is also a shortcut to sympify(), i.e., latex(S('1+2**(x+y)')) also works.

暖阳 2024-10-02 21:34:48

为了构建 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()))

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

To build on tom10's answer you can define a dictionary that will generate symbols and use that when calling eval:

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

Then if you use

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

you shouldn't have to worry about pre-creating Symbols for the variables.

鲜血染红嫁衣 2024-10-02 21:34:48

只需对杰夫·里迪(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}

Just a little fix to Geoff Reedy excellent answer:

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

Before it would not add the new item to the dict. Now you can use this with your expression:

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

and you can further simplify it before converting it to LaTeX:

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

and you get this:

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

万人眼中万个我 2024-10-02 21:34:48

渲染 @Geoff Reedy Latex 格式文本。我们可以使用 matplotlib。

exp = py2tex('1**x+y')
import matplotlib.pyplot as plt
text = exp

plt.text(0.36, 0.5, r"$%s$" %text, fontsize=24)
ax = plt.gca()
ax.axes.get_xaxis().set_visible(False)
ax.axes.get_yaxis().set_visible(False)

plt.show()

输入图片此处描述

To render @Geoff Reedy latex format text. We could use matplotlib.

exp = py2tex('1**x+y')
import matplotlib.pyplot as plt
text = exp

plt.text(0.36, 0.5, r"$%s
quot; %text, fontsize=24)
ax = plt.gca()
ax.axes.get_xaxis().set_visible(False)
ax.axes.get_yaxis().set_visible(False)

plt.show()

enter image description here

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文