Gekko optimal control. How to create multiple termination objectives/conditions? - python

So I am simulating plane flight. The plane flies certain distance (pathx and pathy variables) and then the simulation stops, when certain pathx and pathy values are reached. The solver is trying to minimize the fuel consumption (m.Maximize(mass*tf*final), by maximizing the mass value. The solver controls the accelerator pedal position (Tcontr).
Right now the termination condition is defined like this:
For X axis:
m.Equation(x*final<=pathx)
and
m.Minimize(final*(x-pathx)**2)
And for Y axis:
m.Equation(y*final<=pathy)
and
m.Minimize(final*(y-pathy)**2)
And right now the simulation ends when the desired X value is achieved, while desired Y values is not being achieved.
How do I force the simulation to end when both desired (X and Y) values are achieved?
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.Const(value=0.1)#drag coef
Cl = m.Var(value=1)#lift couef
FuelFlow = m.Var()
D = m.Var(value=25000,lb=0)#drag
Thrmax = m.Const(value=200000)#maximum throttle
Thr = m.Var()#throttle
V = m.Var(value=100,lb=50,ub=240)#velocity
gamma = m.Var(value=0)# Flight-path angle
gammaa = gamma.value
Xi = m.Var(value=0)# Heading angle
Xii = Xi.value
x = m.Var(value=0,lb=0)#x position
y = m.Var(value=0,lb=0)#y position
h = m.Var(value=1000,lb=0)# height
mass = m.Var(value=60000,lb=10000)
pathx = m.Const(value=50000) #intended distance length
pathy = m.Const(value=50000)
L = m.Var(value=0.1)#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=1000.0)#
tf.STATUS = 1
# Controlled parameters
Tcontr = m.MV(value=0.2,lb=0.1,ub=1)# solver controls throttle pedal position
Tcontr.STATUS = 1
Tcontr.DCOST = 0
#Mu = m.Var(value=0)
Mu = m.MV(value=0,lb=-1.5,ub=1.5)# solver controls bank angle
Mu.STATUS = 1
Mu.DCOST = 0
Muu = Mu.value
# 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))#
m.Equation(x.dt()==tf*(V*(math.cos(gammaa.value))*(math.cos(Xii.value))))#
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)
m.Equation(Xi.dt()==tf*((L*math.sin(Muu.value))/(mass*V)))
m.Equation(y.dt()==tf*(V*(math.cos(gammaa.value))*(math.sin(Xii.value))))#
m.Equation(y*final<=pathy)
# Objective Function
m.Minimize(final*(x-pathx)**2) #1D part
m.Minimize(final*(y-pathy)**2) #2D part
m.Maximize(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(8)
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,mass.value,'g:',LineWidth=2,label=r'$mass$')
axs[4].legend(loc='best')
axs[5].plot(tm,T.value,'p-',LineWidth=2,label=r'$T$')
axs[5].legend(loc='best')
axs[6].plot(tm,Mu.value,'p-',LineWidth=2,label=r'$Mu$')
axs[6].legend(loc='best')
axs[7].plot(tm,y.value,'p-',LineWidth=2,label=r'$y$')
axs[7].legend(loc='best')
plt.xlabel('Time')
#plt.ylabel('Value')
plt.show()
Important Update
It looks like termination conditions (all of the above) work as intended. Y axis values can not be reached, because it looks like the math involving it is not working properly.
Y value is being calculated by this equation:
I have turned into this: m.Equation(y.dt()==tf*(V*(math.cos(gammaa.value))*(math.sin(Xii.value))))
Where: V is true air speed (works properly);
tf is a variable that controls simulation time (works properly);
gammaa is derived from gamma, which is flight-path angle, which is 0 in this simulation (works properly);
Xii is derived from Xi, which is heading angle (the main culprit).
Xi value is defined by this equation:
I turned it into this: m.Equation(Xi.dt()==tf*((L*math.sin(Muu.value))/(mass*V)))
Where: L is lift force (works properly);
mass is mass (works properly);
V is true air speed (works properly);
Muu is derived from Mu, which is bank angle, the solver controlled variable (probably the bad apple here).
Y value calculation should be working like this: solver changes Mu, which affects The heading angle Xi, which then should affect Y value.
After some experiments it looks like the range of changes of the Mu value during the simulation is so small, that it barely affects Y value. But it should be a lot wider since Mu boundaries are quite wide (lb=-1.5,ub=1.5). I tried to remove those boundaries, but it did not affect the results of the simulation.
What could be messing up everything here?

