Sympy -- Simplify and gather different variables for variable transformation - python-3.x

In a sympy expression I would like to gather all sub expressions of (x*y)
and replace it by z, wherever possible. In a very simple example, that means performing the map
x*a*y+ (x*y**2) -> a*(x*y) + (x*y)*y -> a*z + z*y
The full code is with another example is
from sympy import symbols,Function,Derivative
from sympy import simplify, exp, cos, sin,log
x,y,z = symbols('x y z')
a,b,c = symbols('a b c')
f,g = Function('f')(x),Function('g')(x)
# Simplify the expression such that all combinations of (x*y) can be replaced by c
expr_1 = ((x**2+y)*y*exp(-c+2*log(x*c*y)))/(x**3*c*y**2)
#simplify(expr_1) ?
In a final step, I would like to replace two functions f*g by h
expr_2 = f*g + f*Derivative(g,x) + Derivative(f*g, x) -> h + f*Derivative(g,x) + Derivative(h, x)

Sometimes an algebraic substitution will do what you want:
>>> eq
a*x*y + x*y**2
>>> eq.subs(x,z/y)
a*z + y*z
But you could just as well have done subs(y,z/x) but that would not have led to as simple of an expression. In such cases you can try both and take the simpler of the two:
>>> from sympy import ordered
>>> next(ordered([eq.subs(x,z/y),eq.subs(y,z/x)]))
a*z + y*z
For expr_1
>>> eq=((x**2+y)*y*exp(-c+2*log(x*c*y)))/(x**3*c*y**2)
>>> next(ordered([eq.subs(x,z/y),eq.subs(y,z/x)]))
c*z*(x**2 + z/x)*exp(-c)/x**2
>>> next(ordered([eq.subs(x,c/y),eq.subs(y,c/x)]))
y**2*(c**2/y**2 + y)*exp(-c)
>>> simplify(_)
(c**2 + y**3)*exp(-c)
The ordered strategy should also work for expr_2.

Related

Python Parameterize Formatting

So I was wondering if there was a way to parameterize the format operator
For example
>>> '{:.4f}'.format(round(1.23456789, 4))
'1.2346
However, is there anyway to do something like this instead
>>> x = 4
>>> '{:.xf}'.format(round(1.23456789, x))
'1.2346
Yes, this is possible with a little bit of string concatenation. Check out the code below:
>>> x = 4
>>> string = '{:.' + str(x) + 'f}' # concatenate the string value of x
>>> string # you can see that string is the same as '{:.4f}'
'{:.4f}'
>>> string.format(round(1.23456789, x)) # the final result
'1.2346'
>>>
or if you wish to do this without the extra string variable:
>>> ('{:.' + str(x) + 'f}').format(round(1.23456789, x)) # wrap the concatenated string in parenthesis
'1.2346'

How to avoid double for loops?

I want to calculate part of 2 matrices (inner, outer) using data from 2 other matrices. They are all the same size. The code below works but it is too slow on big matrices. I used np.fromfunction in another case but was calculating the entire matrix not only a subset.
What's the fastest replacement for the double for loops?
F = np.random.rand(100,100)
S = 10*np.random.rand(100,100) + 1
L,C = F.shape
inner = np.zeros((L,C))
outer = np.zeros((L,C))
for l in range(5, L - 5):
for c in range(5, C - 5):
inner[l,c] = np.mean(F[l-5 : l+5 , c-5:c])
outer[l,c] = np.mean(F[l-5 : l+5 , c+5 : c+5+int(S[l,c])])
It looks like inner is the result of the convolution of 10x5 averaging filter on F. This is quite easy to rewrite as a convolution with scipy and it will be as fast as you can get from a CPU. However since you are leaving out 5 rows and columns on the borders of the matrices, you have to truncate the output inner and inner2 matrices accordingly to be able to compare them.
import numpy as np
from scipy.signal import convolve2d
F = np.random.rand(100,100)
S = 10*np.random.rand(100,100) + 1
L,C = F.shape
inner = np.zeros((L,C))
outer = np.zeros((L,C))
for l in range(5, L - 5):
for c in range(5, C - 5):
inner[l, c] = np.mean(F[l-5 : l+5 , c-5:c])
outer[l, c] = np.mean(F[l-5 : l+5 , c+5 : c+ 5 + int(S[l, c])])
# if inner[l, c] = np.mean(F[l-5 : l+5 , c-5:c+5]),
# then you should use a 10x10 filter
avg_filter = np.ones((10, 5)) / (10*5)
inner2 = convolve2d(F, avg_filter, mode='valid')
# should be very small (1.262e-13 for me)
print((inner2[:89, :89] - inner[5:94, 5:94]).sum())
The expression for outer is quite strange, because of this int(S[l, c]) offset that you add to your expression. I don't think you can represent this as a matrix computation.
So to replace your double for loop you can use from itertools import product which iterates over the cartesian product of two iterables, like this:
from itertools import product
for (l, c) in product(range(5, L - 5), range(5, C - 5)):
outer[l, c] = np.mean(F[l-5 : l+5 , c+5 : c+ 5 + int(S[l, c])])
From a signal processing standpoint, I'm not sure what is the outer matrix supposed to be. It would be easier to write faster code with the desired effect if you told us what you are trying to do.

