ericf
ericf

Reputation: 260

Lambdify partially linear function in sympy

I have a function in sympy f(M,X) which is linear in V but nonlinear in X, both large n dimensional vectors. Give some X, how do I extract the linear vector that gives this function? At the moment I just lambdify and evaluate it for all the unit vectors in M, which seems very inefficient.

For a very simple example f([m1,m2],[x1,x2])= m1*(x1*x2) + m2*x2**2. Given x=[1,2] I want to get [2,4] since f([m1,m2],[1,2])= m1*2 + m2*4, without computing both f([1,0],[1,2]) and f([0,1],[1,2]) separately.

EDIT: added code below and fixed equations above.

Here's the code for the simple example:

import sympy as sym
m1,m2,x1,x2 = sym.symbols('m1,m2,x1,x2')     
f= m1*x1*x2 + m2*x2**2
Lf = sym.lambdify([m1,m2,x1,x2],f)
m1_coeff = Lf(1,0,1,2)
m2_coeff = Lf(0,1,1,2)
print(m1_coeff,m1_coeff)
**output**= 2 2

Here's a slightly more complicated version that shows how inefficient this method seems to be:

import sympy as sym
import numpy as np
n=5
m= sym.symarray('m',n)     
x= sym.symarray('x',n)     
f= sum(m[i]*x[i]**2 for i in range(n))
Lf = sym.lambdify([m,x],f,'numpy')
x_arr=np.arange(n)
m_coeff=np.zeros(n)
for i in range(n):
    m_arr = np.zeros(n)
    m_arr[i] = 1
    m_coeff[i] = Lf(m_arr,x_arr)
print(m_coeff)
**output** = [ 0.  1.  4.  9. 16.]

Upvotes: 0

Views: 155

Answers (1)

hpaulj
hpaulj

Reputation: 231605

In an isympy session:

In [1]: m1,m2,x1,x2 = symbols('m1,m2,x1,x2')
   ...: f= m1*x1*x2 + m2*x2**2
   ...: Lf = lambdify([m1,m2,x1,x2],f)

In [2]: f
Out[2]: 
                2
m₁⋅x₁⋅x₂ + m₂⋅x₂ 

In [9]: print(Lf.__doc__)
Created with lambdify. Signature:

func(m1, m2, x1, x2)

Expression:

m1*x1*x2 + m2*x2**2

Source code:

def _lambdifygenerated(m1, m2, x1, x2):
    return (m1*x1*x2 + m2*x2**2)

In sympy we can use subs:

In [11]: f.subs(dict(x1=1, x2=2))
Out[11]: 2⋅m₁ + 4⋅m₂

And get the coeffs (see that method's docs):

In [15]: f.subs(dict(x1=1, x2=2)).coeff(m1)
Out[15]: 2

In [16]: f.subs(dict(x1=1, x2=2)).coeff(m2)
Out[16]: 4

With the Python function, we can pass array arguments:

In [20]: Lf(np.array([1,0]),np.array([0,1]),1,2)
Out[20]: array([2, 4])

(don't pass lists, which have different meanings for * and +).

2nd

In [22]: n=5
    ...: m= symarray('m',n)
    ...: x= symarray('x',n)
    ...: f= sum(m[i]*x[i]**2 for i in range(n))
    ...: Lf = lambdify([m,x],f,'numpy')

In [23]: f
Out[23]: 
     2        2        2        2        2
m₀⋅x₀  + m₁⋅x₁  + m₂⋅x₂  + m₃⋅x₃  + m₄⋅x₄ 

In [24]: print(Lf.__doc__)
Created with lambdify. Signature:

func(m, x)

Expression:

m_0*x_0**2 + m_1*x_1**2 + m_2*x_2**2 + m_3*x_3**2 + m_4*x_4**2

Source code:

def _lambdifygenerated(_Dummy_22, _Dummy_23):
    [m_0, m_1, m_2, m_3, m_4] = _Dummy_22
    [x_0, x_1, x_2, x_3, x_4] = _Dummy_23
    return (m_0*x_0**2 + m_1*x_1**2 + m_2*x_2**2 + m_3*x_3**2 + m_4*x_4**2)

Using subs with a dictionary:

In [25]: x
Out[25]: array([x_0, x_1, x_2, x_3, x_4], dtype=object)

In [27]: {i:j for i,j in zip(x,np.arange(5))}
Out[27]: {x_0: 0, x_1: 1, x_2: 2, x_3: 3, x_4: 4}

In [28]: f.subs({i:j for i,j in zip(x,np.arange(5))})
Out[28]: m₁ + 4⋅m₂ + 9⋅m₃ + 16⋅m₄

The numpy version is simpler - though it may be harder to visualize :)

In [29]: Lf(np.eye(5),np.arange(5))
Out[29]: array([ 0.,  1.,  4.,  9., 16.])

Upvotes: 2

Related Questions