Try adding a hard terminal constraint for both:
m.Equation((x-pathx)*final==0)
m.Equation((y-pathy)*final==0)
Alternatively, increase the weight on the terminal condition.
w = 1e3
m.Minimize(w*final*(x-pathx)**2) #1D part
m.Minimize(w*final*(y-pathy)**2) #2D part
You may need to use one or both strategies. Terminal conditions can be challenging to solve.
Response to Important Update
Use the Gekko functions with m.sin() and m.cos() instead of the math functions. Also, don't use .value in the equations. Gekko needs the full symbolic graph so that it can compile the equations into byte-code and produce function evaluations and the derivatives with automatic differentiation.
m.Equation(y.dt()==tf*(V*(m.cos(gammaa))*(m.sin(Xii))))
It also helps to avoid divide by zero by multiplying any denominator over to the other side.
m.Equation(mass*V*Xi.dt()==tf*((L*m.sin(Muu))))

Related

Numerical solution for a pendulum

I am having a trouble in providing a graphical representation of my system, which happens to be a harmonically driven pendulum. The problem is shown below for reference.
Problem
The source code I used is shown below using the Verlet Scheme.
#Import needed modules
import numpy as np
import matplotlib.pyplot as plt
#Initialize variables (Initial conditions)
g = 9.8 #Gravitational Acceleration
L = 2.0 #Length of the Pendulum
A0 = 3.0 #Initial amplitude of the driving acceleration
v0 = 0.0 #Initial velocity
theta0 = 90*np.pi/180 #Initial Angle
drivingPeriod = 20.0 #Driving Period
#Setting time array for graph visualization
tau = 0.1 #Time Step
tStop = 10.0 #Maximum time for graph visualization derived from Kinematics
t = np.arange(0., tStop+tau, tau) #Array of time
theta = np.zeros(len(t))
v = np.zeros(len(t))
#Verlet Method
theta[0] = theta0
v[0] = v0
for i in range(len(t)-1):
accel = -((g + (A0*np.sin((2*np.pi*t) / drivingPeriod)))/L) * np.sin(theta[i])
theta[i+1] = theta[i] + tau*v[i] + 0.5*tau**2*accel[i]
v[i+1] = v[i] + 0.5*tau*(accel[i] + accel[i+1])
#Plotting and saving the resulting graph
fig, ax1 = plt.subplots(figsize=(7.5,4.5))
ax1.plot(t,theta*(180/np.pi))
ax1.set_xlabel("Time (t)")
ax1.set_ylabel("Theta")
plt.show()
A sample output is shown.
Output
The pendulum should just go back to its initial angle. How can I solve this issue? Notice that as time evolves, my angle measure (degrees) also increases. I want it to only have a domain of 0 degrees to 360 degrees.
Please change the array computation
accel = -((g + (A0*np.sin((2*np.pi*t) / drivingPeriod)))/L) * np.sin(theta[i])
theta[i+1] = theta[i] + tau*v[i] + 0.5*tau**2*accel[i]
into the proper element computation at the correct place
theta[i+1] = theta[i] + tau*v[i] + 0.5*tau**2*accel[i]
accel[i+1] = -((g + (A0*np.sin((2*np.pi*t[i+1]) / drivingPeriod)))/L) * np.sin(theta[i+1])
Note that you need to compute accel[0] separately outside the loop.
It makes the code more readable if you separate out the specifics of the physical model and declare at the start
def acceleration(t,theta):
return -((g + (A0*np.sin((2*np.pi*t) / drivingPeriod)))/L) * np.sin(theta)
so that later you just call
accel[i+1]=acceleration(t[i+1],theta[i+1])
And even then, with a forced oscillation your system is open, it is possible that the forcing action pumps energy into the pendulum, causing it to start to rotate. This is what your graph shows.
The Verlet method like any symplectic method only promises to somewhat have a constant energy if the system is closed and conservative, that is, in the most common cases, no outside influence and all forces are gradient forces.

How to do space discretization in Gekko?

