Gekko optimal control. I can't keep one value within its bounds - python

I am trying to simulate 3D plane flight. I have an issue with gamma value (Flight-path angle). It gets out of its bounds and then, the simulation stops. The gamma value is being calculated by this equation:
I turned it into this: m.Equation(gamma.dt()==tf*((L*m.cos(Mu)-mass*g*m.cos(gamma))/mass*V))
The target of the simulation is for the plane to reach certain X an Y values(m.Minimize(w*final*(x-pathx)**2) and m.Minimize(w*final*(pathy-y)**2)), while minimizing fuel consumed m.Maximize(0.2*mass*tf*final).
The solver controls gamma value, by controlling lift coefficient Cl, which affects the lift value L, which, in turn, affects gamma value. The equation that calculates lift L value looks like this: m.Equation(L==0.5*Ro*(V**2)*(Cl)*S). But in the end the solver can not control gamma value till the plane gets to its destination.
What could be messing with it?
My code:
import numpy as np
import matplotlib.pyplot as plt
from gekko import GEKKO
import math
#Gekko model
m = GEKKO(remote=False)
#Time points
nt = 11
tm = np.linspace(0,100,nt)
m.time = tm
# Variables
Ro = m.Var(value=1.1)#air density
g = m.Const(value=9.80665)
pressure = m.Var(value=101325)#
T = m.Var(value=281,lb=100)#temperature
T0 = m.Const(value=288)#temperature at see level
S = m.Const(value=122.6)
Cd = m.Var(value=0.025)#drag coef 0.06 works
#Cl = m.Const(value=0.3)#lift couef
FuelFlow = m.Var()
D = m.Var(value=10000,lb=0)#drag
Thrmax = m.Const(value=200000)#maximum throttle
Thr = m.Var()
V = m.Var(value=200,lb=45,ub=240)#velocity
gamma = m.Var(value=0,lb=-0.6,ub=1.2)# Flight-path angle
#gammaa = gamma.value
Xi = m.Var(value=0, lb=-2, ub=2.0)# Heading angle
x = m.Var(value=0,lb=-1000,ub=1015000)#x position
y = m.Var(value=0,lb=-1000,ub=1011000)#y position
h = m.Var(value=1000,lb=-20000,ub=50000)# height
targetH = m.Param(value=10000) #target flight altitude
mass = m.Var(value=60000,lb=10000)
pathx = m.Const(value=1000000) #intended distance in x direction
pathy = m.Const(value=1000000) #intended distance in y direction
L = m.Var(value=250000)#lift
p = np.zeros(nt)
p[-1] = 1.0
final = m.Param(value=p)
m.options.MAX_ITER=10000 # iteration number
#Fixed Variable
tf = m.FV(value=1,lb=0.0001,ub=100.0)#
tf.STATUS = 1
# Controlled parameters
Tcontr = m.MV(value=0.6,lb=0.0,ub=1)# solver controls throttle pedal position
Tcontr.STATUS = 1
Tcontr.DCOST = 0
Mu = m.MV(value=0,lb=-1,ub=1)# solver controls bank angle
Mu.STATUS = 1
Mu.DCOST = 1e-3
Cl = m.MV(value=0.1,lb=-0.3,ub=0.9)# solver controls lift couef
Cl.STATUS = 1
Cl.DCOST = 1e-3
# Equations
m.Equation(Thr==Tcontr*Thrmax)
m.Equation(FuelFlow==0.75882*(1+(V/2938.5)))
m.Equation(D==0.5*Ro*(V**2)*Cd*S)
m.Equation(mass.dt()==tf*(-Thr*(FuelFlow/60000)))#
m.Equation(V.dt()==tf*((Thr-D-mass*g*m.cos(gamma))/mass)) #
m.Equation(x.dt()==tf*(V*(m.cos(gamma))*(m.cos(Xi))))#
m.Equation(x*final<=pathx)
#pressure and density part
m.Equation(T==T0-(0.0065*h))
m.Equation(pressure==101325*(1-(0.0065*h)/T0)**((g*0.0289652)/(8.31446*0.0065)))#
m.Equation(Ro*(8.31446*T)==(pressure*0.0289652))
#2D addition part
m.Equation(L==0.5*Ro*(V**2)*(Cl)*S)# Problem here or no problem, idk
m.Equation(mass*m.cos(gamma)*V*Xi.dt()==tf*((L*m.sin(Mu)))) #
m.Equation(y.dt()==tf*(V*(m.cos(gamma))*(m.sin(Xi))))#
#m.Equation((y-pathy)*final==0)
m.Equation(y*final<=pathy)
#3D addition part
m.Equation(h.dt()==tf*(V*(m.sin(gamma))))#
m.Equation(h*final<=targetH)
m.Equation(gamma.dt()==tf*((L*m.cos(Mu)-mass*g*m.cos(gamma))/mass*V))#
#Cd equation
m.Equation(Cd==((Cl)**2)/10)
# Objective Function
w = 1e4
m.Minimize(w*final*(x-pathx)**2) #1D part (x)
m.Minimize(w*final*(pathy-y)**2) #2D part (y)
m.Maximize(0.2*mass*tf*final) #objective function
m.options.IMODE = 6
m.options.NODES = 2 # it was 3 before
m.options.MV_TYPE = 1
m.options.SOLVER = 3
#m.open_folder() # to search for infeasibilities
m.solve()
tm = tm * tf.value[0]
fig, axs = plt.subplots(11)
fig.suptitle('Results')
axs[0].plot(tm,Tcontr,'r-',LineWidth=2,label=r'$Tcontr$')
axs[0].legend(loc='best')
axs[1].plot(tm,V.value,'b-',LineWidth=2,label=r'$V$')
axs[1].legend(loc='best')
axs[2].plot(tm,x.value,'r--',LineWidth=2,label=r'$x$')
axs[2].legend(loc='best')
axs[3].plot(tm,D.value,'g-',LineWidth=2,label=r'$D$')
axs[3].legend(loc='best')
axs[4].plot(tm,L.value,'p-',LineWidth=2,label=r'$L$')
axs[4].legend(loc='best')
axs[5].plot(tm,y.value,'p-',LineWidth=2,label=r'$y$')
axs[5].legend(loc='best')
axs[6].plot(tm,Ro.value,'p-',LineWidth=2,label=r'$Ro$')
axs[6].legend(loc='best')
axs[7].plot(tm,h.value,'p-',LineWidth=2,label=r'$h$')
axs[7].legend(loc='best')
axs[8].plot(tm,gamma.value,'p-',LineWidth=2,label=r'$gamma$')
axs[8].legend(loc='best')
axs[9].plot(tm,Cl.value,'p-',LineWidth=2,label=r'$Cl$')
axs[9].legend(loc='best')
axs[10].plot(tm,Cd.value,'p-',LineWidth=2,label=r'$Cd$')
axs[10].legend(loc='best')
plt.xlabel('Time')
#plt.ylabel('Value')
plt.show()
Important update
I want the solver to be able to keep gamma value within its boundaries till the necessary x and y values are achieved. The solver has issues with that.
Exhibit A.
In this case the simulation stopped because it could not keep gamma from exceeding its lower bound. gamma usually gets bigger when L (lift) * cos(mu) is bigger than mass*g*cos(gamma), and its gets lower when the situation is the opposite: mass*g*cos(gamma) is bigger than the L (lift) * cos(mu). Then the question becomes: why did suddenly mass*g*cos(gamma) became so much bigger than the L (lift) * cos(mu)? The g was constant. The mass did not change that much during the last moments of the simulation. The L (lift) did not become particularly small. cos(mu) is usually equal to 1 during this part of the simulation.
Exhibit B.
In this case the simulation stopped, once again, because it could not keep gamma value within its upper bound. It is visible , that Cl value during the last moments of the simulation was rising, it was necessary to keep gamma from exceeding its lower bound, but after that, the gamma value spiked and, for some reason, the solver did not lower Cl value, which forced the gamma value to exceed its upper bound, which forced the simulation to stop before the target goals are achieved.
In this case the issue is: Why does doesn't the solver lower the Cl value to stop gamma from exceeding its upper bound?

