1

Sympy lambdify can be used to bridge functionality between numpy and sympy. However, I could not find what shortcomings exist when using lambdify. For example, I am interested in using Max with sympy and numpy:

f = lambdify([x], Max(x, 1), 'numpy')

f(np.array([-1,0,1]))

Throws the ValueError:

ValueError: The argument '[-1, 0, 1]' is not comparable.

On the other hand crude operations with dunder methods work fine:

f = lambdify([x], Add(x, 1), 'numpy')

f(np.array([-1,0,1]))

Output:

array([0, 1, 2])

Ultimately, I would like lambdify to work with numpy arrays containing sympy Symbol objects (or sympy.tensor.array.Array):

a = symarray('a', (3,))
f = lambdify([x], Max(x, 1), 'numpy')

f(a)

Throws the TypeError:

TypeError: cannot determine truth value of Relational

My expected output is:

array([Max(1, a_0), Max(1, a_1), Max(1, a_2)], dtype=object)

Ofcourse, I could implement this using list comprehension.

Kevin
  • 3,096
  • 2
  • 8
  • 37
  • Why don't you use list comprehansion? – hpaulj Jun 11 '21 at 06:29
  • I thought it would be complicated when broadcasting/reducing certain axes, but maybe numpy has support for this by using some kind of iterator? I would imagine einsum is the worst, since I probably need to parse the subscripts string myself. – Kevin Jun 11 '21 at 14:37
  • I haven't worked with `symarray`, but it looks like you treat it like any other object dtype array. That means some math works directly, such as `a+1` - no need to `lambdify`. But applying a 'function' to each element of `a` (such as `Max`) requires explicit iteration. See my edits. – hpaulj Jun 11 '21 at 16:29
  • 1
    See also [How do I use sympy.lambdify with Max function to substitute numpy.maximum instead of numpy.amax?](https://stackoverflow.com/questions/60723841/how-do-i-use-sympy-lambdify-with-max-function-to-substitute-numpy-maximum-instea) – JohanC Jun 11 '21 at 16:44
  • As I understand ```np.max``` is shorthand for ```np.maximum.reduce```, just like ```np.sum``` is shorthand for ```np.add.reduce```. I can apply ```np.maximum``` or ```np.add``` on a object array in a elementwise fashion by using ```frompyfunc``` as hpaulj suggests. On the other hand, I find it difficult to apply the generalized functions like ```reduce```. – Kevin Jun 11 '21 at 17:02
  • I tested by setting ```f=np.frompyfunc(lambda x: Max(x,y),2,1)```. ```f.reduce(a, axis=1)``` works, but ```f.reduce(a, axis=None)``` throws the error ```ValueError: reduction operation ' (vectorized)' is not reorderable, so at most one axis may be specified```. I am using ```a = symarray('a', (3,3))``` above. – Kevin Jun 11 '21 at 17:03
  • I don't see the value in use `reduce` for this task. – hpaulj Jun 11 '21 at 18:00
  • The value ```a``` is ```symarray('a', (3,3))```. I made a typo when declaring the function, it should be: ```f=np.frompyfunc(lambda x,y: Max(x,y),2,1)``` – Kevin Jun 11 '21 at 18:06

1 Answers1

1

help(f) displays:

Help on function _lambdifygenerated:

_lambdifygenerated(x)
    Created with lambdify. Signature:
    
    func(x)
    
    Expression:
    
    Max(1, x)
    
    Source code:
    
    def _lambdifygenerated(x):
        return (amax((1,x), axis=0))

Trying to apply this to 1d array has a couple of problems.

In [62]: a
Out[62]: array([-1,  0,  1])

First it tries to make an array from (1,x), resulting in the ragged array warning.

In [63]: f(a)
/usr/local/lib/python3.8/dist-packages/numpy/core/fromnumeric.py:87: VisibleDeprecationWarning: Creating an ndarray from ragged nested sequences (which is a list-or-tuple of lists-or-tuples-or ndarrays with different lengths or shapes) is deprecated. If you meant to do this, you must specify 'dtype=object' when creating the ndarray.
  return ufunc.reduce(obj, axis, dtype, out, **passkwargs)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-63-730f2a0fa0b7> in <module>
----> 1 f(a)

<lambdifygenerated-4> in _lambdifygenerated(x)
      1 def _lambdifygenerated(x):
----> 2     return (amax((1,x), axis=0))

<__array_function__ internals> in amax(*args, **kwargs)
....
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

And then it gets the ambiguity from error from trying to compare a to 1, the result of which is a boolean array.

lambdify is a relatively 'dumb' lexicographic translator from sympy to numpy, and easily fails.

The code for Add is

def _lambdifygenerated(x):
    return (x + 1)

which is a valid numpy expression.

Your symarray case:

In [72]: a = symarray('a', (3,))

In [73]: a
Out[73]: array([a_0, a_1, a_2], dtype=object)

Again, np.array((1,a)) makes a ragged array. and the max.reduce is comparing array with a scalar.

The Add works, in effect doing a list comprehension on the elements of the a array:

In [74]: g=lambdify([x], Add(x, 1), 'numpy')
In [75]: g(a)
Out[75]: array([a_0 + 1, a_1 + 1, a_2 + 1], dtype=object)

But we don't need lambdify to do this:

In [83]: a+1
Out[83]: array([a_0 + 1, a_1 + 1, a_2 + 1], dtype=object)

And A*A.T kind of calculation:

In [89]: a*a[:,None]
Out[89]: 
array([[a_0**2, a_0*a_1, a_0*a_2],
       [a_0*a_1, a_1**2, a_1*a_2],
       [a_0*a_2, a_1*a_2, a_2**2]], dtype=object)

In [90]: _.sum(axis=1)
Out[90]: 
array([a_0**2 + a_0*a_1 + a_0*a_2, a_0*a_1 + a_1**2 + a_1*a_2,
       a_0*a_2 + a_1*a_2 + a_2**2], dtype=object)

Your Max could be applied to a via frompyfunc:

In [91]: f=np.frompyfunc(lambda x: Max(1,x),1,1)
In [93]: f(a)
Out[93]: array([Max(1, a_0), Max(1, a_1), Max(1, a_2)], dtype=object)

this is similar to:

In [94]: np.array([Max(1,x) for x in a])
Out[94]: array([Max(1, a_0), Max(1, a_1), Max(1, a_2)], dtype=object)

with about the same speed, but a bit more flexibility when using broadcasting and such.

hpaulj
  • 221,503
  • 14
  • 230
  • 353