The goal is to minimize time to complete the lap with Energy constraint this is why my objective is the integral of the speed over distance, but I canโ€™t seem to figure out how to derive and integrate over distance and not time(dt).
If you don't have time in your problem then you can specify m.time as the distance points for integration. However, your differential equations are based on time such as ds/dt = v in 1D. You need to keep time as the variable because that is defined for each of the differentials.
One way to minimize the lap time is to create a new tlap=FV() and then scale all of the differentials by that new adjustable value.
tlap=FV()
m.Equation(s.dt()==v*tlap)
With this tf value, you can minimize final time to reach a final destination.
m.Minimize(tf*final)
This is similar to the rocket launch problem that minimizes final time and control action.
import numpy as np
import matplotlib.pyplot as plt
from gekko import GEKKO
# create GEKKO model
m = GEKKO()
# scale 0-1 time with tf
m.time = np.linspace(0,1,101)
# options
m.options.NODES = 6
m.options.SOLVER = 3
m.options.IMODE = 6
m.options.MAX_ITER = 500
m.options.MV_TYPE = 0
m.options.DIAGLEVEL = 0
# final time
tf = m.FV(value=1.0,lb=0.1,ub=100)
tf.STATUS = 1
# force
u = m.MV(value=0,lb=-1.1,ub=1.1)
u.STATUS = 1
u.DCOST = 1e-5
# variables
s = m.Var(value=0)
v = m.Var(value=0,lb=0,ub=1.7)
mass = m.Var(value=1,lb=0.2)
# differential equations scaled by tf
m.Equation(s.dt()==tf*v)
m.Equation(mass*v.dt()==tf*(u-0.2*v**2))
m.Equation(mass.dt()==tf*(-0.01*u**2))
# specify endpoint conditions
m.fix_final(s, 10.0)
m.fix_final(v, 0.0)
# minimize final time
m.Minimize(tf)
# Optimize launch
m.solve()
print('Optimal Solution (final time): ' + str(tf.value[0]))
# scaled time
ts = m.time * tf.value[0]
# plot results
plt.figure(1)
plt.subplot(4,1,1)
plt.plot(ts,s.value,'r-',linewidth=2)
plt.ylabel('Position')
plt.legend(['s (Position)'])
plt.subplot(4,1,2)
plt.plot(ts,v.value,'b-',linewidth=2)
plt.ylabel('Velocity')
plt.legend(['v (Velocity)'])
plt.subplot(4,1,3)
plt.plot(ts,mass.value,'k-',linewidth=2)
plt.ylabel('Mass')
plt.legend(['m (Mass)'])
plt.subplot(4,1,4)
plt.plot(ts,u.value,'g-',linewidth=2)
plt.ylabel('Force')
plt.legend(['u (Force)'])
plt.xlabel('Time')
plt.show()
There are a few problems that I fixed with your current solution:
Variables w and st are not used
The STATUS for p_s and s_s should be On (1) to be calculated by the solver
The number of time points (50000) is really long and will create a very large problem that will be hard to solve in one solution. You may consider breaking this into successive solutions that advance one cycle (m.options.TIME_SHIFT=1) or multiple (m.options.TIME_SHIFT=10) for each m.solve() command.
There may be references that can help with the problem formulation. It appears that you are taking a more physics-based approach than a data driven approach.
Switched to the APOPT solver for a successful solution.
from gekko import GEKKO
import numpy as np
import matplotlib.pyplot as plt
m = GEKKO(remote=False)
#Constants
mass = m.Const(77) #mass of the rider
g = m.Const(9.81) #gravity
H = m.Const(1.2) #height of the rider
L = m.Const(value=1.4) #lenght of the wheelbase of the bicycle
E_n = m.Const(value=22000) #Energy that can be used
c_rr = m.Const(value=0.0035) #coefficient of drag
s_max = m.Const(value=0.52) #max steer angle
W_m = m.Const(value=1800) #max power that the rider can produce
vWn = m.Const(value=50) #maximal power output variation
vSn = m.Const(value=0.52) #maximal steer output variation
kv = m.Const(value=0.13) #air drag coefficient
ws = m.Const(value=0) #wind speed
Ix = m.Const(value=77) #inertia
W_c = m.Const(value=440) #critical power(watts)
Wj1 = m.Const(value=0.01) ##weighting factors that scale the penalisation
Wj2 = m.Const(value=0.01) #weighting factors that scale the penalisation
dist = 1000 ##distance that that the rider needs to travel
nt = 100 ##calculation at every 10 meters
m.time = np.linspace(0,dist,nt)
p = np.zeros(nt)
p[-1] = 1.0
final = m.Param(value=p)
slope = np.zeros(nt) #SET THE READ CURVATURE AND SLOPE TO 0 for experimentation later we will import it from real road.
curv = np.zeros(nt) #SET THE READ CURVATURE AND SLOPE TO 0 for experimentation later we will import it from real road.
####Import Road Characterisitc####
k = m.Param(value=curv) ##road_curvature
b = m.Param(value=slope) ##slope angle
###Control Variable###
p_s = m.MV(value=1,lb=-1000,ub=1000); p_s.STATUS = 1 ##power
s_s = m.MV(value=0,lb=-100,ub=100); s_s.STATUS = 1 ##steer
###State Variable###
# Not used
#w = m.Param(value=10,lb=-10000,ub=1800) #power done by the rider (positive:pedaling, negative:braking)
#st = m.Param(value=0,lb=-30,ub=30) ##steer angle
s = m.Var(value=1,lb=1e-4,ub=100) #speed along road
v = m.Var(value=1, lb=0, ub=16) #velocity
n = m.Var(value=0,lb=-4, ub=4) ##displacement fron the center of the road upper bound and lower bound are the road width
h = m.Var(value=0,lb=-10,ub=10) #heading of the bicycle
r = m.Var(0,lb=-0.78, ub=0.78) ##roll
r_dot = m.Var(value=0,lb=-100,ub=100) ##roll_rate
W_n = m.Var(value=0.1,lb=-1, ub=1) ##normalised power
s_n = m.Var(value=0,lb=-1, ub=1) #normalised steer angle
e = m.Var(value=22000, lb=0, ub=22000) #energy remaining
####Equations####
#1 dynamics of travelling speed s(s) along the reference line
m.Equation((1-(n-k))*s.dt()==v*m.cos(h))
#2:dynamics of the longitudinal velocity of the bicycle
c1 = m.Intermediate((v*mass)/W_m,'c1')
m.Equation(c1*s*v.dt()==(W_n
-( (v/W_m) * (mass*g* (c_rr* m.cos(b)+m.sin(b))) )
-((v/W_m) * kv*(v-(ws*h))**2)
)
)
#3: dynamic of the lateral displacement
m.Equation(s*n.dt()==m.sin(k))
#4: heading of the bicycle ๐›ผ(s):
m.Equation((L*s)*h.dt()==(s_n*s_max)-k*(L*s))
#5&6: dynamics of the roll angle ๐œ™ (rad) and its rate of change ๐œ™dot(s)
m.Equation(s*r.dt()==(r_dot))
m.Equation(((h**2)*mass+Ix)*(g*L*s)*r_dot.dt()==(H*mass*g)*((v**2)*s_max*s_n+L*g*r))
#7: dynamics of the normalised power output Wn
m.Equation(s*W_n.dt()==p_s)
##8: dynamics of the normalised steering angle ๐›ฟn
m.Equation(s*s_n.dt()==s_s)
#9: dynamic equation describing the evolution of the anaerobic sources
# use lower bound on W_n instead of m.min2(0,W_n)
m.Equation((s*E_n)*e.dt()==(-(W_n*W_m-W_c) ))
####OBJECTIVE####
m.Minimize(m.integral( (1/s) * (1+(Wj1*((p_s/vWn)**2))+(Wj2*((s_s/vSn)**2))) )*final)
m.options.IMODE = 6 # optimal control
m.options.SOLVER = 1 # solver (APOPT)
m.options.DIAGLEVEL=0
#m.open_folder()
m.solve(disp=True, debug=True) # Solve
With this script, I get a successful solution but I haven't investigated the objective function to see if it is giving a reasonable answer.
----------------------------------------------------------------
APMonitor, Version 0.9.2
APMonitor Optimization Suite
----------------------------------------------------------------
--------- APM Model Size ------------
Each time step contains
Objects : 0
Constants : 16
Variables : 15
Intermediates: 1
Connections : 0
Equations : 12
Residuals : 11
Number of state variables: 2970
Number of total equations: - 2772
Number of slack variables: - 0
---------------------------------------
Degrees of freedom : 198
----------------------------------------------
Dynamic Control with APOPT Solver
----------------------------------------------
Iter Objective Convergence
0 2.51001E+03 1.00000E+00
1 4.36075E+04 5.66676E-01
2 3.43092E+03 5.36156E-01
3 7.36773E+03 4.16203E-01
4 2.75250E+03 9.29407E-02
5 4.12278E+03 1.93521E-02
6 5.80466E+05 7.35244E-02
7 4.99119E+04 1.27246E-01
8 2.11556E+03 4.52552E-01
9 6.32932E+03 2.14605E-01
Iter Objective Convergence
10 8.16639E+01 2.76062E-01
11 6.80002E+02 8.83214E-01
12 4.71706E+01 2.87555E-01
13 1.28152E+02 1.36994E-03
14 1.01698E+01 1.08406E+01
15 1.13082E+01 3.00869E+00
16 1.03199E+01 8.67971E+00
17 1.02638E+01 1.28697E-02
18 1.02636E+01 5.64896E-05
19 1.02636E+01 6.72710E-07
Iter Objective Convergence
20 1.02636E+01 6.72710E-07
Successful solution
---------------------------------------------------
Solver : APOPT (v1.0)
Solution time : 3.1271 sec
Objective : 10.263550885927089
Successful solution
---------------------------------------------------
You may want to create plots to make sure that the equations and solver are giving a correct solution. Here is an animation and source code that shows how to set up a model predictive controller with a finite horizon and that advances in time (or space) for each solve command.
The finite horizon approach is used commonly in industrial control to ensure that the optimizer can finish within the required cycle time and balance that with the length of the horizon to "see" future constraints and opportunities for energy or production optimization.

