Need help fixing my implementation of RK4 - python

I'd appreciate it if someone more experienced on implementation would help me to spot my logical flaw in my current code. For the past couple of hours I've been stuck with the implementation and testing of various step sizes for the following RK4 function to solve the Lotka-Volterra Differential equation.
I did my absolute best to ensure readability of the code and comment out crucial steps, so the code below should be clear.
import matplotlib.pyplot as plt
import numpy as np
def model(state,t):
"""
A function that creates an 1x2-array containing the Lotka Volterra Differential equation
Parameter assignement/convention:
a natural growth rate of the preys
b chance of being eaten by a predator
c dying rate of the predators per week
d chance of catching a prey
"""
x,y = state # will corresponding to initial conditions
# consider it as a vector too
a = 0.08
b = 0.002
c = 0.2
d = 0.0004
return np.array([ x*(a-b*y) , -y*(c - d*x) ]) # corresponds to [dx/dt, dy/dt]
def rk4( f, x0, t):
"""
4th order Runge-Kutta method implementation to solve x' = f(x,t) with x(t[0]) = x0.
INPUT:
f - function of x and t equal to dx/dt.
x0 - the initial condition(s).
Specifies the value of x # t = t[0] (initial).
Can be a scalar or a vector (NumPy Array)
Example: [x0, y0] = [500, 20]
t - a time vector (array) at which the values of the solution are computed at.
t[0] is considered as the initial time point
the step size h is dependent on the time vector, choosing more points will
result in a smaller step size.
OUTPUT:
x - An array containing the solution evaluated at each point in the t array.
"""
n = len( t )
x = np.array( [ x0 ] * n ) # creating an array of length n
for i in xrange( n - 1 ):
h = t[i+1]- t[i] # step size, dependent on time vector
# starting below - the implementation of the RK4 algorithm:
# for further informations visit http://en.wikipedia.org/wiki/Runge-Kutta_methods
# k1 is the increment based on the slope at the beginning of the interval (same as Euler)
# k2 is the increment based on the slope at the midpoint of the interval
# k3 is AGAIN the increment based on the slope at the midpoint
# k4 is the increment based on the slope at the end of the interval
k1 = f( x[i], t[i] )
k2 = f( x[i] + 0.5 * h * k1, t[i] + 0.5 * h )
k3 = f( x[i] + 0.5 * h * k2, t[i] + 0.5 * h )
k4 = f( x[i] + h * k3, t[i] + h )
# finally computing the weighted average and storing it in the x-array
t[i+1] = t[i] + h
x[i+1] = x[i] + h * ( ( k1 + 2.0 * ( k2 + k3 ) + k4 ) / 6.0 )
return x
################################################################
# just the graphical output
# initial conditions for the system
x0 = 500
y0 = 20
# vector of times
t = np.linspace( 0, 200, 150 )
result = rk4( model,[x0,y0], t )
plt.plot(t,result)
plt.xlabel('Time')
plt.ylabel('Population Size')
plt.legend(('x (prey)','y (predator)'))
plt.title('Lotka-Volterra Model')
plt.show()
The current output looks 'okay-ish' on a small interval and then goes 'berserk'. Oddly enough the code seems to perform better when I choose a larger step size rather than a small one, which points out that my implementation must be wrong, or maybe my model is off. I couldn't spot the error myself.
Output (wrong):
and this is the desired output which can be easily obtained by using one of Scipys integration modules. Note that on the time interval [0,50] the simulation seems correct, then it gets worse by every step.

Unfortunately, you fell into the same trap I've occasionally fallen into: your initial x0 array contains integers, and thus, all resulting x[i] values will be converted to an integer after calculation.
Why is that? Because int is the type of your initial conditions:
x0 = 500
y0 = 20
The solution is, of course, to explicitly make them floats:
x0 = 500.
y0 = 20.
So why does scipy does it correctly when you feed it integer starting values? It probably converts them to float before starting the actual calculation. You could for example do:
x = np.array( [ x0 ] * n, dtype=np.float)
and then you're still safe to use integer initial conditions without problems.
At least this way, the conversion is done inside the function for once and for all, and if you ever use it again half a year (or, someone else uses it), you can't fall into that trap again.

Related

How to use scipy `minimize` on a difference between two vectors?

