How can I convert a SymPy expression to NumPy? - python

How can I convert a sympy expression to numpy code? For example, say I this was the code for the expression:
expression = 2 * x/y + 10 * sympy.exp(x) # Assuming that x and y are predefined from sympy.symbols
I would want to go from expression to this:
np_expression = "np.dot(2, np.dot(x, np.linalg.pinv(y))) + np.dot(10, np.exp(x))"
Note that x and y are matrices, but we can assume the shapes will match
An example with real numbers would go like this:
a = np.array([1,2],[3,4])
b = np.array([5,6],[7,8])
expression = 2 * a/b + 10 # These would be sympy symbols rather than numbers
and the result would be this:
np_expression = "np.dot(2, np.dot(5, np.linalg.pinv(9))) + 10"

In [1]: expr = 2 *x/y + 10 * exp(x)
In [3]: f = lambdify((x,y), expr)
In [4]: help(f)
_lambdifygenerated(x, y)
Created with lambdify. Signature:
func(x, y)
Expression:
2*x/y + 10*exp(x)
Source code:
def _lambdifygenerated(x, y):
return 2*x/y + 10*exp(x)
Which for specific inputs, array or otherwise:
In [5]: f(np.arange(1,5)[:,None], np.arange(1,4))
Out[5]:
array([[ 29.18281828, 28.18281828, 27.84948495],
[ 77.89056099, 75.89056099, 75.22389432],
[206.85536923, 203.85536923, 202.85536923],
[553.98150033, 549.98150033, 548.648167 ]])
In [6]: f(1,1)
Out[6]: 29.18281828459045
In [7]: f(2,3)
Out[7]: 75.22389432263984
In [8]: f(np.arange(1,4),np.arange(1,4))
Out[8]: array([ 29.18281828, 75.89056099, 202.85536923])
Normal array broadcasting rules apply. Note that x/y is element-wise. I'm not sure what lambdify will translate into dot and inv code.
trying your numpy code:
In [9]: np.dot(2, np.dot(2,np.linalg.pinv(3)))+10*np.exp(2)
---------------------------------------------------------------------------
LinAlgError Traceback (most recent call last)
<ipython-input-9-6cae91f0e0f8> in <module>
----> 1 np.dot(2, np.dot(2,np.linalg.pinv(3)))+10*np.exp(2)
....
LinAlgError: 0-dimensional array given. Array must be at least two-dimensional
We have to change the y into a 2d array, e.g. [[3]]:
In [10]: np.dot(2, np.dot(2,np.linalg.pinv([[3]])))+10*np.exp(2)
Out[10]: array([[75.22389432]])

Related

How is it possible to define a equation, differentiate and plot it, using Sympy in Jupyter Nootebook?

