How to use positional arguments appropriately for a complex problem - python

I am revisiting a school project, which I did not complete to my satisfaction. Namely, I wrote an algorithm that takes an ALMOST arbitrary size set of equations and solves them iteratively. The problem being the "almost" part. Essentially, it must have at least two equations, and will not solve for a single one. This is because, I believe, that I don't understand how to use positional arguments correctly.
Below, in the main method, I define two functions y_prime and z_prime. If I pass them both, I get a beautiful graph of my solutions. But, if I only pass y_prime along with its initial conditions and the solution vector to the rungekutta() function, things go haywire:
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
def rungekutta(dt, y, t, *funcs):
"""
The following code was written in order to
reproduce the classic 4th order Runge-Kutta numerical
method of solving a system of differential equations.
The aim was to not only apply this to the budworm deforestation
model developed by Ludwig et al, but also to create an algorithm
that is generic enough to accept a wide range of ODEs and
systems of ODEs.
:param dt: time step "Delta t"
:param y: The solution vector at the last time step
:param t: The time at the last time step
:param funcs: the vector field dy/dt = f(t,y)
:return: The solution vector for the next time step
"""
k1 = [dt * f(*y, t) for f in funcs]
args = [y_n + 0.5 * k_1 for y_n, k_1 in zip((*y, t), (*k1, dt))]
k2 = [dt * f(*args) for f in funcs]
args = [y_n + 0.5 * k_2 for y_n, k_2 in zip((*y, t), (*k2, dt))]
k3 = [dt * f(*args) for f in funcs]
args = [y_n + k_3 for y_n, k_3 in zip((*y, t), (*k3, dt))]
k4 = [dt * f(*args) for f in funcs]
return [y_n + (k_1 + 2 * k_2 + 2 * k_3 + k_4) / 6 for y_n, k_1, k_2, k_3, k_4 in
zip(y, k1, k2, k3, k4)]
if __name__ == '__main__':
def y_prime(y, z, t):
return -t * y
def z_prime(y, z, t):
return z
t_0 = -10
t_n = 10
dt = .05
steps = int((t_n - t_0) / dt)
y_soln = [0] * steps
z_soln = [0] * steps
time = np.arange(t_0, t_n, dt)
y_soln[0] = 1.928749848e-22
z_soln[0] = .0000453999297625
for i in np.arange(1, steps):
y_soln[i] = rungekutta(dt, y_soln[i-1], time[i-1], y_prime)
The first error I received, when trying to pass a single equation was:
Traceback (most recent call last):
File "C:/Users/wesle/PycharmProjects/Budworms/RK4v2.py", line 57, in <module>
y_soln[i] = rungekutta(dt, y_soln[i-1], time[i-1], y_prime, z_prime)
File "C:/Users/wesle/PycharmProjects/Budworms/RK4v2.py", line 23, in rungekutta
k1 = [dt * f(*y, t) for f in funcs]
File "C:/Users/wesle/PycharmProjects/Budworms/RK4v2.py", line 23, in <listcomp>
k1 = [dt * f(*y, t) for f in funcs]
TypeError: y_prime() argument after * must be an iterable, not float
This was because, I think, I have "y_soln" as a positional argument, but now there is only one and it is no longer iterable. So, I made it a tuple of 1 when I passed it in the main method:
for i in np.arange(1, steps):
y_soln[i] = rungekutta(dt, (y_soln[i-1],), time[i-1], y_prime)
That bit me in the butt, however, because now I am passing a tuple into my y_prime equation, when what it really needs is a float:
Traceback (most recent call last):
File "C:/Users/wesle/PycharmProjects/Budworms/RK4v2.py", line 57, in <module>
y_soln[i] = rungekutta(dt, (y_soln[i-1],), time[i-1], y_prime)
File "C:/Users/wesle/PycharmProjects/Budworms/RK4v2.py", line 23, in rungekutta
k1 = [dt * f(*y, t) for f in funcs]
File "C:/Users/wesle/PycharmProjects/Budworms/RK4v2.py", line 23, in <listcomp>
k1 = [dt * f(*y, t) for f in funcs]
File "C:/Users/wesle/PycharmProjects/Budworms/RK4v2.py", line 38, in y_prime
return -t * y
TypeError: can't multiply sequence by non-int of type 'numpy.float64'
My only work-around so far has been to solve an extra, random equation like $y= y'$ in addition to whatever equation I'm interested in. This seems pretty inefficient though.
So, it seems like I'm damned if I do, or damned if I don't. Is there any remedy to this?
EDIT If you want to see the code actually work, replace this:
for i in np.arange(1, steps):
y_soln[i] = rungekutta(dt, (y_soln[i-1],), time[i-1], y_prime)
with the instance where I pass both equations and their solution vectors to the function:
for i in np.arange(1, steps):
y_soln[i], z_soln[i] = rungekutta(dt, (y_soln[i-1], z_soln[i-1]), time[i-1], y_prime, z_prime)