Different values of Initial weight of linear regression is converging to different minimized cost value

I have implemented a univariate linear regression in python. The code is given below:
import numpy as np
import matplotlib.pyplot as plt
x = np.array([1,2,4,3,5,7,9,11])
y = np.array([3,5,9,7,11,15,19,23])
def hypothesis(w0,w1,x):
return w0 + w1*x
def cost_cal(y,w0,w1,x,m):
diff = hypothesis(w0,w1,x)-y
diff_sqr = np.square(diff)
total_cost = np.sum(diff)
total_cost_sqr = (1/(2*m)) * np.sum(diff_sqr)
return total_cost, total_cost_sqr
def gradient_descent(w0,w1,alpha,x,m,y):
cost, cost_sqr = cost_cal(y,w0,w1,x,m)
temp0 = (alpha/m) * cost
temp1 = (alpha/m) * np.sum(cost*x)
w0 = w0 - temp0
w1 = w1 - temp1
return w0,w1
These are my hypothesis, cost, and gradient_descent functions implemented in python. When I use the initial weight w0 = 0 and w1 = 0, my minimized cost is 0.12589726000013188. But, if I initialize the w0 = -1 and w1 = -2, the minimized cost is 0.5035890400005265. What is the reason behind the different minimum costs using different initial weight values? As the error function MSE, is a convex function, shouldn't it reach the global minimum? Am I doing something wrong?
w0=0
w1=0
alpha =0.0001
m = 8
z = 5000
c = np.zeros(z)
cs = np.zeros(z)
w0_arr=np.zeros(z)
w1_arr=np.zeros(z)
index = np.zeros(z)
i = 0
while (i<z):
index[i] = i
c[i],cs[i] = cost_cal(y,w0,w1,x,m)
#print(i, c[i], cs[i])
w0, w1 = gradient_descent(w0,w1,alpha,x,m,y)
w0_arr[i],w1_arr[i] = w0,w1
i=i+1
inc = np.argmin(cs)
print(inc)
print(cs[inc])
The answer might vary based on your initial vector u choose in weight space. Apart from fact that the cost function is convex the curve has many critical points so it completely depends on the initial point or weights where we end up whether in local or global minima.
image link
https://1.bp.blogspot.com/-ltxplazySpc/XQG4aprY2iI/AAAAAAAABVo/xAqLIln9OWkig5rq4AU2sBFuPBuxW5CFQCLcBGAs/w1200-h630-p-k-no-nu/local_vs_global_minima.PNG
as per the image in the given link if u start from an initial point which is at the left corner we end up landing in global minima if we start from the right end we end up landing in local minima. Cost may vary by a huge difference but in most cases, the difference is not very large in case of local or global minima so if the cost is varying by big difference u need to cross-check once. Picking initial weights randomly is a good practice they should not be set manually.
in gradient_descent function, temp0 is assigned an array instead of the value, the sum of that array must be done before adding.

