How to find point where an unlinear function equals zero - python

I have an unlinear function from neuroscience. ad is a parameter and t is the time.
def alpha(t, ad):
if t < 0:
return 0
else:
return pow(ad, 2) * t * np.exp(-1 * ad * t)
With ad = 2 It rises from x = 0, increases to 0.7 and becomes near 0 at x=3.
I can find when this function is near or equal to 0 by iterating by intervals. But I just need to know where the function is near or equal to 0. I was wondering if there is any way to find it without iterating ex) intersecting with x = 0 function, or when derivative equals 0 ...

I presume that you want to find when the derivative is equal to zero. You can do that with sympy:
In [12]: from sympy import exp, Symbol, nsolve
In [13]: ad = 2
In [14]: t = Symbol('t')
In [15]: f = pow(ad, 2) * t * exp(-1 * ad * t)
In [16]: f
Out[16]:
-2⋅t
4⋅t⋅ℯ
In [17]: f.diff(t)
Out[17]:
-2⋅t -2⋅t
- 8⋅t⋅ℯ + 4⋅ℯ
In [18]: solve(f.diff(t), t)
Out[18]: [1/2]
EDIT: Your answer below suggests a different question from the one in the OP so I'll update this:
You want to find the zeros of this:
In [5]: ad = Symbol('ad')
In [6]: t = Symbol('t')
In [7]: epsilon = Symbol('epsilon')
In [8]: f = pow(ad, 2) * t * exp(-1 * ad * t) - epsilon
In [9]: f
Out[9]:
2 -ad⋅t
ad ⋅t⋅ℯ - ε
We can solve this analytically using solve:
In [10]: sol, = solve(f, t)
In [11]: sol
Out[11]:
⎛-ε ⎞
-W⎜───⎟
⎝ ad⎠
────────
ad
This answer is given in terms of the Lambert W function. You can substitute for ad and epsilon to get the answer for any particular values:
In [12]: sol.subs({ad:2, epsilon:0.01})
Out[12]: 0.00251259459155665
This is giving you the root near zero though because it's branch W0 of the Lambert W function. The other root is given in by branch W_{-1} and is
In [32]: -LambertW(-epsilon/ad, -1)/ad
Out[32]:
⎛-ε ⎞
-W⎜───, -1⎟
⎝ ad ⎠
────────────
ad
In [28]: (-LambertW(-epsilon/ad, -1)/ad).subs({ad:2, epsilon:0.01}).n()
Out[28]: 3.64199856754954
If you just want to solve for these numerically then you can use nsolve:
In [29]: nsolve(f.subs({ad:2, epsilon:0.01}), t, 0)
Out[29]: 0.00251259459155665
In [30]: nsolve(f.subs({ad:2, epsilon:0.01}), t, 1)
Out[30]: 3.64199856754954

I was able to find a solution using Oscar's answer.
The function was exponential, so it never reaches 0 after 0
I just pulled the function a bit with a minus epsilon
(epsilon is user defined)
There will be two zeros, and I just need the second one.
from sympy import exp, Symbol, solve
epsilon = 0.01
ad = 2
t = Symbol('t')
f = pow(ad, 2) * t * exp(-1 * ad * t) - epsilon
print(solve([f], t, dict=True, quick=True))
#print(solve([t > 0, f], t, dict=True, quick=True)) i get an error if i use this
But this solution to finding a near-zero x took much more time than just iterating by interval of 0.01 over the function.
from sympy import exp, Symbol, solve, Piecewise
import numpy as np
epsilon = 0.01
ad = 2
t = Symbol('t')
f = pow(ad, 2) * t * exp(-1 * ad * t) - epsilon
#print(solve([f], t, dict=True, quick=True))
def alpha(t, ad):
if t < 0:
return 0
else:
return pow(ad, 2) * t * np.exp(-1 * ad * t)
def find(function, farg, arange, epsilon):
xs = []
for x in arange:
if function(x, **farg) < epsilon:
xs.append(x)
return xs
if __name__ == "__main__":
import timeit
print(timeit.timeit("solve([f], t, dict=True)",
setup="from __main__ import solve, f, t",
number=100))
print(timeit.timeit("solve([f], t, dict=True, quick=True)",
setup="from __main__ import solve, f, t",
number=100))
print(timeit.timeit("find(alpha, {'ad':ad}, np.arange(0, 4, 0.01), 0.01)",
setup="from __main__ import find, alpha, ad , t, f, np",
number=100))
Outputs
24.860103617
24.020882552
0.10911201300000073