In Jupyter Notebook i would like to define a equation, differentiate and plot the equation.
import sympy as sp
from IPython.display import display
sp.init_printing()
import matplotlib.pyplot as plt
import numpy as np
x = sp.symbols('x')
def func(x):
a= sp.sympify("4/5")
return (x**3+a*x**2)
display(func(x))
def dfunc(x):
a = sp.diff(func(x),x)
return a
display(dfunc(x))
x = np.linspace(-10,10,20)
plt.plot(x,func(x))
plt.plot(x,dfunc(x)) # doesn't work
display(dfunc(x)) shows the wanted function but plt.plot(x,dfunc(x)) returns the error message ValueError: cannot derive by this array
Does anyone know how to get the plot?
(It also doesn't work with sp.integrate(func(x),x) instead of sp.diff(func(x),x). Just another error message is returned ValueError: Invalid limits given: ...)
Many thanks in advance.
Matthias
You can use the SymPy plot function rather than the matplotlib one. The matplotlib plot function expects arrays as inputs whereas the sympy one accepts sympy expressions (and then calculates values to make the arrays for matplotlib):
In [36]: import sympy as sym
In [37]: a = sym.Rational(4, 5)
In [38]: x = sym.Symbol('x')
In [39]: f = x**3 + a*x**2
In [40]: f
Out[40]:
2
3 4⋅x
x + ────
5
In [41]: f.diff(x)
Out[41]:
2 8⋅x
3⋅x + ───
5
In [42]: sym.plot(f.diff(x))

weird behavior of numba guvectorize

I write a function to test numba.guvectorize. This function takes product of two numpy arrays and compute the sum after first axis, as following:
from numba import guvectorize, float64
import numpy as np
#guvectorize([(float64[:], float64[:], float64)], '(n),(n)->()')
def g(x, y, res):
res = np.sum(x * y)
However, the above guvectorize function returns wrong results as shown below:
>>> a = np.random.randn(3,4)
>>> b = np.random.randn(3,4)
>>> np.sum(a * b, axis=1)
array([-0.83053829, -0.15221319, -2.27825015])
>>> g(a, b)
array([4.67406747e-310, 0.00000000e+000, 1.58101007e-322])
What might be causing this problem?
Function g() receives an uninitialized array through the res parameter. Assigning a new value to it doesn't modify the original array passed to the function.
You need to replace the contents of res (and declare it as an array):
#guvectorize([(float64[:], float64[:], float64[:])], '(n),(n)->()')
def g(x, y, res):
res[:] = np.sum(x * y)
The function operates on 1D vectors and returns a scalar (thus the signature (n),(n)->()) and guvectorize does the job of dealing with 2D inputs and returning a 1D output.
>>> a = np.random.randn(3,4)
>>> b = np.random.randn(3,4)
>>> np.sum(a * b, axis=1)
array([-3.1756397 , 5.72632531, 0.45359806])
>>> g(a, b)
array([-3.1756397 , 5.72632531, 0.45359806])
But the original Numpy function np.sum is already vectorized and compiled, so there is little speed gain in using guvectorize in this specific case.
Your a and b arrays are 2-dimensional, while your guvectorized function has signature of accepting 1D arrays and returning 0D scalar. You have to modify it to accept 2D and return 1D.
In one case you do np.sum with axis = 1 in another case without it, you have to do same thing in both cases.
Also instead of res = ... use res[...] = .... Maybe it is not the problem in case of guvectorize but it can be a general problem in Numpy code because you have to assign values instead of variable reference.
In my case I added cache = True param to guvectorize decorator, it only speeds up running through caching/re-using Numba compiled code and not re-compiling it on every run. It just speeds up things.
Full modified corrected code see below:
Try it online!
from numba import guvectorize, float64
import numpy as np
#guvectorize([(float64[:, :], float64[:, :], float64[:])], '(n, m),(n, m)->(n)', cache = True)
def g(x, y, res):
res[...] = np.sum(x * y, axis = 1)
# Test
np.random.seed(0)
a = np.random.randn(3, 4)
b = np.random.randn(3, 4)
print(np.sum(a * b, axis = 1))
print(g(a, b))
Output:
[ 2.57335386 3.41749149 -0.42290296]
[ 2.57335386 3.41749149 -0.42290296]

AttributeError: 'Mul' object has no attribute 'sqrt'

I am receiving the error stated in the title. Full error:
MaxD = Cone*np.sqrt(SymsX/np.pi)*np.exp((-SymsX/(k*T))) #Define Maxwellian distribution function
AttributeError: 'Mul' object has no attribute 'sqrt'
Here is the code:
from sympy.interactive import printing
printing.init_printing(use_latex = True)
import numpy as np
from sympy import Eq, dsolve, Function, Symbol, symbols
import sympy as sp
EpNaut = 8.854187E-12
u0 = 1.256E-6
k = 1/(4*np.pi*EpNaut)
NumGen = 1000 #How many solution points user wants to generate between 0 and maxen (Higher # the more accurate)
T = 1000 #Temperature in (K)
MaxEn = 7*T*k #Max energy in system
Cone = 2/((k*T)**(3/2)) #Constant infront of the Maxwellian distribution function
SymsX = sp.Symbol('SymsX')
MaxD = Function('MaxD')
PFunction = Function('PFunction')
MaxD = Cone*np.sqrt(SymsX/np.pi)*np.exp((-SymsX/(k*T))) #Define Maxwellian distribution function
PFunction = sp.integrate(MaxD) #Integrate function to get probability-error function
print(PFunction)
I also have an additional question. I sometimes see examples use "from ... import ...". Why is this? Shouldn't just importing the entire library be enough? Is it because using the import command doesn't actually import the entire library but really just the most basic functions?
In an isympy session:
In [1]: import numpy as np
In [3]: SymsX = Symbol('SymsX')
In [5]: SymsX/np.pi # symbol * float
Out[5]: 0.318309886183791⋅SymsX
In [6]: SymsX/pi # symbol * symbol
Out[6]:
SymsX
─────
π
In [7]: sqrt(SymsX/pi) # sympy sqrt
Out[7]:
_______
╲╱ SymsX
─────────
√π
In [8]: np.sqrt(SymsX/pi) # numeric sqrt
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
AttributeError: 'Mul' object has no attribute 'sqrt'
The above exception was the direct cause of the following exception:
TypeError Traceback (most recent call last)
<ipython-input-8-27f855f6b3e2> in <module>
----> 1 np.sqrt(SymsX/pi)
TypeError: loop of ufunc does not support argument 0 of type Mul which has no callable sqrt method
np.sqrt has to first convert its input into a numpy array:
In [10]: np.array(SymsX/np.pi)
Out[10]: array(0.318309886183791*SymsX, dtype=object)
This is an object dtype array, not a normal numeric one. Given such an array, q numpy ufunc tries to delegate the action to a element method. e.g. (0.31*SymsX).sqrt()
Multiply and addition do work with this object array:
In [11]: 2*_
Out[11]: 0.636619772367581⋅SymsX
In [12]: _ + __
Out[12]: 0.954929658551372⋅SymsX
These work because the sympy object has the right add and multiply methods:
In [14]: Out[5].__add__
Out[14]: <bound method Expr.__add__ of 0.318309886183791*SymsX>
In [15]: Out[5]+2*Out[5]
Out[15]: 0.954929658551372⋅SymsX
===
The sympy.lambdify is the best tool for using sympy and numpy together. Look up its docs.
In this case the SymsX/pi expression can be converted into a numpy expression with:
In [18]: lambdify(SymsX, Out[5],'numpy')
Out[18]: <function _lambdifygenerated(SymsX)>
In [19]: _(23) # evaluate with `SymsX=23`:
Out[19]: 7.321127382227194
In [20]: 23/np.pi
Out[20]: 7.321127382227186
In [21]: np.sqrt(_19) # np.sqrt now works on the number
Out[21]: 2.7057581899030065
====
The same evaluation in sympy:
In [23]: expr = sqrt(SymsX/pi)
In [24]: expr
Out[24]:
_______
╲╱ SymsX
─────────
√π
In [25]: expr.subs(SymsX, 23)
Out[25]:
√23
───
√π
In [27]: _.evalf()
Out[27]: 2.70575818990300
In a fresh isympy session:
These commands were executed:
>>> from __future__ import division
>>> from sympy import *
>>> x, y, z, t = symbols('x y z t')
>>> k, m, n = symbols('k m n', integer=True)
>>> f, g, h = symbols('f g h', cls=Function)
>>> init_printing()
Documentation can be found at https://docs.sympy.org/1.4/
In [1]: EpNaut = 8.854187E-12
...: u0 = 1.256E-6
...: k = 1/(4*pi*EpNaut)
...: NumGen = 1000
...: T = 1000
...: MaxEn = 7*T*k
...: Cone = 2/((k*T)**(3/2))
...:
...: SymsX = Symbol('SymsX')
...: MaxD = Function('MaxD')
...: PFunction = Function('PFunction')
...: MaxD = Cone*sqrt(SymsX/pi)*exp((-SymsX/(k*T))) #Define Maxwellian distri
...: bution function
...: PFunction = integrate(MaxD) #Integrate function to get probability-error
...: function
...:
The result:
In [2]: PFunction
Out[2]:
⎛ _______ -3.5416748e-14⋅π⋅Syms
1.0 ⎜ 28235229276273.5⋅╲╱ SymsX ⋅ℯ
1.33303949775482e-20⋅π ⋅⎜- ─────────────────────────────────────────────────
⎝ π
X ⎛ _______⎞⎞
7.50165318945357e+19⋅erf⎝1.88193379267178e-7⋅√π⋅╲╱ SymsX ⎠⎟
─ + ──────────────────────────────────────────────────────────⎟
π ⎠
In [3]: MaxD
Out[3]:
1.0 _______ -3.5416748e-14⋅π⋅SymsX
1.33303949775482e-20⋅π ⋅╲╱ SymsX ⋅ℯ
SymsX is still a symbol, so these are sympy expressions, not numbers.

Sympy get vector from vector field

I'm using sympy (which is awesome) and I just made a vector field like this
> import sympy
> from sympy.vector import CoordSys3D
> from sympy import *
> R = CoordSys3D('R')
> x, y, z, t = symbols('x y z t')
> v = x*R.i + 4*z*R.j + y*R.k
x*R.i + 4*z*R.j + y*R.k
> v.evalf(subs={x:6, y:5, z:2})
6.00000000000000*R.i + 8.00000000000000*R.j + 5.00000000000000*R.k
and what I need is to get a vector or list of the form [6.0,8.0,5.0], so is there a way to get a list form v.evalf()? I could use use split or something on "6.00000000000000*R.i + 8.00000000000000*R.j + 5.00000000000000*R.k" but thats seems ugly and maybe there a built in method for that?
In [252]: vector = v.evalf(subs={x:6, y:5, z:2}); vector
Out[252]: 6.00000000000000*R.i + 8.00000000000000*R.j + 5.00000000000000*R.k
In [253]: list(vector.to_matrix(R))
Out[253]: [6.00000000000000, 8.00000000000000, 5.00000000000000]
Other possibilities include
In [256]: vector.as_poly().coeffs()
Out[256]: [6.00000000000000, 8.00000000000000, 5.00000000000000]
In [257]: list(vector.components.values())
Out[257]: [5.00000000000000, 8.00000000000000, 6.00000000000000]
but I think they suffer a fatal flaw which is exposed when one or more of the components equal 0. For example, if z is set to 0:
In [258]: vector = v.evalf(subs={x:6, y:5, z:0}); vector
Out[258]: 6.00000000000000*R.i + 5.00000000000000*R.k
Then list(vector.to_matrix(R)) still returns 3 components:
In [259]: list(vector.to_matrix(R))
Out[259]: [6.00000000000000, 0, 5.00000000000000]
while these other two expressions omit the zero-component:
In [260]: vector.as_poly().coeffs()
Out[260]: [6.00000000000000, 5.00000000000000]
In [261]: list(vector.components.values())
Out[261]: [5.00000000000000, 6.00000000000000]

how to apply a mask from one array to another array?

I've read the masked array documentation several times now, searched everywhere and feel thoroughly stupid. I can't figure out for the life in me how to apply a mask from one array to another.
Example:
import numpy as np
y = np.array([2,1,5,2]) # y axis
x = np.array([1,2,3,4]) # x axis
m = np.ma.masked_where(y>2, y) # filter out values larger than 5
print m
[2 1 -- 2]
print np.ma.compressed(m)
[2 1 2]
So this works fine.... but to plot this y axis, I need a matching x axis. How do I apply the mask from the y array to the x array? Something like this would make sense, but produces rubbish:
new_x = x[m.mask].copy()
new_x
array([5])
So, how on earth is that done (note the new x array needs to be a new array).
Edit:
Well, it seems one way to do this works like this:
>>> import numpy as np
>>> x = np.array([1,2,3,4])
>>> y = np.array([2,1,5,2])
>>> m = np.ma.masked_where(y>2, y)
>>> new_x = np.ma.masked_array(x, m.mask)
>>> print np.ma.compressed(new_x)
[1 2 4]
But that's incredibly messy! I'm trying to find a solution as elegant as IDL...
I had a similar issue, but involving loads more masking commands and more arrays to apply them. My solution is that I do all the masking on one array and then use the finally masked array as the condition in the mask_where command.
For example:
y = np.array([2,1,5,2]) # y axis
x = np.array([1,2,3,4]) # x axis
m = np.ma.masked_where(y>5, y) # filter out values larger than 5
new_x = np.ma.masked_where(np.ma.getmask(m), x) # applies the mask of m on x
The nice thing is you can now apply this mask to many more arrays without going through the masking process for each of them.
Why not simply
import numpy as np
y = np.array([2,1,5,2]) # y axis
x = np.array([1,2,3,4]) # x axis
m = np.ma.masked_where(y>2, y) # filter out values larger than 5
print list(m)
print np.ma.compressed(m)
# mask x the same way
m_ = np.ma.masked_where(y>2, x) # filter out values larger than 5
# print here the list
print list(m_)
print np.ma.compressed(m_)
code is for Python 2.x
Also, as proposed by joris, this do the work new_x = x[~m.mask].copy() giving an array
>>> new_x
array([1, 2, 4])
This may not bee 100% what OP wanted to know,
but it's a cute little piece of code I use all the time -
if you want to mask several arrays the same way, you can use this generalized function to mask a dynamic number of numpy arrays at once:
def apply_mask_to_all(mask, *arrays):
assert all([arr.shape == mask.shape for arr in arrays]), "All Arrays need to have the same shape as the mask"
return tuple([arr[mask] for arr in arrays])
See this example usage:
# init 4 equally shaped arrays
x1 = np.random.rand(3,4)
x2 = np.random.rand(3,4)
x3 = np.random.rand(3,4)
x4 = np.random.rand(3,4)
# create a mask
mask = x1 > 0.8
# apply the mask to all arrays at once
x1, x2, x3, x4 = apply_mask_to_all(m, x1, x2, x3, x4)

Categories