Optimal trajectory to minimize force, issues with final conditions

I am trying to find a trajectory that minimizes the squared integral of the force to move a block from one point to another. Here are the system dynamics:
dx/dt = v (derivative of position is velocity)
dv/dt = u (derivative of velocity is acceleration, which is what I am trying to minimize)
min integral of u**2
The initial conditions and final conditions are:
x(0) = 0, v(0) = 0
x(1) = 1, v(1) = 1
I have implemented this in python using the Gekko library, but I cannot get the final conditions working properly. Using m.fix() to fix the end position makes the problem unsolvable.
Reading online, I used m.Minimize() to make a soft constraint, but the solution was very far off from the end conditions. I added an extra equation to make the velocity less than zero at the end, and that made the solution look like the correct solution, albeit the end position was wrong (if the solution was scaled by a factor, it would be correct).
I should I properly solve this problem?
from gekko import GEKKO
import numpy as np
import matplotlib.pyplot as plt
##model
m = GEKKO() # initialize gekko
nt = 101
m.time = np.linspace(0,1,nt)
# Variables
x = m.Var(value=0)
v = m.Var(value=0)
u = m.Var( fixed_initial=False)
p = np.zeros(nt) # mark final time point
p[-1] = 1.0
final = m.Param(value=p)
# Equations
m.Equation(x.dt()==v)
m.Equation(v.dt()==u)
#m.Equation(x*final >= 1) ##error: Solution Not Found
m.Equation(v*final <= 0)
m.Minimize(final*(x-1)**2)
m.Minimize(final*(v-0)**2)
m.Obj(m.integral(u**2)*final) # Objective function
m.options.IMODE = 6 # optimal control mode
##solve
m.solve() # solve
##plot
plt.figure(1) # plot results
plt.plot(m.time,x.value,'k-',label=r'$x$')
plt.plot(m.time,v.value,'b-',label=r'$v$')
plt.plot(m.time,u.value,'r--',label=r'$u$')
plt.legend(loc='best')
plt.xlabel('Time')
plt.ylabel('Value')
##show plot
plt.show()
Plot of my results
You can fix the problem by putting a higher weight on the final conditions:
m.Minimize(final*1e5*(x-1)**2)
m.Minimize(final*1e5*(v-0)**2)
There is still some tradeoff with the u minimization but it is minimal.
The constraint m.Equation(x*final >= 1) is infeasible when final=0 because this results in the inequality 0 >= 1. If you'd like to use the final position constraint, you'll need to use m.Equation((x-1)*final >= 0) so that the constraint is enforced only at the end but is feasible (0 >= 0) elsewhere. You don't necessarily need the hard constraints with the soft (objective function) constraints for the final condition. Here is a related problem with an inverted pendulum.
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)
# Variables
x = m.Var(value=0)
v = m.Var(value=0)
u = m.Var(fixed_initial=False)
p = np.zeros(nt) # mark final time point
p[-1] = 1.0
final = m.Param(value=p)
# Equations
m.Equation(x.dt()==v)
m.Equation(v.dt()==u)
m.Equation((x-1)*final >= 0)
m.Equation(v*final <= 0)
m.Minimize(final*1e5*(x-1)**2)
m.Minimize(final*1e5*(v-0)**2)
m.Obj(m.integral(u**2)*final) # Objective function
m.options.IMODE = 6 # optimal control mode
m.solve() # solve
plt.figure(1) # plot results
plt.grid()
plt.plot(m.time,x.value,'k-',label=r'$x$')
plt.plot(m.time,v.value,'b-',label=r'$v$')
plt.plot(m.time,u.value,'r--',label=r'$u$')
plt.legend(loc='best')
plt.xlabel('Time')
plt.ylabel('Value')
plt.show()