I have two vectors w1 and w2 (each of length 100), and I want to minimize the sum of their absolute difference i.e.
import numpy as np
def diff(w: np.ndarray) -> float:
"""Get the sum of absolute differences in the vector w.
Args:
w: A flattened vector of length 200, with the first 100 elements
pertaining to w1, and the last 100 elements pertaining to w2.
Returns:
sum of absolute differences.
"""
return np.sum(np.absolute(w[:100] - w[-100:]))
I need to write diff() as only taking one argument since scipy.opyimize.minimize requires the array passed to the x0 argument to be 1 dimensional.
As for constraints, I have
w1 is fixed and not allowed to change
w2 is allowed to change
The sum of absolute values w2 is between 0.1 and 1.1: 0.1 <= sum(abs(w2)) <= 1.1
|w2_i| < 0.01 for any element i in w2
I am confused as to how we code these constraints using the Bounds and LinearConstraints objects. What I've tried so far is the following
from scipy.optimize import minimize, Bounds, LinearConstraint
bounds = Bounds(lb=[-0.01] * 200, ub=[0.01] * 200) # constraint #4
lc = LinearConstraint([[1] * 200], [0.1], [1.1]) # constraint #3
res = minimize(
fun=diff,
method='trust-constr',
x0=w, # my flattened vector containing w1 first 100 elements, and w2 in last 100 elements
bounds=bounds,
constraints=(lc)
)
My logic for the bounds variable is from constrain #4, and for the lc variable comes from constrain #3. However I know I've coded this wrong because because the lower and upper bounds are of length 200 which seems to indicate they are applied to both w1 and w2 whereas I only wan't to apply the constrains to w2 (I get an error ValueError: operands could not be broadcast together with shapes (200,) (100,) if I try to change the length of the array in Bounds from 200 to 100).
The shapes and argument types for LinearConstraint are especially confusing to me, but I did try to follow the scipy example.
This current implementation never seems to finish, it just hangs forever.
How do I properly implement bounds and LinearConstraint so that it satisfies my constraints list above, if that is even possible?
Your problem can easily be formulated as a linear optimization problem (LP). You only need to reformulate all absolute values of the optimization variables.
Changing the notation slightly (x is now the optimization variable w2 and w is just your given vector w1), your problem reads as
min |w_1 - x_1| + .... + |w_N - x_N|
s.t. lb <= |x1| + ... + |xN| <= ub (3)
|x_i| <= 0.01 - eps (4) (models the strict inequality)
where eps is just a sufficiently small number in order to model the strict inequality.
Let's consider the constraint (3). Here, we add additional positive variables z and define z_i = |x_i|. Then, we replace all absolute values |x_i| by z_i and impose the constraints -x_i <= z_i <= x_i which model the relationship z_i = |x_i|. Similarly, you can proceed with the objective and the constraint (4). The latter is by the way trivial and equivalent to -(0.01 - eps) <= x_i <= 0.01 - eps.
In the end, your optimization problem should read (assuming that all your w_i are positive):
min u1 + .... + uN
s.t. lb <= z1 + ... + zN <= ub
-x <= z <= x
-0.01 + eps <= x <= 0.01 - eps
-(w-x) <= u <= w - x
0 <= z
0 <= u
with 3*N optimization variables x1, ..., xN, u1, ..., uN, z1, ..., zN. It isn't hard to write these constraints as an matrix-vector product A_ineq * x <= b_ineq. Then, you can solve it by scipy.optimize.linprog as follows:
import numpy as np
from scipy.optimize import minimize, linprog
n = 100
w = np.abs(np.random.randn(n))
eps = 1e-10
lb = 0.1
ub = 1.1
# linear constraints: A_ub * (x, z, u)^T <= b_ub
A_ineq = np.block([
[np.zeros(n), np.ones(n), np.zeros(n)],
[np.zeros(n), -np.ones(n), np.zeros(n)],
[-np.eye(n), np.eye(n), np.zeros((n, n))],
[-np.eye(n), -np.eye(n), np.zeros((n, n))],
[ np.eye(n), np.zeros((n, n)), -np.eye(n)],
[ np.eye(n), np.zeros((n, n)), np.eye(n)],
])
b_ineq = np.hstack((ub, -lb, np.zeros(n), np.zeros(n), w, w))
# bounds: lower <= (x, z, u)^T <= upper
lower = np.hstack(((-0.01 + eps) * np.ones(n), np.zeros(n), np.zeros(n)))
upper = np.hstack((( 0.01 - eps) * np.ones(n), np.inf*np.ones(n), np.inf*np.ones(n)))
bounds = [(l, u) for (l, u) in zip(lower, upper)]
# objective: c^T * (x, z, u)
c = np.hstack((np.zeros(n), np.zeros(n), np.ones(n)))
# solve the problem
res = linprog(c, A_ub=A_ineq, b_ub=b_ineq, method="highs")
# your solution
x = res.x[:n]
print(res.message)
print(x)
Some notes in arbitrary order:
It's highly recommended to solve linear optimization problems with linprog instead of minimize. The former provides an interface to HiGHS, a high-performance LP solver HiGHs that outperforms all algorithms under the hood of minimize. However, it's also worth mentioning that minimize is meant to be used for nonlinear optimization problems.
In case your values w are not all positive, we need to change the formulation.
You can (and perhaps should, for clarity), use the args argument in minimize, and provide the fixed vector as an extra argument to your function.
If you set up your equation as follows:
def diff(w2, w1):
return np.sum(np.absolute(w1 - w2))
and your constraints with
bounds = Bounds(lb=[-0.01] * 100, ub=[0.01] * 100) # constraint #4
lc = LinearConstraint([[1] * 100], [0.1], [1.1]) # constraint #3
and then do
res = minimize(
fun=diff,
method='trust-constr',
x0=w1,
args=(w2,),
bounds=bounds,
constraints=[lc]
)
Then:
print(res.success, res.status, res.nit, np.abs(res.x).sum(), all(np.abs(res.x) < 0.01))
yields (for me at least)
(True, 1, 17, 0.9841520351691752, True)
which seems to be what you want.
Note that my test inputs are:
w1 = (np.arange(100) - 50) / 1000
w2 = np.ones(100, dtype=float)
which may or may not be favourable to the fitting procedure.