Python, scipy - How to fit a curve using a piecewise function with a conditional parameter that also needs to be calculated?

As the title suggests, I'm trying to fit a piecewise equation to a large data set. The equations I would like to fit to my data are as follows:
y(x) = b, when x < c
else:
y(x) = b + exp(a(x-c)) - 1, when x >= c
There are multiple answers to how such an issue can be addressed, but as a Python beginner I can't figure out how to apply them to my problem:
Curve fit with a piecewise function?
Conditional curve fit with scipy?
The problem is that all variables (a,b and c) have to be calculated by the fitting algorithm.
Thank you for your help!
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
# Reduced Dataset
y = np.array([23.032, 21.765, 20.525, 21.856, 21.592, 20.754, 20.345, 20.534,
23.502, 21.725, 20.126, 21.381, 20.217, 21.553, 21.176, 20.976,
20.723, 20.401, 22.898, 22.02 , 21.09 , 22.543, 22.584, 22.799,
20.623, 20.529, 20.921, 22.505, 22.793, 20.845, 20.584, 22.026,
20.621, 23.316, 22.748, 20.253, 21.218, 23.422, 23.79 , 21.371,
24.318, 22.484, 24.775, 23.773, 25.623, 23.204, 25.729, 26.861,
27.268, 27.436, 29.471, 31.836, 34.034, 34.057, 35.674, 41.512,
48.249])
x = np.array([3756., 3759., 3762., 3765., 3768., 3771., 3774., 3777., 3780.,
3783., 3786., 3789., 3792., 3795., 3798., 3801., 3804., 3807.,
3810., 3813., 3816., 3819., 3822., 3825., 3828., 3831., 3834.,
3837., 3840., 3843., 3846., 3849., 3852., 3855., 3858., 3861.,
3864., 3867., 3870., 3873., 3876., 3879., 3882., 3885., 3888.,
3891., 3894., 3897., 3900., 3903., 3906., 3909., 3912., 3915.,
3918., 3921., 3924.])
# Simple exponential function without conditions (works so far)
def exponential_fit(x,a,b,c):
return b + np.exp(a*(x-c))
popt, pcov = curve_fit(exponential_fit, x, y, p0 = [0.1, 20,3800])
plt.plot(x, y, 'bo')
plt.plot(x, exponential_fit(x, *popt), 'r-')
plt.show()
You should change your function to something like
def exponential_fit(x, a, b, c):
if x >= c:
return b + np.exp(a*(x-c))-1
else:
return b
Edit: As chaosink pointed out in the comments, this approach no longer works as the the above function assumes that x is a scalar. However, curve_fit evaluates the function for array-like x. Consequently, one should use vectorised operations instead, see here for more details. To do so, one can either use
def exponential_fit(x, a, b, c):
return np.where(x >= c, b + np.exp(a*(x-c))-1, b)
or chaosink's suggestion in the comments:
def exponential_fit(x, a, b, c):
mask = (x >= c)
return mask * (b + np.exp(a*(x-c)) - 1) + ~mask * b
Both give:

Python partial derivative

