Sympy: lambdify так, что операции с массивами всегда приводят к массивам, в том числе и к константам? - PullRequest
1 голос
/ 15 января 2020

Мне нужно оценить производную функций (f '), заданную пользователем во многих точках. Точки находятся в списке (или numpy .array, pandas .Series ...). Я получаю ожидаемое значение, когда f 'зависит от симпби-переменной, но не когда f' является константой:

import sympy as sp

f1 = sp.sympify('1')
f2 = sp.sympify('t')

lamb1 = sp.lambdify('t',f1)
lamb2 = sp.lambdify('t',f2)

print(lamb1([1,2,3]))
print(lamb2([1,2,3]))

Я получаю:

1
[1, 2, 3]

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

Эти функции находятся в матрице и являются конечным результатом операций sympy, таких как получение производных. Точная форма f1 и f2 варьируется в зависимости от задачи.

Ответы [ 4 ]

1 голос
/ 16 января 2020

С isympy/ipython самоанализом:

In [28]: lamb2??                                                                                 
Signature: lamb2(t)
Docstring:
Created with lambdify. Signature:

func(arg_0)

Expression:

t

Source code:

def _lambdifygenerated(t):
    return (t)

и для первого:

In [29]: lamb1??                                                                                 
Signature: lamb1(t)
Docstring:
Created with lambdify. Signature:

func(arg_0)

Expression:

1

Source code:

def _lambdifygenerated(t):
    return (1)

Таким образом, возвращается входной аргумент; другой возвращает только константу, независимо от ввода. lambdify выполняет довольно простой лексический перевод с sympy на numpy Python.

edit

Помещение ваших функций в sp.Matrix:

In [55]: lamb3 = lambdify('t',Matrix([f1,f2]))                                                   

In [56]: lamb3??                                                                                 
...
def _lambdifygenerated(t):
    return (array([[1], [t]]))
...

In [57]: lamb3(np.arange(3))                                                                     
Out[57]: 
array([[1],
       [array([0, 1, 2])]], dtype=object)

Так что это возвращает массив numpy; но из-за сочетания форм результатом является объект типа d, а не 2d.

Мы можем увидеть это с помощью прямой генерации массива:

In [53]: np.array([[1],[1,2,3]])                                                                 
Out[53]: array([list([1]), list([1, 2, 3])], dtype=object)

In [54]: np.array([np.ones(3,int),[1,2,3]])                                                      
Out[54]: 
array([[1, 1, 1],
       [1, 2, 3]])

Ни sympy, ни np.array пытается «передать» эту константу. Существуют numpy конструкции, которые будут делать это, такие как умножение и сложение, но эта простая функция sympy и lambdify этого не делают.

edit

frompyfunc - это способ передачи массив (или массивы) для функции, которая работает только со скалярными входами. Хотя lamb2 работает с входом массива, вы не довольны случаем lamb1, или предположительно lamb3.

In [60]: np.frompyfunc(lamb1,1,1)([1,2,3])                                                       
Out[60]: array([1, 1, 1], dtype=object)

In [61]: np.frompyfunc(lamb2,1,1)([1,2,3])                                                       
Out[61]: array([1, 2, 3], dtype=object)

Этот [61] медленнее, чем просто lamb2([1,2,3]), поскольку он эффективно итерирует.

In [62]: np.frompyfunc(lamb3,1,1)([1,2,3])                                                       
Out[62]: 
array([array([[1],
       [1]]), array([[1],
       [2]]),
       array([[1],
       [3]])], dtype=object)

В этом случае Matrix результатом является массив массивов. Но так как формы совпадают, их можно объединить в один массив (различными способами):

In [66]: np.concatenate(_62, axis=1)                                                             
Out[66]: 
array([[1, 1, 1],
       [1, 2, 3]])
1 голос
/ 15 января 2020

lamb1 - это функция, которая возвращает константу 1: def lamb1(x): return 1.

lamb2 - это функция, которая возвращает свой аргумент: def lamb2(x): return x.

Итак, вывод очень хорошо ожидаемый.

Вот подход, который может работать. Я изменил тестовую функцию для f2 на t*t, так как это было более раздражающим в моих тестах (работа с Pow (t, 2)).

import sympy as sp
import numpy as np

f1 = sp.sympify('1')
f2 = sp.sympify('t*t')

def np_lambdify(varname, func):
    lamb = sp.lambdify(varname, func, modules=['numpy'])
    if func.is_constant():
        return lambda t: np.full_like(t, lamb(t))
    else:
        return lambda t: lamb(np.array(t))

lamb1 = np_lambdify('t', f1)
lamb2 = np_lambdify('t', f2)

print(lamb1(1))
print(lamb1([1, 2, 3]))
print(lamb2(2))
print(lamb2([1, 2, 3]))

Выходы:

1
[1 1 1]
4
[1 4 9]
0 голосов
/ 15 января 2020

Я никогда не использую lambdify, поэтому не могу быть слишком критичен по поводу того, как он работает. Но, похоже, вам нужно будет обмануть его, дав ему выражение, которое не упрощается, до скаляра, который при оценке с числами будет уменьшаться до желаемого значения:

>>> import numpy as np
>>> lambdify('t','(1+t)*t-t**2-t+42','numpy')(np.array([1,2,3]))
array([42, 42, 42])
0 голосов
/ 15 января 2020

Я часто использую трюк t * 0 + 1, чтобы создать нулевой вектор такой же длины, что и мои входные данные, но затем добавляю 1 к каждому из его элементов. Работает с NumPy; проверьте, работает ли он с Sympy!

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