将在NumPy中定义的函数转换为SymPy

3

我在numpy中定义了一个函数,现在我想将它转换成sympy格式,以便可以应用于符号sympy变量。尝试直接将numpy函数应用于sympy变量会失败:

import numpy as np
import sympy as sp

def np_fun(a):
    return np.array([np.sin(a), np.cos(a)])

x = sp.symbols('x')
sp_fun = np_fun(x)

我遇到了错误

AttributeError: 'Symbol' object has no attribute 'sin'

我的下一个想法是将numpy函数转换为sympy,但我找不到方法。我知道可以通过将该函数定义为sympy表达式来使此代码工作:

sp_fun = sp.Array([sp.sin(x), sp.cos(x)])

但我使用正弦/余弦函数作为简单的示例。实际函数已在numpy中定义,并且更加复杂,因此重新编写它将非常繁琐。


sympy 能够从自己的表达式中生成 numpy 表达式。但是 sympy 已经在使用符号了。而你的 np_fun 是一个 Python 函数,即使它调用了 numpy 函数。在 .py 文件中,它是一个字符串,但在运行会话中,它已经被解释器解析了。你只能通过 inspect 工具获取字符串版本。 - hpaulj
np.fun(x) 调用 np.sin(x),这将变成 np.sin(np.array(x))np.array(x) 是一个 0d 数组,其中包含一个对象元素,即 Symbol。面对这种类型的数组,np.sin() 尝试执行 x.sin()。这就是你的属性错误的来源。sympy 符号可以在一些 numpy 数学运算中使用,但不包括像 np.sin 这样的函数。 - hpaulj
2个回答

2
原则上,您可以直接修改函数的抽象语法树("abstract syntax tree"),但在实践中可能会变得非常棘手。无论如何,以下是您简单示例的操作方法:
这将从源代码创建一个ast,并从NodeTransformer类派生以就地修改ast。节点转换器具有通用的访问方法,遍历节点及其子树并委托给派生类中的特定节点访问者。在这里,我们将所有名称np更改为sp,然后再更改那些拼写不同的属性为前np现在sp。您需要将所有这样的差异添加到translate字典中。
最后,我们从ast编译回代码对象并执行它,以使修改后的函数可用。
import ast, inspect
import numpy as np
import sympy as sp

def f(a):
    return np.array([np.sin(a), np.cos(a)])

z = ast.parse(inspect.getsource(f))

translate = {'array': 'Array'}

class np_to_sp(ast.NodeTransformer):
    def visit_Name(self, node):
        if node.id=='np':
            node = ast.copy_location(ast.Name(id='sp', ctx=node.ctx), node)
        return node
    def visit_Attribute(self, node):
        self.generic_visit(node)
        if node.value.id=='sp' and node.attr in translate:
            fields = {k: getattr(node, k) for k in node._fields}
            fields['attr'] = translate[node.attr]
            node = ast.copy_location(ast.Attribute(**fields), node)
        return node

np_to_sp().visit(z)

exec(compile(z, '', 'exec'))

x = sp.Symbol('x')
print(f(x))

输出:

[sin(x), cos(x)]

简单增强更新:修改被函数调用的函数:

import ast, inspect
import numpy as np
import sympy as sp

def f(a):
    return np.array([np.sin(a), np.cos(a)])

def f2(a):
    return np.array([1, np.sin(a)])

def f3(a):
    return f(a) + f2(a)

translate = {'array': 'Array'}

class np_to_sp(ast.NodeTransformer):
    def visit_Name(self, node):
        if node.id=='np':
            node = ast.copy_location(ast.Name(id='sp', ctx=node.ctx), node)
        return node
    def visit_Attribute(self, node):
        self.generic_visit(node)
        if node.value.id=='sp' and node.attr in translate:
            fields = {k: getattr(node, k) for k in node._fields}
            fields['attr'] = translate[node.attr]
            node = ast.copy_location(ast.Attribute(**fields), node)
        return node

from types import FunctionType

for fn in f3.__code__.co_names:
    fo = globals()[fn]
    if not isinstance(fo, FunctionType):
        continue
    z = ast.parse(inspect.getsource(fo))
    np_to_sp().visit(z)
    exec(compile(z, '', 'exec'))

x = sp.Symbol('x')
print(f3(x))

输出:

[sin(x) + 1, sin(x) + cos(x)]

谢谢,这是一个有趣的方法。你知道我怎么修改它才能适用于函数的组合吗?例如,如果我定义了 def f2(a): return np.array([1, np.sin(a)])def f3(a): return f(a) + f2(a),并且我想转换 f3 而不是 f - Trevor
@wxyz 有点像,参见更新的帖子。不是非常健壮/通用,但你能理解原则。 - Paul Panzer

0
我建议使用查找和替换将您的numpy函数修改为sympy表达式。您可以在Python中使用str.replace()并定义适当的替换规则来进行操作。如果您发布您的函数,那么提供更多具体信息会更容易一些。

谢谢。那么您是建议我使用搜索和替换来生成额外的Python代码(例如在新的.py文件中),其中定义了新的sympy函数? - Trevor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接