I am trying to put numbers in a function that has partial derivatives but I can't find a correct way to do it,I have searched all the internet and I always get an error.Here is the code:
from sympy import symbols,diff
import sympy as sp
import numpy as np
from scipy.misc import derivative
a, b, c, d, e, g, h, x= symbols('a b c d e g h x', real=True)
da=0.1
db=0.2
dc=0.05
dd=0
de=0
dg=0
dh=0
f = 4*a*b+a*sp.sin(c)+a**3+c**8*b
x = sp.sqrt(pow(diff(f, a)*da, 2)+pow(diff(f, b)*db, 2)+pow(diff(f, c)*dc, 2))
def F(a, b, c):
return x
print(derivative(F(2 ,3 ,5)))
I get the following error: derivative() missing 1 required positional argument: 'x0'
I am new to python so maybe it's a stupid question but I would feel grateful if someone helped me.
You can find three partial derivatives of function foo by variables a, b and c at the point (2,3,5):
f = 4*a*b+a*sp.sin(c)+a**3+c**8*b
foo = sp.sqrt(pow(diff(f, a)*da, 2)+pow(diff(f, b)*db, 2)+pow(diff(f, c)*dc, 2))
foo_da = diff(foo, a)
foo_db = diff(foo, b)
foo_dc = diff(foo, c)
print(foo_da," = ", float(foo_da.subs({a:2, b:3, c:5})))
print(foo_db," = ", float(foo_db.subs({a:2, b:3, c:5})))
print(foo_dc," = ", float(foo_dc.subs({a:2, b:3, c:5})))
I have used a python package 'sympy' to perform the partial derivative. The point at which the partial derivative is to be evaluated is val. The argument 'val' can be passed as a list or tuple.
# Sympy implementation to return the derivative of a function in x,y
# Enter ginput as a string expression in x and y and val as 1x2 array
def partial_derivative_x_y(ginput,der_var,val):
import sympy as sp
x,y = sp.symbols('x y')
function = lambda x,y: ginput
derivative_x = sp.lambdify((x,y),sp.diff(function(x,y),x))
derivative_y = sp.lambdify((x,y),sp.diff(function(x,y),y))
if der_var == 'x' :
return derivative_x(val[0],val[1])
if der_var == 'y' :
return derivative_y(val[0],val[1])
input1 = 'x*y**2 + 5*log(x*y +x**7) + 99'
partial_derivative_x_y(input1,'y',(3,1))

Lambifying Nested Parametric Integrals

I have a loss function L(u1,...,un) that takes the form
L(u) = Integral( (I**2 + J**2) * h, (t,c,d) ) //h=h(t), I=I(t,u)
where
I = Integral( f, (x,a,b) ) // f=f(x,t,u)
J = Integral( g, (x,a,b) ) // g=g(x,t,u)
I want to numerically minimize L with scipy, hence I need to lambdify the expression.
However at this point in time lambdify does not natively support translating integrals. With some tricks one can get it to work with single parametric integrals, see Lambdify A Parametric Integral. However I don't see how the proposed solution could possibly be extended to this generalised case.
One idea that in principle should work is the following:
Take the computational graph defining L. Recursively, starting from the leaves, replace each symbolic operation with the corresponding numerical operation, expressed as a lambda function. However this would lead to an immense nesting of lambda function, which I suspect has a very bad influence on performance.
Ideally we would want to arrive at the same result as one would by hand crafting:
L = lambda u: quad(lambda t: (quad(lambda x: f,a,b)[0]**2
+ quad(lambda x: g,a,b)[0]**2)*h, c, d)[0]
MWE: (using code from old thread)
from sympy import *
from scipy.integrate import quad
import numpy as np
def integral_as_quad(function, limits):
x, a, b = limits
param = tuple(function.free_symbols - {x})
f = sp.lambdify((x, *param), function, modules=['numpy'])
return quad(f, a, b, args=param)[0]
x,a,b,t = sp.symbols('x a b t')
f = (x/t-a)**2
g = (x/t-b)**2
h = exp(-t)
I = Integral( f**2,(x,0,1))
J = Integral( g**2,(x,0,1))
K = Integral( (I**2 + J**2)*h,(t,1,+oo))
F = lambdify( (a,b), K, modules=['sympy',{"Integral": integral_as_quad}])
L = lambda a,b: quad(lambda t: (quad(lambda x: (x/t-a)**2,0,1)[0]**2
+ quad(lambda x: (x/t-b)**2,0,1)[0]**2)*np.exp(-t), 1, np.inf)[0]
display(F(1,1))
display(type(F(1,1)))
display(L(1,1))

Resources