My solution ended up being to convert all lists to numpy arrays, which allowed me to take advantage of the built in element-wise scalar addition and multiplication. This made computing the "k" values much less cumbersome and convoluted:
def rk4(dt, t, field, y_0):
"""
:param dt: float - the timestep
:param t: array - the time mesh
:param field: method - the vector field y' = f(t, y)
:param y_0: array - contains initial conditions
:return: ndarray - solution
"""
# Initialize solution matrix. Each row is the solution to the system
# for a given time step. Each column is the full solution for a single
# equation.
y = np.asarray(len(t) * [y_0])
for i in np.arange(len(t) - 1):
k1 = dt * field(t[i], y[i])
k2 = dt * field(t[i] + 0.5 * dt, y[i] + 0.5 * k1)
k3 = dt * field(t[i] + 0.5 * dt, y[i] + 0.5 * k2)
k4 = dt * field(t[i] + dt, y[i] + k3)
y[i + 1] = y[i] + (k1 + 2 * k2 + 2 * k3 + k4) / 6
return y

Related

Differents results from create function in a different way - only length-1 arrays can be converted to Python scalars

I have defined the following functions in python:
from math import *
import numpy as np
import cmath
def BSM_CF(u, s0, T, r, sigma):
realp = -0.5*u**2*sigma**2*T
imagp = u*(s0+(r-0.5*sigma**2)*T)
zc = complex(realp, imagp)
return cmath.exp(zc)
def BSM_characteristic_function(v, x0, T, r, sigma):
cf_value = np.exp(((x0 / T + r - 0.5 * sigma ** 2) * 1j * v -
0.5 * sigma ** 2 * v ** 2) * T)
return cf_value
Parameters:
alpha = 1.5
K = 90
S0 = 100
T = 1
r = 0.05
sigma = 0.2
k = np.log(K / S0)
s0 = np.log(S0 / S0)
g = 1 # factor to increase accuracy
N = 2 ** 2
eta = 0.15
eps = (2*np.pi)/(N*eta)
b = 0.5 * N * eps - k
u = np.arange(1, N + 1, 1)
vo = eta * (u - 1)
v = vo - (alpha + 1) * 1j
BSMCF = BSM_characteristic_function(v, s0, T, r, sigma)
BSMCF_v2 = BSM_CF(0, s0, T, r, sigma)
print(BSMCF)
print(BSMCF_v2)
Both are the same functions. But, I get different results. How can I fix the function BSM_CF to get the same result from the function BSM_characteristic_function? The idea is get an array with len 4 values as in the funtion BSM_characteristic_function
Your calls are not identical. You are passing v in the first call and 0 in the second call. If I pass 0 for both, the results are identical. If I pass v, it complains because you can't call complex on a vector.
Numeric computation is Not always identical to symbolic algebra. For the first formula, you use complex computation as an alternative, which could result rounding errors in complex part. I came across such mistakes quite often as I used Mathematica, which loves to transfer a real formula to a complex one before doing the numeric computation.

How to fix fitting with scipy.optimize.minimize for derivation condition