Related

The diff function in sympy doesn't return a number?

I'm a newbie learning python. I have a question, can you guys help me? This is my code:
from sympy import *
def test(f, g, a):
f1 = f.subs(x, g)
df1 = diff(f1, x).subs(x, a)
return df1
print(test((2*(x**2) + abs(x + 1)), (x - 1), -1))
Result: -Subs(Derivative(re(x), x), x, -1) - 8
I'm taking the derivative of f(g(x)) with: f = 2(x^2) + abs(x + 1), g = x - 1 and x = -1. When I use diff to calculate the result is -Subs(Derivative(re(x), x), x, -1) - 8, but when I use the formula lim x->x0 (f(x) - f(x0))/(x - x0) I got result is -9. I also tried using a calculator to calculate and the result -9 is the correct result. Is there a way to make diff return -9? Anyone have any help or can give some pointers?
Thanks!
Whenever I see a re or im appear when I didn't expect them, I am inclined to make the symbols real:
>>> from sympy import *
>>> def test(f, g, a):
... f1 = f.subs(x, g)
... df1 = diff(f1, x).subs(x, a)
... return df1
...
>>> var('x',real=True)
x
>>> print(test((2*(x**2) + abs(x + 1)), (x - 1), -1))
-9
Since I'm still a relative beginner to sympy I like to view intermediate results (I even like to do that with numpy which I know much better). Running in isympy:
In [6]: diff(f1,x)
Out[6]:
⎛ d d ⎞
⎜re(x)⋅──(re(x)) + im(x)⋅──(im(x))⎟⋅sign(x)
⎝ dx dx ⎠
4⋅x - 4 + ───────────────────────────────────────────
x
That expression contains unevaluate d/dx and the distinction between the real and imaginary parts of x.
Restricting x to real as suggested in the other answer produces:
In [19]: diff(exp,x)
Out[19]: 4⋅x + sign(x + 1)

How can I plot a 3D graph of a multivariate integral function, and find its global minima

