aishik roy chaudhury
aishik roy chaudhury

Reputation: 329

Calling a multiple argument function generated by 'lambdify' on numpy array

I wrote an expression f in SymPy as shown in the code and then converted into a function using lambdify. Then, I vectorzed it using np.vectorize(f)to be able to apply it on a numpy array.

import numpy as np
from math import exp
a = Symbol('a')
x = Symbol('x')
b = Symbol('b')
c = Symbol('c')

from sympy import *
f = exp(-(a+b+c)*x)*(4+exp(-(a+b)*x) -2*exp(-a*x) - 2*exp(-c*x))
f = lambdify([x,(a,b,c)], f)
vff = np.vectorize(f)

t = np.arange(0, 5, 0.01, dtype=np.float64)
y = vff(t, (1,1,1)) # (1,1,1) stands for (a,b,c)

But while doing so, the last line throws following error.

TypeError: _lambdifygenerated() missing 1 required positional argument: '_Dummy_191'.

I think the syntax might be wrong. I searched on internet, but could not find the right syntax. Can anyone tell me the correct syntax?

Upvotes: 1

Views: 741

Answers (1)

hpaulj
hpaulj

Reputation: 231605

In [8]: print(f.__doc__)                                                                                             
Created with lambdify. Signature:

func(x, arg_1)

Expression:

(exp(x*(-a - b)) + 4 - 2*exp(-c*x) - 2*exp(-a*x))*exp(x*(-a - b - c))

Source code:

def _lambdifygenerated(x, _Dummy_166):
    [a, b, c] = _Dummy_166
    return ((exp(x*(-a - b)) + 4 - 2*exp(-c*x) - 2*exp(-a*x))*exp(x*(-a - b - c)))

Imported modules:

f can be run with:

In [9]: f(np.arange(3),(1,1,1))                                                                                      
Out[9]: array([1.        , 0.13262366, 0.00861856])

Since f works with an array input, I don't think you need the vectorized form.

I get your error if I call vff with one argument:

In [14]: vff(np.arange(3))                                                                                           
---------------------------------------------------------------------------
...
TypeError: _lambdifygenerated() missing 1 required positional argument: '_Dummy_166'

With 2 arguments I get a different error:

In [15]: vff(np.arange(3),(1,1,1))                                                                                   
---------------------------------------------------------------------------
...
<lambdifygenerated-1> in _lambdifygenerated(x, _Dummy_166)
      1 def _lambdifygenerated(x, _Dummy_166):
----> 2     [a, b, c] = _Dummy_166
      3     return ((exp(x*(-a - b)) + 4 - 2*exp(-c*x) - 2*exp(-a*x))*exp(x*(-a - b - c)))

TypeError: 'numpy.int64' object is not iterable

That's because vectorize is passing scalar 1 as the second argument. vectorize passes scalar tuples to the function, not arrays. It's designed from functions that take scalar arguments, not arrays or even tuples. There are some ways around that - but keep in mind that vectorize is not a speed tool.

Since you don't want vectorize to iterate on the (a,b,c) argument, we can 'exclude' it:

In [16]: vff = np.vectorize(f, excluded=[1])                                                                         

In [17]: vff(np.arange(3),(1,1,1))                                                                                   
Out[17]: array([1.        , 0.13262366, 0.00861856])

That works the same as f (above). But it is much slower:

In [18]: timeit vff(np.arange(300),(1,1,1))                                                                          
5.29 ms ± 11.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [19]: timeit f(np.arange(300),(1,1,1))                                                                            
103 µs ± 41 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

vectorize is even slower than a list comprehension:

In [20]: timeit np.array([f(i,(1,1,1)) for i in range(300)])                                                         
3.91 ms ± 6.17 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Upvotes: 2

Related Questions