I am trying to fit a function cav=p(T,x) with the condition that the derivation after x of cav for constant T is always positive dp/dx (for constant T) > 0. The data of x, T and p are from excel sheets. z are my coefficients I am trying to get.
I've used the solution from here Fitting with constraints on derivative Python as a template. Here is my code as it is right now with the providing error message:
import pandas as pd
import os
from scipy.optimize import minimize
import numpy as np
df = pd.read_excel(os.path.join(os.path.dirname(__file__), "./data.xlsx"))
T = np.array(df['T'], dtype=float)
x = np.array(df['x'], dtype=float)
p = np.array(df['p'], dtype=float)
p_s = 67
def cav(z,T,x): #my function
return x * p_s + x * (1 - x) * (z[0] + z[1] * T + z[2] * T ** 2 + z[3] * x + z[4] * x * T + z[5] * x * T ** 2) * p_s
def resid(p,T,x):
return ((p-cav(T,x))**2).sum()
def constr(z):
return np.gradient(cav(z,x,T))
con1 = {'type': 'ineq', 'fun': constr}
z0 = np.array([0,0,0,0,0,0], dtype=float)
res = minimize(resid,z0, args=(p,T,x), method='cobyla',options={'maxiter':50000}, constraints=con1)
And the Error:
TypeError: resid() takes 3 positional arguments but 4 were given
I don't understand what exactly do I have to put in as arguments for the three def. Thanks for any help!
The error is because you are passing 3 arguments to resid in addition to the initial guess z0.
Thus, the line which has to change is:
res = minimize(resid,z0, args=(T,x), method='cobyla',options={'maxiter':50000}, constraints=con1)
Another issue in your code is:
def resid(p,T,x):
return ((p-cav(T,x))**2).sum()
Your method cav takes three arguments but you pass only two. So this probably should be changed to:
def resid(p,T,x):
return ((p-cav(p,T,x))**2).sum()

Explicit Euler method doesn't behave how I expect