I have a cost function f(r, Q), which is obtained in the code below. The cost function f(r, Q) is a function of two variables r and Q. I want to plot the values of the cost function for all values of r and Q in the range given below and also find the global minimum value of f(r, Q).
The range of r and Q are respectively :
0 < r < 5000
5000 < Q < 15000
The plot should be in r, Q and f(r,Q) axis.
Code for the cost function:
from numpy import sqrt, pi, exp
from scipy import optimize
from scipy.integrate import quad
import numpy as np
mean, std = 295, 250
l = 7
m = 30
p = 15
w = 7
K = 100
c = 5
h = 0.001 # per unit per day
# defining Cumulative distribution function
def cdf(x):
cdf_eqn = lambda t: (1 / (std * sqrt(2 * pi))) * exp(-(((t - mean) ** 2) / (2 * std ** 2)))
cdf = quad(cdf_eqn, -np.inf, x)[0]
return cdf
# defining Probability density function
def pdf(x):
return (1 / (std * sqrt(2 * pi))) * exp(-(((x - mean) ** 2) / (2 * std ** 2)))
# getting the equation in place
def G(r, Q):
return K + c * Q \
+ w * (quad(cdf, 0, Q)[0] + quad(lambda x: cdf(r + Q - x) * cdf(x), 0, r)[0]) \
+ p * (mean * l - r + quad(cdf, 0, r)[0])
def CL(r, Q):
return (Q - r + mean * l - quad(cdf, 0, Q)[0]
- quad(lambda x: cdf(r + Q - x) * cdf(x), 0, r)[0]
+ quad(cdf, 0, r)[0]) / mean
def I(r, Q):
return h * (Q + r - mean * l - quad(cdf, 0, Q)[0]
- quad(lambda x: cdf(r + Q - x) * cdf(x), 0, r)[0]
+ quad(cdf, 0, r)[0]) / 2
def f(params):
r, Q = params
TC = G(r, Q)/CL(r, Q) + I(r, Q)
return TC
How to plot this function f(r,Q) in a 3D plot and also get the global minima or minimas and values of r and Q at that particular point.
Additionally, I already tried using scipy.optimize.minimize to minimise the cost function f(r, Q) but the problem I am facing is that, it outputs the results - almost same as the initial guess given in the parameters for optimize.minimize. Here is the code for minimizing the function:
initial_guess = [2500., 10000.]
result = optimize.minimize(f, initial_guess, bounds=[(1, 5000), (5000, 15000)], tol=1e-3)
print(result)
Output:
fun: 2712.7698818644253
hess_inv: <2x2 LbfgsInvHessProduct with dtype=float64>
jac: array([-0.01195986, -0.01273293])
message: b'CONVERGENCE: REL_REDUCTION_OF_F_<=_FACTR*EPSMCH'
nfev: 6
nit: 1
status: 0
success: True
x: array([ 2500.01209628, 10000.0127784 ])
The output x: array([ 2500.01209628, 10000.0127784 ]) - Which I doubt is the real answer and also it is almost same as the initial guess provided. Am I doing anything wrong in minimizing or is there any other way to do it? So I want to plot the cost function and look around for myself.
It could be great if I can have an interactive plot to play around with
My answer is concerned only with plotting but in the end I'll comment on the issue of minimax.
For what you need a 3D surface plot is, imho, overkill, I'll show you instead show the use of contourf and contour to have a good idea of what is going on with your function.
First, the code — key points:
your code, as is, cannot be executed in a vector context, so I wrote an explicit loop to compute the values,
due to Matplotib design, the x axis of matrix data is associated on columns, this has to be accounted for,
the results of the countour and contourf must be saved because they are needed for the labels and the color bar, respectively,
no labels or legends because I don't know what you are doing.
That said, here it is the code
import matplotlib.pyplot as plt
import numpy as np
from numpy import sqrt, pi, exp
from scipy.integrate import quad
mean, std = 295, 250
l, m, p = 7, 30, 15
w, K, c = 7, 100, 5
h = 0.001 # per unit per day
# defining Cumulative distribution function
def cdf(x):
cdf_eqn = lambda t: (1 / (std * sqrt(2 * pi))) * exp(-(((t - mean) ** 2) / (2 * std ** 2)))
cdf = quad(cdf_eqn, -np.inf, x)[0]
return cdf
# defining Probability density function
def pdf(x):
return (1 / (std * sqrt(2 * pi))) * exp(-(((x - mean) ** 2) / (2 * std ** 2)))
# getting the equation in place
def G(r, Q):
return K + c * Q \
+ w * (quad(cdf, 0, Q)[0] + quad(lambda x: cdf(r + Q - x) * cdf(x), 0, r)[0]) \
+ p * (mean * l - r + quad(cdf, 0, r)[0])
def CL(r, Q):
return (Q - r + mean * l - quad(cdf, 0, Q)[0]
- quad(lambda x: cdf(r + Q - x) * cdf(x), 0, r)[0]
+ quad(cdf, 0, r)[0]) / mean
def I(r, Q):
return h * (Q + r - mean * l - quad(cdf, 0, Q)[0]
- quad(lambda x: cdf(r + Q - x) * cdf(x), 0, r)[0]
+ quad(cdf, 0, r)[0]) / 2
# pulling it all together
def f(r, Q):
TC = G(r, Q)/CL(r, Q) + I(r, Q)
return TC
nr, nQ = 6, 11
r = np.linspace(0, 5000, nr)
Q = np.linspace(5000, 15000, nQ)
z = np.zeros((nr, nQ)) # r ←→ y, Q ←→ x
for i, ir in enumerate(r):
for j, jQ in enumerate(Q):
z[i, j] = f(ir, jQ)
print('%2d: '%i, ','.join('%8.3f'%v for v in z[i]))
fig, ax = plt.subplots()
cf = plt.contourf(Q, r, z)
cc = plt.contour( Q, r, z, colors='k')
plt.clabel(cc)
plt.colorbar(cf, orientation='horizontal')
ax.set_aspect(1)
plt.show()
and here the results of its execution
$ python cost.py
0: 4093.654,3661.777,3363.220,3120.073,2939.119,2794.255,2675.692,2576.880,2493.283,2426.111,2359.601
1: 4072.865,3621.468,3315.193,3068.710,2887.306,2743.229,2626.065,2528.934,2447.123,2381.802,2316.991
2: 4073.852,3622.443,3316.163,3069.679,2888.275,2744.198,2627.035,2529.905,2448.095,2382.775,2317.965
3: 4015.328,3514.874,3191.722,2939.397,2758.876,2618.292,2505.746,2413.632,2336.870,2276.570,2216.304
4: 3881.198,3290.628,2947.273,2694.213,2522.845,2394.095,2293.867,2213.651,2148.026,2098.173,2047.140
5: 3616.675,2919.726,2581.890,2352.015,2208.814,2106.289,2029.319,1969.438,1921.555,1887.398,1849.850
$
I can add that global minimum and global maximum are in the corners, while there are two sub-horizontal lines of local minima (lower line) and local maxima (upper line) in the approximate regions r ≈ 1000 and r ≈ 2000.