I'm not sure exactly what the problem is. It looks like the optimizer is controlling gamma throughout the horizon, and it always stays within its bounds of -0.6 to 1.2. Can you provide more information about what is going wrong?
max(gamma.value)
>>>> 1.2
min(gamma.value)
>>>> -0.6
Here is the solution that I generated by running your code:
Note that you get a fairly different solution if you solve the problem with a higher time resolution:

Related

GEKKO does not find optimal solution for a moon lander that does not go to zero height at zero speed

I'm trying to solve an optimal control problem where a person lands on the Moon, but has a device that can propel her upwards, via a control parameter, alpha. The objective of the problem is to find the minimal time in which the moon lander can reach the surface of the moon, at speed zero (all motion along the vertical axis).
Now, I have implemented the code using gekko, with python, and it works just fine if the person starts, for instance, 40m above the surface of the Moon, and reaches the surface (final height = 0m) at speed zero. However, if I modify the code to have the person start, say, from 50m above the surface and get to a final height of 10m, gekko always converges to a point of local infeasibility. I have tried many different final heights, and it only seems to work when I set it to 0.
Is it a gekko problem or am I overlooking something in my code?
I followed the ideas shown here: https://apmonitor.com/do/index.php/Main/MinimizeFinalTime
Here's my code:
from gekko import GEKKO
import numpy as np
import matplotlib.pyplot as plt
m = GEKKO(remote=True)
m.solver_options = ['max_iter 500']
nt = 501
tm = np.linspace(0,1,nt)
m.time = tm
#variables
initialMass = m.Const(value=10.)
initialSpeed = m.Const(value=0.)
initialHeight = m.Const(value=40.)
finalHeight = m.Const(value=10.)
g = m.Const(value=1.625) #gravitational acceleration
k = m.Const(value=0.01)
power = m.Const(value=initialMass)
v = m.Var(value=initialSpeed) #position
h = m.Var(value=initialHeight) #height
mass = m.Var(value=initialMass, lb=0., ub=initialMass) #mass
#MV
alpha = m.MV(value=0.2, lb=0.2, ub=1.) #control variable
alpha.STATUS = 1
#FV
tf = m.FV(value=1., lb=0.1)
tf.STATUS = 1 # the value can be adjusted by the optimizer
p = np.zeros(nt)
p[-1] = 1.
final = m.Param(value=p)
#Equations
m.Equation(v.dt() == tf*(-g+2*alpha*g*power/mass))
m.Equation(h.dt() == tf*v)
m.Equation(mass.dt() == -tf*k*alpha)
m.Equation(h*final == finalHeight)
m.Equation(v*final == 0.)
m.Obj(tf)
m.options.IMODE = 6
m.solve(disp=True)
print('Solution')
print('Final time: '+str(tf.value[0]))
I managed to solve the problem. First, I added a lower bound for h, the height,
h = m.Var(value=initialHeight,lb=finalHeight)
and then I added an objective function to be minimized. Instead of
m.Equation(h*final == finalHeight)
now I have written
m.Minimize(final*(h+1)**2)
I followed the ideas presented here https://apmonitor.com/wiki/index.php/Apps/BrysonDenhamProblem