Solving ODE numerically with Python

I am solving an ODE for an harmonic oscillator numerically with Python. When I add a driving force it makes no difference, so I'm guessing something is wrong with the code. Can anyone see the problem? The (h/m)*f0*np.cos(wd*i) part is the driving force.
import numpy as np
import matplotlib.pyplot as plt
# This code solves the ODE mx'' + bx' + kx = F0*cos(Wd*t)
# m is the mass of the object in kg, b is the damping constant in Ns/m
# k is the spring constant in N/m, F0 is the driving force in N,
# Wd is the frequency of the driving force and x is the position
# Setting up
timeFinal= 16.0 # This is how far the graph will go in seconds
steps = 10000 # Number of steps
dT = timeFinal/steps # Step length
time = np.linspace(0, timeFinal, steps+1)
# Creates an array with steps+1 values from 0 to timeFinal
# Allocating arrays for velocity and position
vel = np.zeros(steps+1)
pos = np.zeros(steps+1)
# Setting constants and initial values for vel. and pos.
k = 0.1
m = 0.01
vel0 = 0.05
pos0 = 0.01
freqNatural = 10.0**0.5
b = 0.0
F0 = 0.01
Wd = 7.0
vel[0] = vel0 #Sets the initial velocity
pos[0] = pos0 #Sets the initial position
# Numerical solution using Euler's
# Splitting the ODE into two first order ones
# v'(t) = -(k/m)*x(t) - (b/m)*v(t) + (F0/m)*cos(Wd*t)
# x'(t) = v(t)
# Using the definition of the derivative we get
# (v(t+dT) - v(t))/dT on the left side of the first equation
# (x(t+dT) - x(t))/dT on the left side of the second
# In the for loop t and dT will be replaced by i and 1
for i in range(0, steps):
vel[i+1] = (-k/m)*dT*pos[i] + vel[i]*(1-dT*b/m) + (dT/m)*F0*np.cos(Wd*i)
pos[i+1] = dT*vel[i] + pos[i]
# Ploting
#----------------
# With no damping
plt.plot(time, pos, 'g-', label='Undampened')
# Damping set to 10% of critical damping
b = (freqNatural/50)*0.1
# Using Euler's again to compute new values for new damping
for i in range(0, steps):
vel[i+1] = (-k/m)*dT*pos[i] + vel[i]*(1-(dT*(b/m))) + (F0*dT/m)*np.cos(Wd*i)
pos[i+1] = dT*vel[i] + pos[i]
plt.plot(time, pos, 'b-', label = '10% of crit. damping')
plt.plot(time, 0*time, 'k-') # This plots the x-axis
plt.legend(loc = 'upper right')
#---------------
plt.show()
The problem here is with the term np.cos(Wd*i). It should be np.cos(Wd*i*dT), that is note that dT has been added into the correct equation, since t = i*dT.
If this correction is made, the simulation looks reasonable. Here's a version with F0=0.001. Note that the driving force is clear in the continued oscillations in the damped condition.
The problem with the original equation is that np.cos(Wd*i) just jumps randomly around the circle, rather than smoothly moving around the circle, causing no net effect in the end. This can be best seen by plotting it directly, but the easiest thing to do is run the original form with F0 very large. Below is F0 = 10 (ie, 10000x the value used in the correct equation), but using the incorrect form of the equation, and it's clear that the driving force here just adds noise as it randomly moves around the circle.
Note that your ODE is well behaved and has an analytical solution. So you could utilize sympy for an alternate approach:
import sympy as sy
sy.init_printing() # Pretty printer for IPython
t,k,m,b,F0,Wd = sy.symbols('t,k,m,b,F0,Wd', real=True) # constants
consts = {k: 0.1, # values
m: 0.01,
b: 0.0,
F0: 0.01,
Wd: 7.0}
x = sy.Function('x')(t) # declare variables
dx = sy.Derivative(x, t)
d2x = sy.Derivative(x, t, 2)
# the ODE:
ode1 = sy.Eq(m*d2x + b*dx + k*x, F0*sy.cos(Wd*t))
sl1 = sy.dsolve(ode1, x) # solve ODE
xs1 = sy.simplify(sl1.subs(consts)).rhs # substitute constants
# Examining the solution, we note C3 and C4 are superfluous
xs2 = xs1.subs({'C3':0, 'C4':0})
dxs2 = xs2.diff(t)
print("Solution x(t) = ")
print(xs2)
print("Solution x'(t) = ")
print(dxs2)
gives
Solution x(t) =
C1*sin(3.16227766016838*t) + C2*cos(3.16227766016838*t) - 0.0256410256410256*cos(7.0*t)
Solution x'(t) =
3.16227766016838*C1*cos(3.16227766016838*t) - 3.16227766016838*C2*sin(3.16227766016838*t) + 0.179487179487179*sin(7.0*t)
The constants C1,C2 can be determined by evaluating x(0),x'(0) for the initial conditions.

Categories

Resources