I have implemented the following explicit euler method in python:
def explicit_euler(df, x0, h, N):
"""Solves an ODE IVP using the Explicit Euler method.
Keyword arguments:
df - The derivative of the system you wish to solve.
x0 - The initial value of the system you wish to solve.
h - The step size.
N - The number off steps.
"""
x = np.zeros(N)
x[0] = x0
for i in range(0, N-1):
x[i+1] = x[i] + h * df(x[i])
return x
Following the article on wikipedia I can plot the function and verify that I get the same plot: . I believe that here the method I have written is working correctly.
Next I tried to use it to solve the last system given on this page and instead of the plot shown there I obtain this:
I am not sure why my plot doesn't match the one shown on the webpage. The explicit euler method seems to work fine when I use it to solve systems where the slope doesn't change, but for an oscillating function it never seems to mimic it at all. Not even showing the expected error gain as indicated on the linked webpage. I am not sure what is wrong with the method I have implemented.
Here is the code used for plotting and the derivative:
def g(t):
return -0.5 * np.exp(t * 0.5) * np.sin(5 * t) + 5 * np.exp(t * 0.5)
* np.cos(5 * t)
h = 0.001
x0 = 0
tn = 4
N = int(tn / h)
x = ee.explicit_euler(f, x0, h, N)
t = np.arange(0, tn, h)
fig = plt.figure()
plt.plot(t, x, label="Explicit Euler")
plt.plot(t, (np.exp(0.5 * t) * np.sin(5 * t)), label="Analytical
solution")
#plt.plot(t, np.exp(0.5 * t), label="Analytical solution")
plt.xlabel('Timesteps t')
plt.ylabel('x(t)=e^(0.5*t) * sin(5*t)')
plt.legend()
plt.grid()
plt.show()
Edit:
As requested here is the current equation I am applying the method to:
y'-y=-0.5*e^(t/2)*sin(5t)+5e^(t/2)*cos(5t)
Where y(0)=0.
I would like to make clear however that this behaviour doesn't occur just for this equation but all equations where the slope has a change in sign, or oscillating behaviour.
Edit 2:
Ok thanks. Yes the code below does indeed work. But I have one further question. In the simple example I had for the exponential function, I had defined a method:
def f(x):
return x
for the system f'(x)=x. This gave the output of my first graph which looks correct. I then defined another function:
def k(x):
return cos(x)
for the system f'(x)=cos(x), this does not give expected output. But when I change the function definition to
def k(t, x):
return cos(t)
I get the expected output. If I change my function
def f(t, x):
return t
I get an incorrect output. Am I always actually evaluating the function at a time step and is it just by chance for the system x'=x that at each time step the value is just the value of x?
I had understood that the Euler method used the value of the previously calculated value in order to get the next value. But if I run code for my function k(x)=cos(x), I get output pictured below, which must be incorrect. This now uses the updated code you provided.
def k(t, x):
return np.cos(x)
h = 0.1 # Step size
x0 = (0, 0) # Initial point of iteration
tn = 10 # Time step to iterate to
N = int(tn / h) # Number of steps
x = ee.explicit_euler(k, x0, h, N)
t = np.arange(0, tn, h)
The problem is that you have incorrectly raised the function g, you want to solve the equation:
From where we observe that:
y' = y -0.5*e^(t/2)*sin(5t)+5e^(t/2)*cos(5t)
Then we define the function f(t, y) = y -0.5*e^(t/2)*sin(5t)+5e^(t/2)*cos(5t) as:
def f(t, y):
return y -0.5 * np.exp(t * 0.5) * np.sin(5 * t) + 5 * np.exp(t * 0.5) * np.cos(5 * t)
The initial point of iteration is f0=(t(0), y(0)):
f0 = (0, 0)
Then from Euler's equations:
def explicit_euler(df, x0, h, N):
"""Solves an ODE IVP using the Explicit Euler method.
Keyword arguments:
df - The derivative of the system you wish to solve.
x0 - The initial value of the system you wish to solve.
h - The step size.
N - The number off steps.
"""
x = np.zeros(N)
t, x[0] = x0
for i in range(0, N-1):
x[i+1] = x[i] + h * df(t ,x[i])
t += h
return x
Complete Code:
def explicit_euler(df, x0, h, N):
"""Solves an ODE IVP using the Explicit Euler method.
Keyword arguments:
df - The derivative of the system you wish to solve.
x0 - The initial value of the system you wish to solve.
h - The step size.
N - The number off steps.
"""
x = np.zeros(N)
t, x[0] = x0
for i in range(0, N-1):
x[i+1] = x[i] + h * df(t ,x[i])
t += h
return x
def df(t, y):
return -0.5 * np.exp(t * 0.5) * np.sin(5 * t) + 5 * np.exp(t * 0.5) * np.cos(5 * t) + y
h = 0.001
f0 = (0, 0)
tn = 4
N = int(tn / h)
x = explicit_euler(df, f0, h, N)
t = np.arange(0, tn, h)
fig = plt.figure()
plt.plot(t, x, label="Explicit Euler")
plt.plot(t, (np.exp(0.5 * t) * np.sin(5 * t)), label="Analytical solution")
#plt.plot(t, np.exp(0.5 * t), label="Analytical solution")
plt.xlabel('Timesteps t')
plt.ylabel('x(t)=e^(0.5*t) * sin(5*t)')
plt.legend()
plt.grid()
plt.show()
Screenshot:
Dump y' and what is on the right side is what you should place in the df function.
We will modify the variables to maintain the same standard for the variables, and will y be the dependent variable, and t the independent variable.
Equation 2: In this case the equation f'(x)=cos(x) will be rewritten to:
y'=cos(t)
Then:
def df(t, y):
return np.cos(t)
In conclusion, if we have an equation of the following form:
y' = f(t, y)
Then:
def df(t, y):
return f(t, y)

scipy.integrate.quad gives TypeError: integer argument expected, got float

I am trying to do Fourier series with numpy. I am trying to write functions as they're defined here
But I am having trouble already at defining a0.
# "M1(t)" function definition.
def M1(t, *args):
tau, M0 = args
omega = 2 * np.pi / tau
return (2 * M0 + M0 * np.sin(omega * t - 2 / 3 * np.pi) +
M0 * np.sin(omega * t - 4/ 3 * np.pi))
# "M2(t)" function definition.
def M2(t, *args):
tau, M0 = args
omega = 2 * np.pi / tau
return (3 * M0 + M0 * np.sin(omega * t) + M0 * np.sin(omega * t - 2 / 3 * np.pi) +
M0 * np.sin(omega * t - 4/ 3 * np.pi))
def a0(tau, *args):
# limits of integrals; a = lower of 1st integral;
# b = higher of 1st and lower od 2nd integral;
# c = higher of 2nd integral
a, b, c = 0, tau / 2, tau
i1, err1 = quad(M1, a, b, *args)
i2, err2 = quad(M2, b, c, *args)
return 2 / tau * (i1 + i2)
When I run this code I get the following error:
TypeError: integer argument expected, got float
As requested, traceback of error:
Traceback (most recent call last):
File "C:/Users/Alex/Documents/Faks/Magisterij/1. letnik/VD/2. seminar/periodicno_vzbujanje.py", line 86, in <module>
a0 = a0(parameters[0], *parameters)
File "C:/Users/Alex/Documents/Faks/Magisterij/1. letnik/VD/2. seminar/periodicno_vzbujanje.py", line 41, in a0
i1, err1 = quad(M1, a, b, *args)
File "C:\Users\Alex\Anaconda3\lib\site-packages\scipy\integrate\quadpack.py", line 315, in quad
points)
File "C:\Users\Alex\Anaconda3\lib\site-packages\scipy\integrate\quadpack.py", line 380, in _quad
return _quadpack._qagse(func,a,b,args,full_output,epsabs,epsrel,limit)
TypeError: integer argument expected, got float
Extra question: how can I pass combined function to quad? For example: M1(t, *args) * np.cos(omega * t)? Do I have to define it as a new function and then pass it in or is there quicker way? Because I feel it's kind of redundant to type 4 extra functions.
UPDATE:
I realized I've been passing aditional arguments wrong the whole time.
I changed i2, err2 = quad(M2, b, c, *args) to i2, err2 = quad(M2, b, c, args). However now I get the following error:
ValueError: not enough values to unpack (expected 2, got 1).
With your M1
In [202]: M1(0,1,1)
Out[202]: 1.9999999999999996
In [203]: integrate.quad(M1,0,1,(1,1))
Out[203]: (2.0, 2.220446049250313e-14)
In [204]: M1(0,*(1,1))
Out[204]: 1.9999999999999996
The args tuple should look like what you'd pass to M1 with the *() syntax.