1D Plane flight optimal control problem in gekko

I need to solve 1D plane flight optimal control problem. I have a plane that is 1000m high. I need it to travel 1000m forward along x-axis while minimizing fuel consumption. And when it achieves x=1000 I need program to stop.
My objective function looks like this: min J => -mass(tf). (By maximizing mass, the consumed fuel gets minimized). Optimizer controls the accelerator pedal.
The problem is subject to the following constraints: dx/dt = V; dV/dt = (Throttle - Drag)/mass; dm/dt = -Throttle * fuelflow.
I have developed a Gekko / Python script as follows. However, the optimization script does not achieve a solution, raising:
Exception: #error: Solution Not Found
I have tried to change stuff here and there, but it did not work and I'm not sure what the problem is. Here is my code:
import numpy as np
import matplotlib.pyplot as plt
from gekko import GEKKO
import math
#Gekko model
m = GEKKO(remote=False)
#Time points
nt = 101
m.time = np.linspace(0,100,nt)
# Parameters
Tcontr = m.MV(value=0.5,lb=0.3,ub=1) #throttle pedal position
Tcontr.STATUS = 1
Tcontr.DCOST = 0
# Variables
Ro = 1.1 #air density
S = 122.6
Cd = value=0.1
FuelFlow = m.Var(value=0.7)
D = m.Var() #drag
Thrmax = 200000 #maximum theoretical throttle
Thr = m.Var() #real throttle
V = m.Var() #Velocity
x = m.Var(value=0)
mass = m.Var(value=60000)
p = np.zeros(nt)
p[-1] = 1.0
final = m.Param(value=p)
# Equations
m.Equation(x.dt()==V)
m.Equation(Thr==Tcontr*Thrmax) #Throttle
m.Equation(V.dt()==(Thr-D)/mass)
m.Equation(mass.dt()==-Thr*FuelFlow)
m.Equation(D==0.5*Ro*(V**2)*Cd*S) #Drag
m.Equation(final*x==1000) # to stop when x==1000 is achieved
# Objective Function
m.Obj(-mass*final)
m.options.IMODE = 6
m.options.NODES = 2
m.options.MV_TYPE = 1
m.options.SOLVER = 3
m.open_folder()
m.solve()
It seems like the major problem was that the upper bound of your MV (Tcontr) is little bit too low to meet all the constraints.
And, I guess the "FuelFlow" is need to be a m.Const as I assumed it is a fuel flowrate per throttle.
Lastly, when you use the final for designating the value at the last time point, you need to make sure the associated Equation is also feasible for the entire time steps. I suggest to use this instead.
m.Equation(final*(x-1000)==0)
That way, you can achive your goal without posing any infeasible equation as your previous equation did. Your previous equation is infeasible at the most of the time points except the last time point with non-zero value. It's been resulting "0 (LHS) = 1000 (RHS)" before the final time point.
Please see the modified code below.
import numpy as np
import matplotlib.pyplot as plt
from gekko import GEKKO
# import math
#Gekko model
m = GEKKO(remote=True)
#Time points
nt = 101
m.time = np.linspace(0,100,nt)
# Parameters
Tcontr = m.MV(value=0.5,lb=0.3,ub=10) #throttle pedal position
Tcontr.STATUS = 1
Tcontr.DCOST = 1e-2
# Variables
Ro = 1.1 #air density
S = 122.6
Cd = value=0.1
FuelFlow = m.Const(value=0.7)
D = m.Var() #drag
Thrmax = 200000 #maximum theoretical throttle
Thr = m.Var() #real throttle
V = m.Var() #Velocity
x = m.Var(value=0)
mass = m.Var(value=60000)
p = np.zeros(nt)
p[-1] = 1.0
final = m.Param(value=p)
# Equations
m.Equation(x.dt()==V)
m.Equation(Thr==Tcontr*Thrmax) #Throttle
m.Equation(mass*V.dt()==(Thr-D))
m.Equation(mass.dt()==-Thr*FuelFlow)
m.Equation(D==0.5*Ro*(V**2)*Cd*S) #Drag
m.Equation(final*(x-1000)==0) # to stop when x==1000 is achieved
# Objective Function
m.Obj(-mass*final)
m.options.IMODE = 6
m.options.NODES = 3
m.options.MV_TYPE = 1
m.options.SOLVER = 3
# m.open_folder()
m.solve()