passing numpy ndarray as inputs of a fsolve function

I want to numerically solve a system of nonlinear equations and pass numpy ndarrays as inputs. Consider the arbitrary code below:
import numpy as np
from scipy.optimize import fsolve
def eqs(A, B, C, D):
eq1 = (A - B * np.sin(C)).tolist()
eq2 = [5 * B + D * np.sum(A * np.cos(C))]
return eq1 + eq2
n = 3
A = np.zeros((n))
A0 = np.random.rand(n)
B = 0.0
B0 = np.random.rand(1)[0]
C = np.random.rand(n)
D = np.random.rand(1)[0]
sol = fsolve(func = eqs, x0 = [A0, B0], args = [C, D])
which leads to
missing required positional arguments
error and changing the function to:
def eqs(A, B, C, D):
eq1 = A - B * np.sin(C)
eq2 = C[0] * B + D * np.sum(A * np.cos(C))
return [eq1, eq2]
also doesn't help. However, I highly doubt that the error has anything to do with passing ndarrays. One approach could be to change all the ndarrays to python lists back and forth. But then I would not be able to use numpy's vectorized functions like np.sin()...
I would appreciate if you could help me know how this should be done.
P.S. Equations above are just arbitrary and they may not have solutions at all.
Check if this solve your equation:
import numpy as np
from scipy.optimize import fsolve
def eqs(X, Y):
A, B = X[:3], X[3]
C, D = Y[:3], Y[3]
eq1 = A - B * np.sin(C)
eq2 = C[0] * B + D * np.sum(A * np.cos(C))
return np.append(eq1, eq2)
n = 3
A = np.zeros((n))
A0 = np.random.rand(n)
B = 0.0
B0 = np.random.rand(1)[0]
C = np.random.rand(n)
D = np.random.rand(1)[0]
sol = fsolve(func = eqs, x0 = np.append(A0, B0), args = np.append(C, D))
sol
Output:
array([ 0.e+000, -1.e-323, 5.e-324, -1.e-323])
These scipy.optimize functions require a function with a signature like
f(x, *args)
x is a array (often 1d) that the solver will vary; args is a tuple of arguments that are just passed through from the outside.
Change your eqs to fit this pattern
In [11]: def eqs(X, C, D):
...: A, B = X[:-1], X[-1]
...: eq1 = (A - B * np.sin(C)).tolist()
...: eq2 = [5 * B + D * np.sum(A * np.cos(C))]
...: return eq1 + eq2
...: n = 3
...: A0 = np.random.rand(n)
...: B0 = np.random.rand(1)
...:
...: C = np.random.rand(n)
...: D = np.random.rand(1)
Make a test call to eqs:
In [12]: eqs(np.concatenate((A0,B0)),C,D)
Out[12]:
[-0.28460532658572657,
-0.03649115738682615,
0.7625781482352719,
array([5.46430853])]
Now try it in the fsolve:
In [13]: fsolve(eqs, np.concatenate((A0,B0)), args=(C,D))
Out[13]: array([0., 0., 0., 0.])

Python integration of large array of coefficient