How to fit a piecewise (alternating linear and constant segments) function to a parabolic function?

I do have a function, for example , but this can be something else as well, like a quadratic or logarithmic function. I am only interested in the domain of . The parameters of the function (a and k in this case) are known as well.
My goal is to fit a continuous piece-wise function to this, which contains alternating segments of linear functions (i.e. sloped straight segments, each with intercept of 0) and constants (i.e. horizontal segments joining the sloped segments together). The first and last segments are both sloped. And the number of segments should be pre-selected between around 9-29 (that is 5-15 linear steps + 4-14 constant plateaus).
Formally
The input function:
The fitted piecewise function:
I am looking for the optimal resulting parameters (c,r,b) (in terms of least squares) if the segment numbers (n) are specified beforehand.
The resulting constants (c) and the breakpoints (r) should be whole natural numbers, and the slopes (b) round two decimal point values.
I have tried to do the fitting numerically using the pwlf package using a segmented constant models, and further processed the resulting constant model with some graphical intuition to "slice" the constant steps with the slopes. It works to some extent, but I am sure this is suboptimal from both fitting perspective and computational efficiency. It takes multiple minutes to generate a fitting with 8 slopes on the range of 1-50000. I am sure there must be a better way to do this.
My idea would be to instead using only numerical methods/ML, the fact that we have the algebraic form of the input function could be exploited in some way to at least to use algebraic transforms (integrals) to get to a simpler optimization problem.
import numpy as np
import matplotlib.pyplot as plt
import pwlf
# The input function
def input_func(x,k,a):
return np.power(x,1/a)*k
x = np.arange(1,5e4)
y = input_func(x, 1.8, 1.3)
plt.plot(x,y);
def pw_fit(func, x_r, no_seg, *fparams):
# working on the specified range
x = np.arange(1,x_r)
y_input = func(x, *fparams)
my_pwlf = pwlf.PiecewiseLinFit(x, y_input, degree=0)
res = my_pwlf.fit(no_seg)
yHat = my_pwlf.predict(x)
# Function values at the breakpoints
y_isec = func(res, *fparams)
# Slope values at the breakpoints
slopes = np.round(y_isec / res, decimals=2)
slopes = slopes[1:]
# For the first slope value, I use the intersection of the first constant plateau and the input function
slopes = np.insert(slopes,0,np.round(y_input[np.argwhere(np.diff(np.sign(y_input - yHat))).flatten()[0]] / np.argwhere(np.diff(np.sign(y_input - yHat))).flatten()[0], decimals=2))
plateaus = np.unique(np.round(yHat))
# If due to rounding slope values (to two decimals), there is no change in a subsequent step, I just remove those segments
to_del = np.argwhere(np.diff(slopes) == 0).flatten()
slopes = np.delete(slopes,to_del + 1)
plateaus = np.delete(plateaus,to_del)
breakpoints = [np.ceil(plateaus[0]/slopes[0])]
for idx, j in enumerate(slopes[1:-1]):
breakpoints.append(np.floor(plateaus[idx]/j))
breakpoints.append(np.ceil(plateaus[idx+1]/j))
breakpoints.append(np.floor(plateaus[-1]/slopes[-1]))
return slopes, plateaus, breakpoints
slo, plat, breaks = pw_fit(input_func, 50000, 8, 1.8, 1.3)
# The piecewise function itself
def pw_calc(x, slopes, plateaus, breaks):
x = x.astype('float')
cond_list = [x < breaks[0]]
for idx, j in enumerate(breaks[:-1]):
cond_list.append((j <= x) & (x < breaks[idx+1]))
cond_list.append(breaks[-1] <= x)
func_list = [lambda x: x * slopes[0]]
for idx, j in enumerate(slopes[1:]):
func_list.append(plateaus[idx])
func_list.append(lambda x, j=j: x * j)
return np.piecewise(x, cond_list, func_list)
y_output = pw_calc(x, slo, plat, breaks)
plt.plot(x,y,y_output);
(Not important, but I think the fitted piecewise function is not continuous as it is. Intervals should be x<=r1; r1<x<=r2; ....)
As Anatolyg has pointed out, it looks to me that in the optimal solution (for the function posted at least, and probably for any where the derivative is different from zero), the horizantal segments will collapse to a point or the minimum segment length (in this case 1).
EDIT---------------------------------------------
The behavior above could only be valid if the slopes could have an intercept. If the intercepts are zero, as posted in the question, one consideration must be taken into account: Is the initial parabolic function defined in zero or nearby? Imagine the function y=0.001 *sqrt(x-1000), then the segments defined as b*x will have a slope close to zero and will be so similar to the constant segments that the best fit will be just the line that without intercept that fits better all the function.
Provided that the function is defined in zero or nearby, you can start by approximating the curve just by linear segments (with intercepts):
divide the function domain in N intervals(equal intervals or whose size is a function of the average curvature (or second derivative) of the function along the domain).
linear fit/regression in each intervals
for each interval, if a point (or bunch of points) in the extreme of any interval is better fitted by the line of the neighbor interval than the line of its interval, this point is assigned to the neighbor interval.
Repeat from 2) until no extreme points are moved.
Linear regressions might be optimized not to calculate all the covariance matrixes from scratch on each iteration, but just adding the contributions of the moved points to the previous covariance matrixes.
Then each linear segment (LSi) is replaced by a combination of a small constant segment at the beginning (Cbi), a linear segment without intercept (Si), and another constant segment at the end (Cei). This segments are easy to calculate as Si will contain the middle point of LSi, and Cbi and Cei will have respectively the begin and end values of the segment LSi. Then the intervals of each segment has to be calculated as an intersection between lines.
With this, the constant end segment will be collinear with the constant begin segment from the next interval so they will merge, resulting in a series of constant and linear segments interleaved.
But this would be a floating point start solution. Next, you will have to apply all the roundings which will mess up quite a lot all the segments as the conditions integer intervals and linear segments without slope can be very confronting. In fact, b,c,r are not totally independent. If ci and ri+1 are known, then bi+1 is already fixed
If nothing is broken so far, the final task will be to minimize the error/cost function (I assume that it will be the integral of the error between the parabolic function and the segments). My guess is that gradients here will be quite a pain, as if you change for example one ci, all the rest of the bj and cj will have to adapt as well due to the integer intervals restriction. However, if you can generalize the derivatives between parameters ( how much do I have to adapt bi+1 if ci changes a unit), you can propagate the change of one parameter to all other parameters and have kind of a gradient. Then for each interval, you can estimate what would be the ideal parameter and averaging all intervals calculate the best gradient step. Let me illustrate this:
Assuming first that r parameters are fixed, if I change c1 by one unit, b2 changes by 0.1, c2 changes by -0.2 and b3 changes by 0.2. This would be the gradient.
Then I estimate, comparing with the parabolic curve, that c1 should increase 0.5 (to reduce the cost by 10 points), b2 should increase 0.2 (to reduce the cost by 5 points), c2 should increase 0.2 (to reduce the cost by 6 points) and b3 should increase 0.1 (to reduce the cost by 9 points).
Finally, the gradient step would be (0.5/1·10 + 0.2/0.1·5 - 0.2/(-0.2)·6 + 0.1/0.2·9)/(10 + 5 + 6 + 9)~= 0.45. Thus, c1 would increase 0.45 units, b2 would increase 0.45·0.1, and so on.
When you add the r parameters to the pot, as integer intervals do not have an proper derivative, calculation is not straightforward. However, you can consider r parameters as floating points, calculate and apply the gradient step and then apply the roundings.
We can integrate the squared error function for linear and constant pieces and let SciPy optimize it. Python 3:
import matplotlib.pyplot as plt
import numpy as np
import scipy.optimize
xl = 1
xh = 50000
a = 1.3
p = 1 / a
n = 8
def split_b_and_c(bc):
return bc[::2], bc[1::2]
def solve_for_r(b, c):
r = np.empty(2 * n)
r[0] = xl
r[1:-1:2] = c / b[:-1]
r[2::2] = c / b[1:]
r[-1] = xh
return r
def linear_residual_integral(b, x):
return (
(x ** (2 * p + 1)) / (2 * p + 1)
- 2 * b * x ** (p + 2) / (p + 2)
+ b ** 2 * x ** 3 / 3
)
def constant_residual_integral(c, x):
return x ** (2 * p + 1) / (2 * p + 1) - 2 * c * x ** (p + 1) / (p + 1) + c ** 2 * x
def squared_error(bc):
b, c = split_b_and_c(bc)
r = solve_for_r(b, c)
linear = np.sum(
linear_residual_integral(b, r[1::2]) - linear_residual_integral(b, r[::2])
)
constant = np.sum(
constant_residual_integral(c, r[2::2])
- constant_residual_integral(c, r[1:-1:2])
)
return linear + constant
def evaluate(x, b, c, r):
i = 0
while x > r[i + 1]:
i += 1
return b[i // 2] * x if i % 2 == 0 else c[i // 2]
def main():
bc0 = (xl + (xh - xl) * np.arange(1, 4 * n - 2, 2) / (4 * n - 2)) ** (
p - 1 + np.arange(2 * n - 1) % 2
)
bc = scipy.optimize.minimize(
squared_error, bc0, bounds=[(1e-06, None) for i in range(2 * n - 1)]
).x
b, c = split_b_and_c(bc)
r = solve_for_r(b, c)
X = np.linspace(xl, xh, 1000)
Y = [evaluate(x, b, c, r) for x in X]
plt.plot(X, X ** p)
plt.plot(X, Y)
plt.show()
if __name__ == "__main__":
main()
I have tried to come up with a new solution myself, based on the idea of #Amo Robb, where I have partitioned the domain, and curve fitted a dual - constant and linear - piece together (with the help of np.maximum). I have used the 1 / f(x)' as the function to designate the breakpoints, but I know this is arbitrary and does not provide a global optimum. Maybe there is some optimal function for these breakpoints. But this solution is OK for me, as it might be appropriate to have a better fit at the first segments, at the expense of the error for the later segments. (The task itself is actually a cost based retail margin calculation {supply price -> added margin}, as the retail POS software can only work with such piecewise margin function).
The answer from #David Eisenstat is correct optimal solution if the parameters are allowed to be floats. Unfortunately the POS software can not use floats. It is OK to round up c-s and r-s afterwards. But the b-s should be rounded to two decimals, as those are inputted as percents, and this constraint would ruin the optimal solution with long floats. I will try to further improve my solution with both Amo's and David's valuable input. Thank You for that!
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
# The input function f(x)
def input_func(x,k,a):
return np.power(x,1/a) * k
# 1 / f(x)'
def one_per_der(x,k,a):
return a / (k * np.power(x, 1/a-1))
# 1 / f(x)' inverted
def one_per_der_inv(x,k,a):
return np.power(a / (x*k), a / (1-a))
def segment_fit(start,end,y,first_val):
b, _ = curve_fit(lambda x,b: np.maximum(first_val, b*x), np.arange(start,end), y[start-1:end-1])
b = float(np.round(b, decimals=2))
bp = np.round(first_val / b)
last_val = np.round(b * end)
return b, bp, last_val
def pw_fit(end_range, no_seg, **fparams):
y_bps = np.linspace(one_per_der(1, **fparams), one_per_der(end_range,**fparams) , no_seg+1)[1:]
x_bps = np.round(one_per_der_inv(y_bps, **fparams))
y = input_func(x, **fparams)
slopes = [np.round(float(curve_fit(lambda x,b: x * b, np.arange(1,x_bps[0]), y[:int(x_bps[0])-1])[0]), decimals = 2)]
plats = [np.round(x_bps[0] * slopes[0])]
bps = []
for i, xbp in enumerate(x_bps[1:]):
b, bp, last_val = segment_fit(int(x_bps[i]+1), int(xbp), y, plats[i])
slopes.append(b); bps.append(bp); plats.append(last_val)
breaks = sorted(list(x_bps) + bps)[:-1]
# If due to rounding slope values (to two decimals), there is no change in a subsequent step, I just remove those segments
to_del = np.argwhere(np.diff(slopes) == 0).flatten()
breaks_to_del = np.concatenate((to_del * 2, to_del * 2 + 1))
slopes = np.delete(slopes,to_del + 1)
plats = np.delete(plats[:-1],to_del)
breaks = np.delete(breaks,breaks_to_del)
return slopes, plats, breaks
def pw_calc(x, slopes, plateaus, breaks):
x = x.astype('float')
cond_list = [x < breaks[0]]
for idx, j in enumerate(breaks[:-1]):
cond_list.append((j <= x) & (x < breaks[idx+1]))
cond_list.append(breaks[-1] <= x)
func_list = [lambda x: x * slopes[0]]
for idx, j in enumerate(slopes[1:]):
func_list.append(plateaus[idx])
func_list.append(lambda x, j=j: x * j)
return np.piecewise(x, cond_list, func_list)
fparams = {'k':1.8, 'a':1.2}
end_range = 5e4
no_steps = 10
x = np.arange(1, end_range)
y = input_func(x, **fparams)
slopes, plats, breaks = pw_fit(end_range, no_steps, **fparams)
y_output = pw_calc(x, slopes, plats, breaks)
plt.plot(x,y_output,y);

Using python built-in functions for coupled ODEs

THIS PART IS JUST BACKGROUND IF YOU NEED IT
I am developing a numerical solver for the Second-Order Kuramoto Model. The functions I use to find the derivatives of theta and omega are given below.
# n-dimensional change in omega
def d_theta(omega):
return omega
# n-dimensional change in omega
def d_omega(K,A,P,alpha,mask,n):
def layer1(theta,omega):
T = theta[:,None] - theta
A[mask] = K[mask] * np.sin(T[mask])
return - alpha*omega + P - A.sum(1)
return layer1
These equations return vectors.
QUESTION 1
I know how to use odeint for two dimensions, (y,t). for my research I want to use a built-in Python function that works for higher dimensions.
QUESTION 2
I do not necessarily want to stop after a predetermined amount of time. I have other stopping conditions in the code below that will indicate whether the system of equations converges to the steady state. How do I incorporate these into a built-in Python solver?
WHAT I CURRENTLY HAVE
This is the code I am currently using to solve the system. I just implemented RK4 with constant time stepping in a loop.
# This function randomly samples initial values in the domain and returns whether the solution converged
# Inputs:
# f change in theta (d_theta)
# g change in omega (d_omega)
# tol when step size is lower than tolerance, the solution is said to converge
# h size of the time step
# max_iter maximum number of steps Runge-Kutta will perform before giving up
# max_laps maximum number of laps the solution can do before giving up
# fixed_t vector of fixed points of theta
# fixed_o vector of fixed points of omega
# n number of dimensions
# theta initial theta vector
# omega initial omega vector
# Outputs:
# converges true if it nodes restabilizes, false otherwise
def kuramoto_rk4_wss(f,g,tol_ss,tol_step,h,max_iter,max_laps,fixed_o,fixed_t,n):
def layer1(theta,omega):
lap = np.zeros(n, dtype = int)
converges = False
i = 0
tau = 2 * np.pi
while(i < max_iter): # perform RK4 with constant time step
p_omega = omega
p_theta = theta
T1 = h*f(omega)
O1 = h*g(theta,omega)
T2 = h*f(omega + O1/2)
O2 = h*g(theta + T1/2,omega + O1/2)
T3 = h*f(omega + O2/2)
O3 = h*g(theta + T2/2,omega + O2/2)
T4 = h*f(omega + O3)
O4 = h*g(theta + T3,omega + O3)
theta = theta + (T1 + 2*T2 + 2*T3 + T4)/6 # take theta time step
mask2 = np.array(np.where(np.logical_or(theta > tau, theta < 0))) # find which nodes left [0, 2pi]
lap[mask2] = lap[mask2] + 1 # increment the mask
theta[mask2] = np.mod(theta[mask2], tau) # take the modulus
omega = omega + (O1 + 2*O2 + 2*O3 + O4)/6
if(max_laps in lap): # if any generator rotates this many times it probably won't converge
break
elif(np.any(omega > 12)): # if any of the generators is rotating this fast, it probably won't converge
break
elif(np.linalg.norm(omega) < tol_ss and # assert the nodes are sufficiently close to the equilibrium
np.linalg.norm(omega - p_omega) < tol_step and # assert change in omega is small
np.linalg.norm(theta - p_theta) < tol_step): # assert change in theta is small
converges = True
break
i = i + 1
return converges
return layer1
Thanks for your help!
You can wrap your existing functions into a function accepted by odeint (option tfirst=True) and solve_ivp as
def odesys(t,u):
theta,omega = u[:n],u[n:]; # or = u.reshape(2,-1);
return [ *f(omega), *g(theta,omega) ]; # or np.concatenate([f(omega), g(theta,omega)])
u0 = [*theta0, *omega0]
t = linspan(t0, tf, timesteps+1);
u = odeint(odesys, u0, t, tfirst=True);
#or
res = solve_ivp(odesys, [t0,tf], u0, t_eval=t)
The scipy methods pass numpy arrays and convert the return value into same, so that you do not have to care in the ODE function. The variant in comments is using explicit numpy functions.
While solve_ivp does have event handling, using it for a systematic collection of events is rather cumbersome. It would be easier to advance some fixed step, do the normalization and termination detection, and then repeat this.
If you want to later increase efficiency somewhat, use directly the stepper classes behind solve_ivp.

leastsq trouble passing parameters Python

I am trying to fit the following function: Detrended SNR into my data. C1, C2 and h are the parameters I need to obtain from the leastsq's method. C1 and C2 are simple but the problem is that my h(t) is in reality: . What I want to obtain are the coefficients hj inside that function (in my case there are 35 different hj's ). This function is the sum of different basis B-Splines each one weighted different and the number of coefficients is equal to the number of knots of the B-Spline. As I want to obtain C1, C2 and h1..35 I do the following:
funcLine = lambda tpl, eix_x: (tpl[0]*np.sin((4*math.pi*np.sum(bsplines_evaluades * np.transpose([tpl[2],tpl[3],tpl[4],tpl[5],tpl[6],tpl[7],tpl[8],tpl[9],tpl[10],tpl[11],tpl[12],tpl[13],tpl[14],tpl[15],tpl[16],tpl[17],tpl[18],tpl[19],tpl[20],tpl[21],tpl[22],tpl[23],tpl[24],tpl[25],tpl[26],tpl[27],tpl[28],tpl[29],tpl[30],tpl[31],tpl[32],tpl[33],tpl[34],tpl[35],tpl[36],tpl[37]]) , axis=0))*eix_x/lambda1) + tpl[1]*np.cos((4*math.pi*np.sum(bsplines_evaluades * np.transpose([tpl[2],tpl[3],tpl[4],tpl[5],tpl[6],tpl[7],tpl[8],tpl[9],tpl[10],tpl[11],tpl[12],tpl[13],tpl[14],tpl[15],tpl[16],tpl[17],tpl[18],tpl[19],tpl[20],tpl[21],tpl[22],tpl[23],tpl[24],tpl[25],tpl[26],tpl[27],tpl[28],tpl[29],tpl[30],tpl[31],tpl[32],tpl[33],tpl[34],tpl[35],tpl[36],tpl[37]]) , axis=0))*eix_x/lambda1))*np.exp(-4*np.power(k, 2)*lambda_big*np.power(eix_x, 2))
func = funcLine
ErrorFunc = lambda tpl, eix_x, ydata: np.power(func(tpl, eix_x) - ydata,2)
tplFinal1, success = leastsq(ErrorFunc, [2, -2, 8.2*np.ones(35)], args=(eix_x, ydata))
tpl(0)=C1, tpl(1)=C2 and tpl(2..35)=my coefficients. bsplines_evaluades is a matrix [35,86000] where each row is the temporal function of each basis b-spline so I weight each row with its individual coefficient, 86000 is the length of eix_x. ydata(eix_x) is the function I want to aproximate. lambda1= 0.1903 ; lambda_big= 2; k=2*pi/lambda1. The output is the same initial parameters which is not logic.
Can anyone help me? I have tried with curvefit too but it does not work.
Data is in : http://www.filedropper.com/data_5>http://www.filedropper.com/download_button.png width=127 height=145 border=0/> http://www.filedropper.com >online backup storage
EDIT
The code right now is:
lambda1 = 0.1903
k = 2 * math.pi / lambda1
lambda_big = 2
def funcLine(tpl, eix_x):
C1, C2, h = tpl[0], tpl(1), tpl[2:]
hsum = np.sum(bsplines_evaluades * h, axis=1) # weight each
theta = 4 * np.pi * np.array(hsum) * np.array(eix_x) / lambda1
return (C1*np.sin(theta)+C2*np.cos(theta))*np.exp(-4*lambda_big*(k*eix_x)**2) # lambda_big = 2
if len(eix_x) != 0:
ErrorFunc = lambda tpl, eix_x, ydata: funcLine(tpl, eix_x) - ydata
param_values = 7.5 * np.ones(37)
param_values[0] = 2
param_values(1) = -2
tplFinal2, success = leastsq(ErrorFunc, param_values, args=(eix_x, ydata))
The problem is that the output parameters don't change with respect the initial ones. Data (x_axis,ydata,bsplines_evaluades):
gist.github.com/hect1995/dcd36a4237fe57791d996bd70e7a9fc7 gist.github.com/hect1995/39ae4768ebb32c27f1ddea97e24d96af gist.github.com/hect1995/bddd02de567f8fcbedc752371b47ff71
It's always helpful (to yourself as well as us) to provide a readable example that is complete enough to be runnable. Your example with lambdas and very long line is definitely not readable, making it very easy for you to miss simple mistakes. One of the points of using Python is to make code easier to read.
It is fine to have spline coefficients as fit variables, but you want to have the np.ndarray of variables to be exactly 1-dimensional. So your parameter array should be
param_values = 8.2 * np.ones(37)
param_values[0] = 2
param_values[1] = -2.
result_params, success = leastsq(errorFunc, param_values, ....)
It should be fine to use curve_fit() too. Beyond that, it's hard to give much help, as you give neither a complete runnable program (leaving lots of terms undefined), nor the output or error messages from running your code.
Several things could be wrong here: I'm not sure you're indexing your tpl array correctly (if it has 37 entries, the indexes should be 0:36). And your errorFunc should probably return the residual rather than the square residual.
Finally, I think your h-sum may be incorrect: you want to sum over the $N$ axis but not the $x$ axis, right?
You might tidy up your code as follows and see if it helps (without some data it's difficult to test for myself):
def funcLine(tpl, eix_x):
C1, C2, h = tpl[0], tpl[1], tpl[2:]
hsum = np.sum(bsplines_evaluades * h, axis=1)
theta = 4 * np.pi * hsum * eix_x / lambda1
return (C1 * np.sin(theta) + C2 * np.cos(theta)) * np.exp(-4 *lambda_big *
(k * eix_x)**2)
errorFunc = lambda tpl, eix_x, ydata: funcLine(tpl, eix_x) - ydata
tplFinal2, success = leastsq(errorFunc, [2, -2, 8.2*np.ones(35)],
args=(eix_x, ydata))

Artefacts from Riemann sum in scipy.signal.convolve

Short summary: How do I quickly calculate the finite convolution of two arrays?
Problem description
I am trying to obtain the finite convolution of two functions f(x), g(x) defined by
To achieve this, I have taken discrete samples of the functions and turned them into arrays of length steps:
xarray = [x * i / steps for i in range(steps)]
farray = [f(x) for x in xarray]
garray = [g(x) for x in xarray]
I then tried to calculate the convolution using the scipy.signal.convolve function. This function gives the same results as the algorithm conv suggested here. However, the results differ considerably from analytical solutions. Modifying the algorithm conv to use the trapezoidal rule gives the desired results.
To illustrate this, I let
f(x) = exp(-x)
g(x) = 2 * exp(-2 * x)
the results are:
Here Riemann represents a simple Riemann sum, trapezoidal is a modified version of the Riemann algorithm to use the trapezoidal rule, scipy.signal.convolve is the scipy function and analytical is the analytical convolution.
Now let g(x) = x^2 * exp(-x) and the results become:
Here 'ratio' is the ratio of the values obtained from scipy to the analytical values. The above demonstrates that the problem cannot be solved by renormalising the integral.
The question
Is it possible to use the speed of scipy but retain the better results of a trapezoidal rule or do I have to write a C extension to achieve the desired results?
An example
Just copy and paste the code below to see the problem I am encountering. The two results can be brought to closer agreement by increasing the steps variable. I believe that the problem is due to artefacts from right hand Riemann sums because the integral is overestimated when it is increasing and approaches the analytical solution again as it is decreasing.
EDIT: I have now included the original algorithm 2 as a comparison which gives the same results as the scipy.signal.convolve function.
import numpy as np
import scipy.signal as signal
import matplotlib.pyplot as plt
import math
def convolveoriginal(x, y):
'''
The original algorithm from http://www.physics.rutgers.edu/~masud/computing/WPark_recipes_in_python.html.
'''
P, Q, N = len(x), len(y), len(x) + len(y) - 1
z = []
for k in range(N):
t, lower, upper = 0, max(0, k - (Q - 1)), min(P - 1, k)
for i in range(lower, upper + 1):
t = t + x[i] * y[k - i]
z.append(t)
return np.array(z) #Modified to include conversion to numpy array
def convolve(y1, y2, dx = None):
'''
Compute the finite convolution of two signals of equal length.
#param y1: First signal.
#param y2: Second signal.
#param dx: [optional] Integration step width.
#note: Based on the algorithm at http://www.physics.rutgers.edu/~masud/computing/WPark_recipes_in_python.html.
'''
P = len(y1) #Determine the length of the signal
z = [] #Create a list of convolution values
for k in range(P):
t = 0
lower = max(0, k - (P - 1))
upper = min(P - 1, k)
for i in range(lower, upper):
t += (y1[i] * y2[k - i] + y1[i + 1] * y2[k - (i + 1)]) / 2
z.append(t)
z = np.array(z) #Convert to a numpy array
if dx != None: #Is a step width specified?
z *= dx
return z
steps = 50 #Number of integration steps
maxtime = 5 #Maximum time
dt = float(maxtime) / steps #Obtain the width of a time step
time = [dt * i for i in range (steps)] #Create an array of times
exp1 = [math.exp(-t) for t in time] #Create an array of function values
exp2 = [2 * math.exp(-2 * t) for t in time]
#Calculate the analytical expression
analytical = [2 * math.exp(-2 * t) * (-1 + math.exp(t)) for t in time]
#Calculate the trapezoidal convolution
trapezoidal = convolve(exp1, exp2, dt)
#Calculate the scipy convolution
sci = signal.convolve(exp1, exp2, mode = 'full')
#Slice the first half to obtain the causal convolution and multiply by dt
#to account for the step width
sci = sci[0:steps] * dt
#Calculate the convolution using the original Riemann sum algorithm
riemann = convolveoriginal(exp1, exp2)
riemann = riemann[0:steps] * dt
#Plot
plt.plot(time, analytical, label = 'analytical')
plt.plot(time, trapezoidal, 'o', label = 'trapezoidal')
plt.plot(time, riemann, 'o', label = 'Riemann')
plt.plot(time, sci, '.', label = 'scipy.signal.convolve')
plt.legend()
plt.show()
Thank you for your time!
or, for those who prefer numpy to C. It will be slower than the C implementation, but it's just a few lines.
>>> t = np.linspace(0, maxtime-dt, 50)
>>> fx = np.exp(-np.array(t))
>>> gx = 2*np.exp(-2*np.array(t))
>>> analytical = 2 * np.exp(-2 * t) * (-1 + np.exp(t))
this looks like trapezoidal in this case (but I didn't check the math)
>>> s2a = signal.convolve(fx[1:], gx, 'full')*dt
>>> s2b = signal.convolve(fx, gx[1:], 'full')*dt
>>> s = (s2a+s2b)/2
>>> s[:10]
array([ 0.17235682, 0.29706872, 0.38433313, 0.44235042, 0.47770012,
0.49564748, 0.50039326, 0.49527721, 0.48294359, 0.46547582])
>>> analytical[:10]
array([ 0. , 0.17221333, 0.29682141, 0.38401317, 0.44198216,
0.47730244, 0.49523485, 0.49997668, 0.49486489, 0.48254154])
largest absolute error:
>>> np.max(np.abs(s[:len(analytical)-1] - analytical[1:]))
0.00041657780840698155
>>> np.argmax(np.abs(s[:len(analytical)-1] - analytical[1:]))
6
Short answer: Write it in C!
Long answer
Using the cookbook about numpy arrays I rewrote the trapezoidal convolution method in C. In order to use the C code one requires three files (https://gist.github.com/1626919)
The C code (performancemodule.c).
The setup file to build the code and make it callable from python (performancemodulesetup.py).
The python file that makes use of the C extension (performancetest.py)
The code should run upon downloading by doing the following
Adjust the include path in performancemodule.c.
Run the following
python performancemodulesetup.py build
python performancetest.py
You may have to copy the library file performancemodule.so or performancemodule.dll into the same directory as performancetest.py.
Results and performance
The results agree neatly with one another as shown below:
The performance of the C method is even better than scipy's convolve method. Running 10k convolutions with array length 50 requires
convolve (seconds, microseconds) 81 349969
scipy.signal.convolve (seconds, microseconds) 1 962599
convolve in C (seconds, microseconds) 0 87024
Thus, the C implementation is about 1000 times faster than the python implementation and a bit more than 20 times as fast as the scipy implementation (admittedly, the scipy implementation is more versatile).
EDIT: This does not solve the original question exactly but is sufficient for my purposes.

Categories