Optimizing cycling pacing strategy with Gekko, Converged to a point of local infeasibility

I am trying to produce a model of the motion of a cyclist which optimises the time taken to complete a 12km hill time trial, subject to various constraints such as total anaerobic energy as max power. I am attempting to reproduce the results in "Road Cycling Climbs Made Speedier by Personalized Pacing Strategies" by Wolf et al. The model so far is described by the code below: where Pow is the cyclist power, tf is the time taken to complete the course, E_kin is the kinetic energy of the cyclist, E_an is the anaerobic energy reserve (initially E_an_init) which decreases when the cyclist outputs a power higher than P_c, s is the gradient of the course and mu is the rolling resistance constant, eta is the mechanical efficiency of the bike and C_p is the aerodynamic drag coefficient. I am currently using IMODE 6 but I've also tried 9. When currently running the code I get an error saying the solution could not be found and I am not sure why this is occurring, I've tried checking the infeasibilities file but I can't make sense. I would appreciate some pointers as to where I might be going wrong. This is my first time using Gekko so I assume it may be something related to my implementation of the model.
from gekko import GEKKO
import numpy as np
m = GEKKO(remote=False) # initialize gekko
nt = 501
m.time = np.linspace(0,1,nt)
#Parameters, taken from paper, idepdendant of the path
#mass = 80
g = 9.81
mu = 0.004
r_w = 0.335
I_s = 0.658
M = 80#mass + (I_s/r_w**2)
C_p = 0.7
rho = 1.2
A = 0.4
eta = 0.95
P_c = 150
P_max = 1000
#Path dependant
x_f = 12000
s = 0.081
#Define anaerobic energy reserve, dependant on the cyclist
E_an_init = 20000
#Variables
xpos = m.Var(value = 0, lb = 0,ub = x_f,name='xpos')
E_kin = m.Var(value = 0, lb=0, name='E_kin')#start at 1m/s
E_an = m.Var(value = E_an_init, lb=0 , ub=E_an_init, name='E_an')
p = np.zeros(nt) # mark final time point
p[-1] = 1.0
final = m.Param(value=p)
# optimize final time
tf = m.FV(value=1,lb = 0, name='tf')
tf.STATUS = 1
# control changes every time period
Pow = m.MV(value=0,lb=0,ub=P_max, name='Pow')
Pow.STATUS = 1
# Equations
m.Equation(xpos.dt()==((2*E_kin/M)**0.5)*tf)
m.Equation(E_kin.dt()==(eta*Pow-((2*E_kin/M)**0.5)*(M*g*(s+mu*m.cos(m.atan(s))) + C_p*rho*A*E_kin/M))*tf)
m.Equation(E_an.dt()==(P_c-Pow)*tf)
m.Equation(xpos*final == x_f)
m.Equation(E_an*final >= 0)
#m.options.MAX_ITER = 1000
m.Minimize(tf*final) # Objective function
m.options.IMODE = 6 # optimal control mode
m.open_folder() # open folder if remote=False to see infeasibilities.txt
m.solve(disp=True) # solve
print('Final Time: ' + str(tf.value[0]) + "s")
The results of the failed attempt look okay when plotted but it says that that the 12km are covered in 48.6 seconds which can't be correct when the velocity of the cyclist is around 20km/hr for most of the 12km. Seems like the results are a function of nt, which makes no sense. Moreover, the velocity of the cyclist derived from dx/dt doesn't match that derived from the kinetic energy equation.
I have used this solution as a reference so far:
https://apmonitor.com/do/index.php/Main/MinimizeFinalTime
You need to reform the final equation for position. Otherwise, it is 0==12000 for all but the final point. The original form gives an infeasible solution.
m.Equation(xpos*final == x_f)
Here is the correct form of that equation.
m.Equation(final*(xpos-x_f)==0)
In this form, it is 0==0 everywhere except at the end where it is 1*(xpos-12000)==0. It also helps to give an objective function term to guide the final condition with:
m.Minimize(final*(xpos-x_f)**2)
This guides the solver towards the feasible solution to meet the end point.
The inequality constraint
m.Equation(E_an*final >= 0)
isn't needed if E_an has a lower bound of zero with E_an = m.Var(lb=0) or by setting E_an.LOWER=0. It should always be positive throughout the entire time horizon, not just at the final solution. It is easier for the solver to have a constraint on the variable (e.g. lower bound) than to add an additional inequality constraint (e.g. with m.Equation()).
from gekko import GEKKO
import numpy as np
import matplotlib.pyplot as plt
m = GEKKO() # initialize gekko
nt = 101
m.time = np.linspace(0,1,nt)
#Parameters, taken from paper, idepdendant of the path
#mass = 80
g = 9.81
mu = 0.004
r_w = 0.335
I_s = 0.658
M = 80 #mass + (I_s/r_w**2)
C_p = 0.7
rho = 1.2
A = 0.4
eta = 0.95
P_c = 150
P_max = 1000
#Path dependant
x_f = 12000
s = 0.081
#Define anaerobic energy reserve, dependant on the cyclist
E_an_init = 20000
#Variables
xpos = m.Var(value = 0, lb = 0,ub = x_f,name='xpos')
E_kin = m.Var(value = 0, lb=1e-3, name='E_kin')#start at 1m/s
E_an = m.Var(value = E_an_init, lb=0 , ub=E_an_init, name='E_an')
p = np.zeros(nt) # mark final time point
p[-1] = 1.0
final = m.Param(value=p)
# optimize final time
tf_guess = 900
tf = m.FV(value=tf_guess,lb=300, ub=10000, name='tf')
tf.STATUS = 1
# control changes every time period
Pow = m.MV(value=100,lb=0,ub=P_max, name='Pow')
Pow.STATUS = 1
# Equations
Esr = m.Intermediate((2*E_kin/M)**0.5)
m.Equation(xpos.dt()==Esr*tf)
m.Equation(E_kin.dt()==(eta*Pow-Esr*(M*g*(s+mu*m.cos(m.atan(s))) \
+ C_p*rho*A*E_kin/M))*tf)
m.Equation(E_an.dt()==(P_c-Pow)*tf)
m.Minimize(final*(xpos-x_f)**2)
m.Equation(final*(xpos-x_f)==0)
m.Minimize(final*(tf**2))
m.options.IMODE = 6 # optimal control mode
m.solve(disp=True) # solve
print('Final Time: ' + str(tf.value[0]) + "s")
print('Final Position: ' + str(xpos.value[-1]))
print('Final Energy: ' + str(E_an.value[-1]))
t = m.time*tf.value[0]/60.0
plt.figure(figsize=(9,6))
plt.subplot(4,1,1)
plt.plot(t,xpos.value,'r-',label='xpos')
plt.legend()
plt.subplot(4,1,2)
plt.plot(t,E_kin.value,'b-',label='E_kin')
plt.legend()
plt.subplot(4,1,3)
plt.plot(t,E_an.value,'k-',label='E_an')
plt.legend()
plt.subplot(4,1,4)
plt.plot(t,Pow.value,'-',color='orange',label='Pow')
plt.legend()
plt.xlabel('Time (min)')
plt.show()
The solution doesn't look quite right for a competitive cyclist to cover 12 km. It should be much faster so there may be a problem with the current equations. The final position constraint is met and the final energy is zero but the time should be faster.
Final Time: 5550.6961268s
Final Position: 12000.0
Final Energy: 0.0

