Преобразование функции, определенной в NumPy, в SymPy

#python #numpy #sympy

#python #numpy #sympy

Вопрос:

У меня есть функция, определенная в 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)])
  

Но я использую функцию sine / cosine в качестве простого примера. Фактическая функция, которую я использую, уже определена в numpy и намного сложнее, поэтому было бы очень утомительно переписывать ее.

Комментарии:

1. sympy имеет возможность генерировать numpy выражения из своих собственных выражений. Но sympy уже работает с символами. Но ваша np_fun функция на Python, даже если она вызывает numpy функции. В .py файле это строка, но в запущенном сеансе она уже проанализирована интерпретатором. Получить строковую версию можно только с inspect помощью инструментов.

2. np.fun(x) вызывает np.sin(x) , которая становится np.sin(np.array(x)) . np.array(x) представляет собой массив 0d с элементом object, the Symbol . Столкнувшись с таким типом массива, np.sin() пытается сделать x.sin() . Это источник ошибки вашего атрибута. sympy символы могут использоваться в некоторой numpy математике, но не такие, как np.sin .

Ответ №1:

В принципе, вы могли бы напрямую модифицировать ast («абстрактное синтаксическое дерево») функции, хотя на практике это может оказаться довольно сложным. В любом случае, вот как это сделать для вашего простого примера:

Это создает из исходного кода ast и является производным от NodeTransformer класса для изменения ast на месте. Преобразователь узла имеет общий метод visit, который обходит узел и его поддерево, делегируя конкретным посетителям узла в производных классах. Здесь мы меняем все имена np на sp , а затем меняем эти атрибуты на прежние, np now sp , которые пишутся по-другому. Вам пришлось бы добавить все подобные различия в translate dict.

Наконец, мы компилируем обратно из ast в объект code и выполняем его, чтобы сделать измененную функцию доступной.

 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)]
  

Комментарии:

1. Спасибо, это интересный подход. Вы знаете, как я мог бы изменить это, чтобы это работало для композиций функций. Например, если я определяю def f2(a): return np.array([1, np.sin(a)]) и def f3(a): return f(a) f2(a) , и я хотел преобразовать f3 вместо f

2. @wxyz Вроде того, см. Обновленный пост. Хотя и не очень надежный / общий, но принцип вы понимаете.

Ответ №2:

Я бы рекомендовал использовать Find и Replace, чтобы преобразовать вашу функцию numpy в выражение sympy. Вы можете сделать это на python, используя str.replace() и определяя правила для замены текста в соответствии с вашей функцией. Если вы опубликуете свою функцию, было бы проще предоставить больше подробностей.

Комментарии:

1. Спасибо. Итак, вы предлагаете мне использовать поиск и замену для генерации дополнительного кода python (например,в новом файле .py), в котором определены новые функции sympy?