I'm trying to optimize simple integration in python which looks something like
from scipy import integrate
import numpy as np
from scipy.special import kv
import time
#Example function
def integrand(x, a, b, c):
return a * (x ** (-b)) * (np.sqrt(x ** (c) + 1) - 1)
#Real Function that I want to calculate
def Bes(xx):
return integrate.quad(lambda x: kv(5./3.,x), xx,np.inf)
def F(x,a,b,c,d,e,f):
zx = 1/((x**2.+1)*a)
feq = e*x**(f)
if (x>c):
feq *= c/x * np.exp(-(x/d)**2.)
return b*Bes(zx)*feq*x**2.
start = time.time()
array_length = 10
a = np.random.rand(array_length)+3.
b = np.random.rand(array_length)+1.
c = np.random.rand(array_length)
d = (np.random.rand(array_length)+1)*100.
e = np.random.rand(array_length)*100.
f = np.random.rand(array_length)
inte = np.array([])
for i in range(array_length):
result = integrate.quad(lambda x: F(x, a[i], b[i], c[i],d[i],e[i],f[i]),0.01,100000.)
inte = np.append(inte,result[0])
print("For array length = %i" % array_length)
print("Time = %.2f [sec]" %(time.time()-start))
But the problems that I'm facing are
a, b, c are array with length > 10^7 (same length)
integration range of x starts at 0.01 and extends to infinite
Integration at the small x (like [0.01, 1]) is very important and needs small step.
I want to integrate this function on each coefficient value and returns the entire array of integration as the result (length ~ 10^7), efficiently.
What kind of tools should I use?
(+) I just changed my code from simple example to actual integration form that I need to solve. Sorry for making confusion.
I suspected that this integral would converge for certain values of b and c, so I tried to evaluate this using Sympy:
import sympy
sympy.init_printing()
a, b, c = sympy.symbols('a, b, c', positive=True)
x = sympy.Symbol('x', positive=True)
sympy.integrate(a*(x**(-b))*(sympy.sqrt(x**c+1)-1), (x, 0, sympy.oo))
This means that you should be able to obtain the correct results with this code as long as your coefficients pass the check function.
from numpy import sqrt, pi
from scipy.special import gamma
def check(a, b, c):
assert (-(-b + 1)/c < 1)
assert (1/2 - (-b + 1)/c > 1)
assert (1 - (-b + 1)/c > 1)
def result(a, b, c):
return a*gamma(-b/c + 1 + 1/c)*gamma(b/c - 1/2 - 1/c)/(2*sqrt(pi)*(b - 1))

Use Python SciPy to solve ODE

Now I face some problem when I use scipy.integrate.ode.
I want to use spectral method (fourier transform) solve a PDE including dispersive and convection term, such as
du/dt = A * d^3 u / dx^3 + C * du/dx
Then from fourier transform this PDE will convert to a set of ODEs in complex space (uk is complex vector)
duk/dt = (A * coeff^3 + C * coeff) * uk
coeff = (2 * pi * i * k) / L
k is wavenumber, (e.g.. k = 0, 1, 2, 3, -4, -3, -2, -1)
i^2 = -1,
L is length of domain.
When I use r = ode(uODE).set_integrator('zvode', method='adams'), python will warn like:
c ZVODE-- At current T (=R1), MXSTEP (=I1) steps
taken on this call before reaching TOUT
In above message, I1 = 500
In above message, R1 = 0.2191432098050D+00
I feel it is because the time step I chosen is too large, however I cannot decrease time step as every step is time consuming for my real problem. Do I have any other way to resolve this problem?
Did you consider solving the ODEs symbolically? With Sympy you can type
import sympy as sy
sy.init_printing() # use IPython for better results
from sympy.abc import A, C, c, x, t # variables
u = sy.Function(b'u')(x,t)
eq = sy.Eq(u.diff(t), c*u)
sl1 = sy.pde.pdsolve(eq, u)
print("The solution of:")
sy.pprint(eq)
print("was determined to be:")
sy.pprint(sl1)
print("")
print("Substituting the coefficient:")
k,L = sy.symbols("k L", real=True)
coeff = (2 * sy.pi * sy.I * k) / L
cc = (A * coeff**3 + C * coeff)
sl2 = sy.simplify(sl1.replace(c, cc))
sy.pprint(sl2)
gives the following output:
The solution of:
∂
──(u(x, t)) = c⋅u(x, t)
∂t
was determined to be:
c⋅t
u(x, t) = F(x)⋅ℯ
Substituting the coefficient:
⎛ 2 2 2⎞
-2⋅ⅈ⋅π⋅k⋅t⋅⎝4⋅π ⋅A⋅k - C⋅L ⎠
──────────────────────────────
3
L
u(x, t) = F(x)⋅ℯ
Note that F(x) depends on your initial values of u(x,t=0), which you need to provide.
Use sl2.rhs.evalf() to substitute in numbers.

Categories