Solving Shrodinger's equation for a particle in a harmonic potential well

Hello (this is my first time posting in stack overflow), I am trying the calculate the first 3 energy levels of a particle in a harmonic potential using the shooter method
The code is adapted from a script in Computational Physics by Mark Newman, this script calculated the ground state for a particle in a box.
here is the link http://www-personal.umich.edu/~mejn/cp/programs/squarewell.py
here is my adapted code
import numpy as np
from scipy.constants import m_e,hbar,elementary_charge
#define constants
vo = 50
a = 1e-11
#define limits of integration for adaptive runge-kutta method
xi = -10*a
xf = 10*a
N = 1000
dx = (xf-xi)/N
def V(x):#define harmonic potential well
return vo*((x**2)/(a**2))
def f(r,x,E): #schrodinger's equation
psi = r[0]
phi = r[1]
fpsi = psi
fphi = (2*m_e/(hbar**2))*(V(x)-E)*psi
return np.array([fpsi,fphi],float)
def solve(E): #calculates wave function for an energy E
psi = 0.0
phi = 1.0
r = np.array([psi,phi],float)
for x in np.arange(xi,xf,dx): #adaptive runge-kutta method
k1 = dx*f(r,x,E)
k2 = dx*f(r+0.5*k1,x+0.5*dx,E)
k3 = dx*f(r+0.5*k2,x+0.5*dx,E)
k4 = dx*f(r+k3,x+dx,E)
r += (k1+2*k2+2*k3+k4)/6
return r[0]
#finds the energy using secant method
E1 = 0.0
E2 = elementary_charge
psi2 = solve(E1)
target = elementary_charge/1000
while abs(E1-E2)>target:
psi1,psi2 = psi2,solve(E2)
E1,E2 = E2,E2-psi2*(E2-E1)/(psi2-psi1)
print (E2/elementary_charge)
when run I get this error
RuntimeWarning: invalid value encountered in double_scalars
E1,E2 = E2,E2-psi2*(E2-E1)/(psi2-psi1)
which I think means that psi2 and psi1 are too close together but I am not quite sure how to fix this
Yep! You are right about the values being too close to each other. Your code returns a nan. It is because of the division by zero.
I would suggest using a correction factor. Something like max(delta, (psi2-psi1)) in the denominator where delta can still be a very small value but it will prevent the division by zero.