curve_fit: calculations with x before fit

I'm using the curve_fit function of the scipy package to fit some experimental data. I wrote my objective function like that:
def binding21(G0, eps1, eps2, K1, K2):
A = K1 * K2
B = K1 * (2 * K2 * H0 - K2 * G0 + 1)
C = K1 * (H0 - G0) + 1
D = G0
roots = np.roots([A, B, C, -D])
roots = [value for value in roots if np.isreal(value)]
G = max(roots)
y = (eps1 * H0 * K1 * G + eps2 * H0 * K1 * K2 * G**2) / \
(1 + K1 * G + K1 * K2 * G**2)
return y
G0 is my array of x values. However, the model I use in my objective function is not defined as y=f(G0), but instead as y=f(G=f(G0)).
I need to find the roots of G=f(G0) before I can calculate y: I have to choose the smallest positive value of the roots returned, and assign it to G.
For now, the previous code returns:
Traceback (most recent call last):
File "/home/djipey/Working/Data_boss/21.py", line 80, in <module>
result = mod.fit(y, G0=x)
File "/usr/lib/python3.5/site-packages/lmfit/model.py", line 506, in fit
output.fit(data=data, weights=weights)
File "/usr/lib/python3.5/site-packages/lmfit/model.py", line 710, in fit
self.init_fit = self.model.eval(params=self.params, **self.userkws)
File "/usr/lib/python3.5/site-packages/lmfit/model.py", line 372, in eval
result = self.func(**self.make_funcargs(params, kwargs))
File "/home/djipey/Working/Data_boss/21.py", line 38, in binding21
roots = np.roots([A, B, C, -D])
File "/usr/lib/python3.5/site-packages/numpy/lib/polynomial.py", line 207, in roots
p = atleast_1d(p)
File "/usr/lib/python3.5/site-packages/numpy/core/shape_base.py", line 50, in atleast_1d
ary = asanyarray(ary)
File "/usr/lib/python3.5/site-packages/numpy/core/numeric.py", line 525, in asanyarray
return array(a, dtype, copy=False, order=order, subok=True)
ValueError: setting an array element with a sequence.
Because I'm trying to find the roots using G0, that is not a single value but an array.
Could you help me to solve this problem ?
EDIT:
I'm basically trying to fit this equation:
With:
Epsilon_HG, Epsilon_HG2, K1 and K2 are the parameters. H0 is a known constant. G is the variable in this model. And G depends on G0, which is the "x" values I give to the model.
I have several experimental data points where Y=f(G0). But I need to fit Y=f(G). G can be obtained only by solving the cubic equation above.
I finally found the solution, which is in fact very simple:
G = []
def binding21(G0, eps1, eps2, K1, K2):
global G
G = []
for value in G0:
A = K1 * K2
B = K1 * (2 * K2 * H0 - K2 * value + 1)
C = K1 * (H0 - value) + 1
D = value
roots = np.roots([A, B, C, -D])
roots = [float(value) for value in roots if np.isreal(value)]
G.append(max(roots))
G = np.array(G)
y = (eps1 * H0 * K1 * G + eps2 * H0 * K1 * K2 * G**2) / \
(1 + K1 * G + K1 * K2 * G**2)
return y
I basically re-calculate G at each iteration of curve_fit. But I have to do it for the whole list, I can't use one particular value. So, I just do it for the entire list :)
And as I need the final value of G, I had to use a global variable. I know it's evil, but I couldn't find a better solution in that case.
For the chemists out there, this snippet is used to fit titrations for binding experiments (model 1:2).

Categories