In our physics class we have to model a damping torsional pendulum.
Ilustration of torsional pendulum:
We came up with this equation of motion:
Where θ is the angle, A is torsion parameter, B is Newton's parameter, C is Stokes' parameter, and D is friction parameter. We also use the sign function sgn that determines the direction of the acting force upon the pendulum, depending on the current angle from the reference point.
The problem is, that I'm unable to solve it using Runge-Kutta method in Python.
I got a working solution in MATLAB by using Euler's method, which has some flaws, but it is something.
MATLAB Code:
function [theta, dtheta, epsilon] = drt(t, theta0, dtheta0, A, B, C, D)
epsilon = zeros(1, length(t));
theta = epsilon;
dtheta = epsilon;
theta(1) = theta0;
dtheta(1) = dtheta0;
epsilon(1) = A * alpha0 - B * dtheta^2 - C * dtheta - D;
dt = t(2) - t(1);
for i = 1 : (length(t) - 1)
epsilon(i + 1)= - A * theta(i) - B * dtheta(i)^2 * sign(dtheta(i)) - C * dtheta(i) - D * sign(dtheta(i));
dtheta(i + 1)= dtheta(i) + dt * epsilon(i);
theta(i + 1) = theta(i) + dt * dtheta(i);
end
end
We call this MATLAB function like this for example:
t = linspace(0, 10, 100);
theta0 = 90;
dtheta0 = 0;
A = 1;
B = 0.1;
C = 0.1;
D = 0.1;
[theta, dtheta, epsilon] = drt(t, theta0, dtheta0, A, B, C, D);
We can then plot the theta and other values in a graph, which shows us, how the torsional pendulum is being damped by the external forces acting on it.
Python Code:
import numpy as np
import matplotlib.pyplot as plt
# Damping torsional pendulum
def drp(drp_alpha, drp_d_alpha, drp_params):
a = drp_params["tors"]
b = drp_params["newt"]
c = drp_params["stok"]
d = drp_params["fric"]
result = a * drp_alpha - b * np.power(drp_d_alpha, 2) * np.sign(drp_d_alpha) - c * drp_d_alpha - d * np.sign(drp_d_alpha)
return result
# Runge-Kutta 4th Order
# f - function DamRotPen
# x0 - initial condition
# t0 - initial time
# tmax - maximum time
# dt - sample time
def RG4(rg4_f, rg4_x0, rg4_t0, rg4_tmax, rg4_dt):
# Time vector
rg4_t = np.arange(rg4_t0, rg4_tmax, rg4_dt)
# Time vector size
rg4_t_sz = rg4_t.size
# Initialize the array
rg4_alpha = np.zeros(rg4_t_sz)
# Initial value of the system
rg4_alpha[0] = rg4_x0
for k in range(rg4_t_sz - 1):
k1 = dt * f(rg4_t[k], rg4_alpha[k])
k2 = dt * rg4_f(rg4_t[k] + dt / 2, rg4_alpha[k] + k1 / 2)
k3 = dt * rg4_f(rg4_t[k] + dt / 2, rg4_alpha[k] + k2 / 2)
k4 = dt * rg4_f(rg4_t[k] + dt, rg4_alpha[k] + k3)
rg4_d_alpha = (k1 + 2 * k2 + 2 * k3 + k4) / 6
rg4_alpha[k + 1] = rg4_alpha[k] + rg4_d_alpha
return rg4_alpha, rg4_t
# Parameters of the forces acting on the system
# tors - torsion parameter
# newt - Newton's parameter
# stok - Stokes' parameter
# fric - friction parameter
params = {"tors": 1, "newt": 0.1, "stok": 0.1, "fric": 0.1}
# Start parameters
alpha = 90
d_alpha = 0
# Initial time
t0 = 0
# Maximum time
tmax = 120
# Sample time
dt = 0.01
# Define DamRotPen function as 'f' using lambda
f = lambda t, alpha : drp(alpha, d_alpha, params)
# Try to solve this shit
alpha, t = RG4(f, alpha, t0, tmax, dt)
# Plot this shit
plt.plot(t, alpha, "r", "r", label="Position I guess")
plt.xlabel("Time t / s")
plt.grid()
plt.show()
After plotting the values, we can see on the graph, that the θ sky rockets after certain amount of time. I don't know what I'm doing wrong, I tried practically everything, so that's why I'm asking you for help. (Though I think part of the problem might be my misunderstanding on how to implement the Runge-Kutta method, maybe I got the math wrong, etc...)
Related
As mentioned above, the function below works, however its very slow. I am very interested in using faster/optimised numpy (or other) vectorized alternatives. I have not posted the entire script here due to it being too large.
My specific question is - are there suitable numpy (or other) functions that I can use to 1) reduce run time and 2) reduce code volume of this function, specifically the for loop?
Edit: mass, temp, U and dpdh are functions that carry out simple algebraic calculations and return constants
def my_system(t, y, n, hIn, min, mAlumina, cpAlumina, sa, V):
dydt = np.zeros(3 * n) #setting up zeros array for solution (solving for [H0,Ts0,m0,H1,Ts1,m1,H2,Ts2,m2,..Hn,Tsn,mn])
# y = [h_0, Ts_0, m_0, ... h_n, Ts_n, m_n]
# y[0] = hin
# y[1] = Ts0
# y[2] = minL
i=0
## Using thermo
T = temp(y[i],P) #initial T
m = mass(y[i],P) #initial m
#initial values
dydt[i] = (min * (hIn - y[i]) + (U(hIn,P,min) * sa * (y[i + 1] - T))) / m # dH/dt (eq. 2)
dydt[i + 1] = -(U(hIn,P,min) * sa * (y[i + 1] - T)) / (mAlumina * cpAlumina) # dTs/dt from eq.3
dmdt = dydt[i] * dpdh(y[i], P) * V # dm/dt (holdup variation) eq. 4b
dydt[i + 2] = min - dmdt # mass flow out (eq.4a)
for i in range(3, 3 * n, 3): #starting at index 3, and incrementing by 3 because we are solving for 'triplets' [h,Ts,m] in each loop
## Using thermo
T = temp(y[i],P)
m = mass(y[i],P)
# [h, TS, mdot]
dydt[i] = (dydt[i-1] * (y[i - 3] - y[i]) + (U(y[i-3], P, dydt[i-1]) * sa * (y[i + 1] - T))) / m # dH/dt (eq.2), dydt[i-1] is the mass of the previous tank
dydt[i + 1] = -(U(y[i-3], P, dydt[i-1]) * sa * (y[i + 1] - T)) / (mAlumina * cpAlumina) # dTs/dt eq. (3)
dmdt = dydt[i] * dpdh(y[i], P) * V # Equation 4b
dydt[i + 2] = dydt[i-1] - dmdt # Equation 4a
return dydt
The functions mass, temp, U, and dpdh used inside the my_system function all take numbers as input, perform some simple algebraic operation and return a number (no need to optimise these I am just providing them for further context)
def temp(H,P):
# returns temperature given enthalpy (after processing function)
T = flasher.flash(H=H, P=P, zs=zs, retry=True).T
return T
def mass(H, P):
# returns mass holdup in mol
m = flasher.flash(H=H, P=P, zs=zs, retry=True).rho()*V
return m
def dpdh(H, P):
res = flasher.flash(H=H, P=P, zs=zs, retry=True)
if res.phase_count == 1:
if res.phase == 'L':
drho_dTf = res.liquid0.drho_dT()
else:
drho_dTf = res.gas.drho_dT()
else:
drho_dTf = res.bulk._equilibrium_derivative(of='rho', wrt='T', const='P')
dpdh = drho_dTf/res.dH_dT_P()
return dpdh
def U(H,P,m):
# Given T, P, m
air = Mixture(['nitrogen', 'oxygen'], Vfgs=[0.79, 0.21], H=H, P=P)
mu = air.mu*1000/mWAir #mol/m.s
cp = air.Cpm #J/mol.K
kg = air.k #W/m.K
g0 = m/areaBed #mol/m2.s
a = sa*n/vTotal #m^2/m^3 #QUESTIONABLE
psi = 1
beta = 10
pr = (mu*cp)/kg
re = (6*g0)/(a*mu*psi)
hfs = ((2.19*(re**1/3)) + (0.78*(re**0.619)))*(pr**1/3)*(kg)/diameterParticle
h = 1/((1/hfs) + ((diameterParticle/beta)/kAlumina))
return h
Reference Image:
enter image description here
For improving the speed, you can see Numba, which is useable if you use NumPy a lot but not every code can be used with Numba. Apart from that, the formulation of the equation system is confusing. You are solving 3 equations and adding the result to a single dydt list by 3 elements each. You can simply create three lists, solve each equation and add them to their respective list. For this, you need to re-write my_system as:
import numpy as np
def my_system(t, RHS, hIn, Ts0, minL, mAlumina, cpAlumina, sa, V):
# get initial boundary condition values
y1 = RHS[0]
y2 = RHS[1]
y3 = RHS[2]
## Using thermo
T = # calculate T
m = # calculate m
# [h, TS, mdot] solve dy1dt for h, dy2dt for TS and dy3dt for mdot
dy1dt = # dH/dt (eq.2), y1 corresponds to initial or previous value of dy1dt
dy2dt = # dTs/dt eq. (3), y2 corresponds to initial or previous value of dy2dt
dmdt = # Equation 4b
dy3dt = # Equation 4a, y3 corresponds to initial or previous value of dy3dt
# Left-hand side of ODE
LHS = np.zeros([3,])
LHS[0] = dy1dt
LHS[1] = dy2dt
LHS[2] = dy3dt
return LHS
In this function, you can pass RHS as a list with initial values ([dy1dt, dy2dt, dy3dt]) which will be unpacked as y1, y2, and y3 respectively and use them for respective differential equations. The solved equations (next values) will be saved to dy1dt, dy2dt, and dy3dt which will be returned as a list LHS.
Now you can solve this using scipy.integrate.odeint. Therefore, you can leave the for loop structure and solve the equations by using this method as follows:
hIn = #some val
Ts0 = #some val
minL = #some val
mAlumina = #some vaL
cpAlumina = #some val
sa = #some val
V = #some val
P = #some val
## Using thermo
T = temp(hIn,P) #initial T
m = mass(hIn,P) #initial m
#initial values
y01 = # calculate dH/dt (eq. 2)
y02 = # calculate dTs/dt from eq.3
dmdt = # calculate dm/dt (holdup variation) eq. 4b
y03 = # calculatemass flow out (eq.4a)
n = # time till where you want to solve the equation system
y0 = [y01, y02, y03]
step_size = 1
t = np.linspace(0, n, int(n/step_size)) # use that start time to which initial values corresponds
res = odeint(my_sytem, y0, t, args=(hIn, Ts0, minL, mAlumina, cpAlumina, sa, V,), tfirst=True)
print(res[:,0]) # print results for dH/dt
print(res[:,1]) # print results for dTs/dt
print(res[:,2]) # print results for Equation 4a
Here, I have passed all the initial values as y0 and chosen a step size of 1 which you can change as per your need.
I try to solve the Duffing equation using odeint:
def func(z, t):
q, p = z
return [p, F*np.cos(OMEGA * t) + Fm*np.cos(3*OMEGA * t) + 2*gamma*omega*p - omega**2 * q - beta * q**3]
OMEGA = 1.4
T = 1 / OMEGA
F = 0.2
gamma = 0.1
omega = -1.0
beta = 0.0
Fm = 0
z0 = [1, 0]
psi = 1 / ((omega**2 - OMEGA**2)**2 + 4*gamma**2*OMEGA**2)
wf = np.sqrt(omega**2 - gamma**2)
t = np.linspace(0, 100, 1000)
sol1 = odeintw(func, z0, t, atol=1e-13, rtol=1e-13, mxstep=1000)
When F = gamma = beta = 0 we have a system of two linear homogeneous equations. It's simple!
But when F not equal 0 the system becomes non homogeneous. The problem is that the numerical solution does not coincide with the analytical one:
Figure 1
the numerical solution does not take into account the inhomogeneous solution of the equation.
I have not been able to figure out if it is possible to use here solve_bvp function. Could you help me?
Inserting the constants, the equation becomes
x'' + 2*c*x' + x = F*cos(W*t)
The general solution form is
x(t)=A*cos(W*t)+B*sin(W*t)+exp(-c*t)*(C*cos(w*t)+D*sin(w*t))
w^2=1-c^2
for the particular solution one gets
-W^2*(A*cos(W*t)+B*sin(W*t))
+2*c*W*(B*cos(W*t)-A*sin(W*t))
+ (A*cos(W*t)+B*sin(W*t))
=F*cos(W*t)
(1-W^2)*A + 2*c*W*B = F
-2*c*W*A + (1-W^2)*B = 0
For the initial conditions it is needed that
A+C = 1
W*B-c*C+w*D=0
In python code thus
F=0.2; c=0.1; W=1.4
w=(1-c*c)**0.5
A,B = np.linalg.solve([[1-W*W, 2*c*W],[-2*c*W,1-W*W]], [F,0])
C = 1-A; D = (c*C-W*B)/w
print(f"w={w}, A={A}, B={B}, C={C}, D={D}")
with the output
w=0.99498743710662, A=-0.192, B=0.056, C=1.192, D=0.04100554286257586
continuing
t = np.linspace(0, 100, 1000)
u=odeint(lambda u,t:[u[1], F*np.cos(W*t)-2*c*u[1]-u[0]], [1,0],t, atol=1e-13, rtol=1e-13)
plt.plot(t,u[:,0],label="odeint", lw=3);
plt.plot(t,A*np.cos(W*t)+B*np.sin(W*t)+np.exp(-c*t)*(C*np.cos(w*t)+D*np.sin(w*t)), label="exact");
plt.legend(); plt.grid(); plt.show();
results in an exact fit
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 3 years ago.
Improve this question
I want to numerically compute the Lyapunov Spectrum of the Lorenz System by using the standard method which is described in this Paper, p.81.
One basically need integrate the Lorenz system and the tangential vectors (i used the Runge-Kutta method for this). The evolution equation of the tangential vectors are given by the Jacobi matrix of the Lorenz system. After each iterations one needs to apply the Gram-Schmidt scheme on the vectors and store its lengths. The three Lyapunov exponents are then given by the averages of the stored lengths.
I implemented the above explained scheme in python (used version 3.7.4), but I don't get the correct results.
I thing the bug lies in the Rk4-Method for der vectors, but i cannot find any error...The RK4-method for the trajectories x,y,z works correctly (indicated by the plot) and the implemented Gram-Schmidt scheme is also correctly implemented.
I hope that someone could look through my short code and maybe find my error
Edit: Updated Code
from numpy import array, arange, zeros, dot, log
import matplotlib.pyplot as plt
from numpy.linalg import norm
# Evolution equation of tracjectories and tangential vectors
def f(r):
x = r[0]
y = r[1]
z = r[2]
fx = sigma * (y - x)
fy = x * (rho - z) - y
fz = x * y - beta * z
return array([fx,fy,fz], float)
def jacobian(r):
M = zeros([3,3])
M[0,:] = [- sigma, sigma, 0]
M[1,:] = [rho - r[2], -1, - r[0] ]
M[2,:] = [r[1], r[0], -beta]
return M
def g(d, r):
dx = d[0]
dy = d[1]
dz = d[2]
M = jacobian(r)
dfx = dot(M, dx)
dfy = dot(M, dy)
dfz = dot(M, dz)
return array([dfx, dfy, dfz], float)
# Initial conditions
d = array([[1,0,0], [0,1,0], [0,0,1]], float)
r = array([19.0, 20.0, 50.0], float)
sigma, rho, beta = 10, 45.92, 4.0
T = 10**5 # time steps
dt = 0.01 # time increment
Teq = 10**4 # Transient time
l1, l2, l3 = 0, 0, 0 # Lengths
xpoints, ypoints, zpoints = [], [], []
# Transient
for t in range(Teq):
# RK4 - Method
k1 = dt * f(r)
k11 = dt * g(d, r)
k2 = dt * f(r + 0.5 * k1)
k22 = dt * g(d + 0.5 * k11, r + 0.5 * k1)
k3 = dt * f(r + 0.5 * k2)
k33 = dt * g(d + 0.5 * k22, r + 0.5 * k2)
k4 = dt * f(r + k3)
k44 = dt * g(d + k33, r + k3)
r += (k1 + 2 * k2 + 2 * k3 + k4) / 6
d += (k11 + 2 * k22 + 2 * k33 + k44) / 6
# Gram-Schmidt-Scheme
orth_1 = d[0]
d[0] = orth_1 / norm(orth_1)
orth_2 = d[1] - dot(d[1], d[0]) * d[0]
d[1] = orth_2 / norm(orth_2)
orth_3 = d[2] - (dot(d[2], d[1]) * d[1]) - (dot(d[2], d[0]) * d[0])
d[2] = orth_3 / norm(orth_3)
for t in range(T):
k1 = dt * f(r)
k11 = dt * g(d, r)
k2 = dt * f(r + 0.5 * k1)
k22 = dt * g(d + 0.5 * k11, r + 0.5 * k1)
k3 = dt * f(r + 0.5 * k2)
k33 = dt * g(d + 0.5 * k22, r + 0.5 * k2)
k4 = dt * f(r + k3)
k44 = dt * g(d + k33, r + k3)
r += (k1 + 2 * k2 + 2 * k3 + k4) / 6
d += (k11 + 2 * k22 + 2 * k33 + k44) / 6
orth_1 = d[0] # Gram-Schmidt-Scheme
l1 += log(norm(orth_1))
d[0] = orth_1 / norm(orth_1)
orth_2 = d[1] - dot(d[1], d[0]) * d[0]
l2 += log(norm(orth_2))
d[1] = orth_2 / norm(orth_2)
orth_3 = d[2] - (dot(d[2], d[1]) * d[1]) - (dot(d[2], d[0]) * d[0])
l3 += log(norm(orth_3))
d[2] = orth_3 / norm(orth_3)
# Correct Solution (2.16, 0.0, -32.4)
lya1 = l1 / (dt * T)
lya2 = l2 / (dt * T) - lya1
lya3 = l3 / (dt * T) - lya1 - lya2
lya1, lya2, lya3
# my solution T = 10^5 : (1.3540301507934012, -0.0021967491623752448, -16.351653561383387)
The above code is updated according to Lutz suggestions.
The results look much better but they are still not 100% accurate.
Correct Solution (2.16, 0.0, -32.4)
My solution (1.3540301507934012, -0.0021967491623752448, -16.351653561383387)
The correct solutions are from Wolf's Paper, p.289. On page 290-291 he describes his method, which looks exactly the same as in the paper that i mentioned in the beginning of this post (Paper, p.81).
So there must be another error in my code...
You need to solve the system of point and Jacobian as the (forward) coupled system that it is. In the original source exactly that is done, everything is updated in one RK4 call for the combined system.
So for instance in the second stage, you would mix the operations to have a combined second stage
k2 = dt * f(r + 0.5 * k1)
M = jacobian(r + 0.5 * k1)
k22 = dt * g(d + 0.5 * k11, r + 0.5 * k1)
You could also delegate the computation of M inside the g function, as this is the only place where it is needed, and you increase locality in the scope of variables.
Note that I changed the update of d from k1 to k11, which should be the main source of the error in the numerical result.
Additional remarks on the last code version (2/28/2021):
As said in the comments, the code looks like it will do what the mathematics of the algorithm prescribes. There are two misreadings that prevent the code from returning a result close to the reference:
The parameter in the paper is sigma=16.
The paper uses not the natural logarithm, but the binary one, that is, the magnitude evolution is given as 2^(L_it). So you have to divide the computed exponents by log(2).
Using the method derived in https://scicomp.stackexchange.com/questions/36013/numerical-computation-of-lyapunov-exponent I get the exponents
[2.1531855610566595, -0.00847304754613621, -32.441308372177566]
which is sufficiently close to the reference (2.16, 0.0, -32.4).
Psi in the Carr Madan method of option pricing is the Fourier transform of the option price as a function of the characteristic function.
The function psi should return a scalar as opposed to a vector which is causing me the problem. So I am looking for suggestions of alternative implementation.
import numpy as np
from scipy.special import gamma
param = [24.79, 94.45, 95.79, 0.2495, 0]
alpha = 1.1
lnS = 4.142976
r = 0.05
Sigma = 0.25
scale = 1
FFT_N = int(np.power(2,15))
uvec = np.linspace(1,FFT_N,FFT_N)
vj = (uvec-1) * 0.5
Tmt = 1/12
def psi(CF, vj, alpha, lnS, T=Tmt):
u=vj-(alpha*1j+1j)
denom = alpha**2 + alpha - Sigma**2 + vj * 2 * alpha * 1j + 1j * vj
return CF(u,lnS, Tmt)/denom
CF=lambda u, lnS, T: cf_log_cgmy(u=u, lnS=lnS, T=Tmt, mu=r, half_etasq=param[4],C=param[0], G=param[1], M=param[2], Y=param[3])
def cf_log_cgmy(u, lnS, T, mu ,half_etasq, C, G, M, Y):
omega = -C*gamma(-Y)*(np.power(M-1,Y)-np.power(M,Y)+np.power(G+1,Y)-np.power(G,Y ))
phi_CGMY = C*T*gamma(-Y)*(np.power(M-1j*u,Y)-np.power(M,Y)+np.power(G+1j*u,Y)- np.power(G,Y))
phi = 1j*u*(lnS + (mu+omega-half_etasq)*T) + phi_CGMY - half_etasq*np.power(u,2)
return np.exp(scale*phi)
psi(CF, vj, alpha, lnS, T=Tmt)
The psi function stands for equation (6) on page 64 of the attached below paper
https://engineering.nyu.edu/sites/default/files/2018-08/CarrMadan2_0.pdf
I came to ask for some help with maths and programming.
What am I trying to do? I'm trying to implement a simulation of a chaotic billiard system, following the algorithm in this excerpt.
How am I trying it? Using numpy and matplotlib, I implemented the following code
def boundaryFunction(parameter):
return 1 + 0.1 * np.cos(parameter)
def boundaryDerivative(parameter):
return -0.1 * np.sin(parameter)
def trajectoryFunction(parameter):
aux = np.sin(beta - phi) / np.sin(beta - parameter)
return boundaryFunction(phi) * aux
def difference(parameter):
return trajectoryFunction(parameter) - boundaryFunction(parameter)
def integrand(parameter):
rr = boundaryFunction(parameter)
dd = boundaryDerivative (parameter)
return np.sqrt(rr ** 2 + dd ** 2)
##### Main #####
length_vals = np.array([], dtype=np.float64)
alpha_vals = np.array([], dtype=np.float64)
# nof initial phi angles, alpha angles, and nof collisions for each.
n_phi, n_alpha, n_cols, count = 10, 10, 10, 0
# Length of the boundary
total_length, err = integrate.quad(integrand, 0, 2 * np.pi)
for phi in np.linspace(0, 2 * np.pi, n_phi):
for alpha in np.linspace(0, 2 * np.pi, n_alpha):
for n in np.arange(1, n_cols):
nu = np.arctan(boundaryFunction(phi) / boundaryDerivative(phi))
beta = np.pi + phi + alpha - nu
# Determines next impact coordinate.
bnds = (0, 2 * np.pi)
phi_new = optimize.minimize_scalar(difference, bounds=bnds, method='bounded').x
nu_new = np.arctan(boundaryFunction(phi_new) / boundaryDerivative(phi_new))
# Reflection angle with relation to tangent.
alpha_new = phi_new - phi + nu - nu_new - alpha
# Arc length for current phi value.
arc_length, err = integrate.quad(integrand, 0, phi_new)
# Append values to list
length_vals = np.append(length_vals, arc_length / total_length)
alpha_vals = np.append(alpha_vals, alpha)
count += 1
print "{}%" .format(100 * count / (n_phi * n_alpha))
What is the problem? When calculating phi_new, the equation has two solutions (assuming the boundary is convex, which is.) I must enforce that phi_new is the solution which is different from phi, but I don't know how to do that. Are there more issues with the code?
What should the output be? A phase space diagram of S x Alpha, looking like this.
Any help is very appreciated! Thanks in advance.
One way you could try would be (given there really are only two solutions) would be
epsilon = 1e-7 # tune this
delta = 1e-4 # tune this
# ...
bnds = (0, 2 * np.pi)
phi_new = optimize.minimize_scalar(difference, bounds=bnds, method='bounded').x
if abs(phi_new - phi) < epsilon:
bnds_1 = (0, phi - delta)
phi_new_1 = optimize.minimize_scalar(difference, bounds=bnds_1, method='bounded').x
bnds_2 = (phi + delta, 2 * np.pi)
phi_new_2 = optimize.minimize_scalar(difference, bounds=bnds_2, method='bounded').x
if difference(phi_new_1) < difference(phi_new_2):
phi_new = phi_new_1
else:
phi_new = phi_new_2
Alternatively, you could introduce a penalty-term, e.g. delta*exp(eps/(x-phi)^2) with appropriate choices of epsilon and delta.