Why Does My Code Update

So most of the time I am asking questions on stack exchange I usually have the wrong answer, but this time my code is producing the correct graphs I just want to know why. My question is why does theta update properly despite its dependence on omega coming after theta. By all means run my code if you do not believe me. Just as a warning I am not a computer scientist, I am just a physics student attempting to solve problems using computational methods, but I am interested why it works. Here is my code:
# This program is designed to show the difference between
# undamped, damped, and critically damped oscillators behavior over
# time.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
m = float(raw_input('Enter a mass for the pendelum '))
g = 9.8 # gravity
l = float(raw_input('Enter a number for length '))
theta = float(raw_input('Enter a number for postion (radians) '))
theta1 = float(raw_input('Enter a number for postion (radians) '))
theta2 = float(raw_input('Enter a number for postion (radians) '))
omega = 0
omega1 = 0
omega2 = 0
ArrayTheta = [theta]
ArrayTheta1 = [theta1]
ArrayTheta2 = [theta2]
q1 = float(raw_input('Enter a number for the damping constant '))
q2 = float(raw_input('Enter a number for the damping constant '))
q3 = float(raw_input('Enter a number for the damping constant '))
step = .001
tx = np.arange(0,5,step)
for i in np.arange(step,5,step):
theta = theta + omega*step
ArrayTheta.append(theta)
omega = omega + -(g/l)*np.sin(theta)*step+(-q1*omega*step)
theta1 = theta1 + omega1*step
ArrayTheta1.append(theta1)
omega1 = omega1 - (g/l)*np.sin(theta1)*step+(-q2*omega1*step)
theta2 = theta2 + omega2*step
ArrayTheta2.append(theta2)
omega2 = omega2 - (g/l)*np.sin(theta2)*step+(-q3*omega2*step)
# this does not really make sense to me that theta2 is able to update despite
# omega2 being after it. Clearly I should have put omega2 before theta2 yet
# it still works.
plt.plot(tx,ArrayTheta, color ='blue')
plt.plot(tx,ArrayTheta1, color ='red')
plt.plot(tx,ArrayTheta2, color ='green')
plt.ylabel('Position in Radians')
plt.xlabel('Time')
blue_patch = mpatches.Patch(color='blue', label='Damped q1')
red_patch = mpatches.Patch(color='red', label='Damped q2')
green_patch = mpatches.Patch(color='green', label='Damped q3')
plt.legend(handles=[blue_patch,red_patch,green_patch])
plt.show()
You set omega, omega1 and omega2 to zero in the beginning and you update them in each for loop. You simply don't see, that the first two data points of each theta are the same.
All of your omega variables are initialized to 0 before the loop starts, so on the first iteration they will have that value during the update of their respective theta variables, so the first theta value appended to the results will be the same as the initial value. The update to the omega value changes the loop's behavior on the later iterations.
It might make more sense if you changed your code so that the ArrayTheta lists start empty and your loop started at 0. That way you'd only get the one copy of the initial theta values in the lists, rather than the two your current code puts at the start:
omega = 0
omega1 = 0
omega2 = 0
ArrayTheta = [] # start these as empty lists
ArrayTheta1 = []
ArrayTheta2 = []
# unchanged stuff omitted
for i in np.arange(0,5,step): # start this loop at zero so you have the right number of steps
theta = theta + omega*step # on the first iteration, this doesn't change theta
ArrayTheta.append(theta) # so the first value in the list will be the original theta
omega = omega + -(g/l)*np.sin(theta)*step+(-q1*omega*step) # omega changes if theta != 0
The reason it is working is because you have defined the omega variables
omega = 0
omega1 = 0
omega2 = 0
So when you are at iteration 0 of your for loop, all the omega values are equal to 0 before you calculate theta. Then that makes your theta values as
theta = theta
theta2 = theta2
theta3 = theta3
You then update the value of omega. In iteration 1, the omega variables are equal to what you defined them as in iteration 0. Basically, when you update omega in step n, it is used to update theta in step n+1.
It looks like your thetas are angles, the omegas are their time derivatives, and that the time derivatives of the omega variables depend on the values of theta. You have a second order initial value problem.
Your code as-is works, but you are using naive Euler technique. This "works" because any n-dimensional second degree ODE can be re-expressed as a 2n-dimensional first degree ODE. This however discards a lot of the geometry of the problem.
You would be using the symplectic Euler technique if you change the code so that the derivatives are updated before the angles. This tends to preserve some of the geometry of the problem.

Categories