I'm new to both GEKKO and Python so bear with me. I'm trying to do a control optimization problem of a system of the general form:
Equations of Motion
This requires that I solve the 2nd-order terms as either Intermediates or Equations. However, I'm really struggling with how to express this correctly. I've read elsewhere that Gekko objects do not accept np.linalg.invbut can solve these equations when expressed implicitly. The below code attempts to express the .dt() terms implicitly, but the solver provides the below error message:
Exception: #error: Intermediate Definition
Error: Intermediate variable with no equality (=) expression
[((i3)*(cos(v3)))0((i6)*(cos((v3-v5))))]
STOPPING...
The above message (I believe) is referring to the M matrix. It's unclear to me if this is an issue with the formulation of my Intermediates or with the Equations.
Note: in this case, I could workaround this issue by explicitly expressing the 2nd-order terms, but even for such a simple system, those equations are rather large. For higher complexity systems, that won't be practical, so I'd really appreciate a method to solve the equations of motion in matrix form.
Much appreciated.
import math
import matplotlib
matplotlib.use("TkAgg")
import numpy as np
from gekko import GEKKO
#Defining a model
m = GEKKO()
#################################
#Define initial and final conditions and limits
x0 = -1; xdot0 = 0
q10 = 0; q1dot0 = 0
q20 = 0; q2dot0 = 0
xf = 1; xdotf = 0
q1f = 0; q1dotf = 0
q2f = 0; q2dotf = 0
xmin = -1.5; xmax = 1.5
umin = -10; umax = 10
#Defining the time parameter (0, 1)
N = 100
t = np.linspace(0,1,N)
m.time = t
#Final time
TF = m.FV(3,lb=2,ub=25); TF.STATUS = 1
end_loc = len(m.time)-1
#Parameters
mc = m.Param(value=1) #cart mass
m1 = m.Param(value=.01) #link 1 mass
m2 = m.Param(value=.01) #link 2 mass
L1 = m.Param(value=1) #link 1 length
LC1 = m.Param(value=.5) #link 1 CM pos
L2 = m.Param(value=1) #link 1 length
LC2 = m.Param(value=.5) #link 1 CM pos
I1 = m.Param(value=.01) #link 1 MOI
I2 = m.Param(value=.01) #link 2 MOI
g = m.Const(value=9.81) #gravity
pi = math.pi; pi = m.Const(value=pi)
#MV
u = m.MV(value=0,lb=umin,ub=umax); u.STATUS = 1
#State Variables
x, xdot, q1, q1dot, q2, q2dot = m.Array(m.Var, 6)
x.value = x0; xdot.value = xdot0
q1.value = q10; q1dot.value = q1dot0
q2.value = q20; q2dot.value = q2dot0
x.LOWER = xmin; x.UPPER = xmax
#Intermediates
h1 = m.Intermediate(mc + m1 + m2)
h2 = m.Intermediate(m1*LC1 + m2*L1)
h3 = m.Intermediate(m2*LC2)
h4 = m.Intermediate(m1*LC1**2 + m2*L1**2 + I1)
h5 = m.Intermediate(m2*LC2*L1)
h6 = m.Intermediate(m2*LC2**2 + I2)
h7 = m.Intermediate(m1*LC1*g + m2*L1*g)
h8 = m.Intermediate(m2*LC2*g)
M = m.Intermediate(np.array([[h1, h2*m.cos(q1), h3*m.cos(q2)],
[h2*m.cos(q1), h4, h5*m.cos(q1-q2)],
[h3*m.cos(q2), h5*m.cos(q1-q2), h6]]))
C = m.Intermediate(np.array([[0, -h2*q1dot*m.sin(q1), -h3*q2dot*m.sin(q2)],
[0, 0, h5*q2dot*m.sin(q1-q2)],
[0, -h5*q1dot*m.sin(q1-q2), 0]]))
G = m.Intermediate(np.array([[0], [-h7*m.sin(q1)], [-h8*m.sin(q2)]]))
U = m.Intermediate(np.array([[u], [0], [0]]))
DQ = m.Intermediate(np.array([[xdot], [q1dot], [q2dot]]))
CDQ = m.Intermediate(C*DQ)
#Defining the State Space Model
m.Equation(M*np.array([[xdot.dt()/TF], [q1dot.dt()/TF], [q2dot.dt()/TF]]) == U - CDQ - G)
m.Equation(x.dt()/TF == xdot)
m.Equation(q1.dt()/TF == q1dot)
m.Equation(q2.dt()/TF == q2dot)
#Defining final condition
m.fix(x,pos=end_loc,val=xf)
m.fix(xdot,pos=end_loc,val=xdotf)
m.fix(q1,pos=end_loc,val=q1f)
m.fix(q1dot,pos=end_loc,val=q1dotf)
m.fix(q2,pos=end_loc,val=q2f)
m.fix(q2dot,pos=end_loc,val=q2dotf)
#Try to minimize final time
m.Obj(TF)
m.options.IMODE = 6 #MPC
m.solve() #(disp=False)
m.time = np.multiply(TF, m.time)
print('Final time: ', TF.value[0])
A few things to modify:
Use np.array() without the m.Intermediate() to define arrays.
Use M#b or np.dot(M,b) for the dot product, not M*b.
Use np.array([]) for the 3x1 array definitions instead of np.array([[],[],[]]). The dot product understands the intent.
Use m.fix_final() to fix a final point. You may also consider softening the final constraint with the strategies shown in the Inverted Pendulum problem, especially if the solver isn't able to find a feasible solution.
Use m.GEKKO(remote=False) to solve locally, with no internet connection. Using the public server is also fine, but the local option may be faster and more reliable later when you are solving larger problems.
Here is the modified Python Gekko code:
import math
import matplotlib
matplotlib.use("TkAgg")
import numpy as np
from gekko import GEKKO
#Defining a model
m = GEKKO()
#################################
#Define initial and final conditions and limits
x0 = -1; xdot0 = 0
q10 = 0; q1dot0 = 0
q20 = 0; q2dot0 = 0
xf = 1; xdotf = 0
q1f = 0; q1dotf = 0
q2f = 0; q2dotf = 0
xmin = -1.5; xmax = 1.5
umin = -10; umax = 10
#Defining the time parameter (0, 1)
N = 100
t = np.linspace(0,1,N)
m.time = t
#Final time
TF = m.FV(3,lb=2,ub=25); TF.STATUS = 1
#Parameters
mc = m.Param(value=1) #cart mass
m1 = m.Param(value=.01) #link 1 mass
m2 = m.Param(value=.01) #link 2 mass
L1 = m.Param(value=1) #link 1 length
LC1 = m.Param(value=.5) #link 1 CM pos
L2 = m.Param(value=1) #link 1 length
LC2 = m.Param(value=.5) #link 1 CM pos
I1 = m.Param(value=.01) #link 1 MOI
I2 = m.Param(value=.01) #link 2 MOI
g = m.Const(value=9.81) #gravity
pi = math.pi; pi = m.Const(value=pi)
#MV
u = m.MV(value=0,lb=umin,ub=umax); u.STATUS = 1
#State Variables
x, xdot, q1, q1dot, q2, q2dot = m.Array(m.Var, 6)
x.value = x0; xdot.value = xdot0
q1.value = q10; q1dot.value = q1dot0
q2.value = q20; q2dot.value = q2dot0
x.LOWER = xmin; x.UPPER = xmax
#Intermediates
h1 = m.Intermediate(mc + m1 + m2)
h2 = m.Intermediate(m1*LC1 + m2*L1)
h3 = m.Intermediate(m2*LC2)
h4 = m.Intermediate(m1*LC1**2 + m2*L1**2 + I1)
h5 = m.Intermediate(m2*LC2*L1)
h6 = m.Intermediate(m2*LC2**2 + I2)
h7 = m.Intermediate(m1*LC1*g + m2*L1*g)
h8 = m.Intermediate(m2*LC2*g)
M = np.array([[h1, h2*m.cos(q1), h3*m.cos(q2)],
[h2*m.cos(q1), h4, h5*m.cos(q1-q2)],
[h3*m.cos(q2), h5*m.cos(q1-q2), h6]])
C = np.array([[0, -h2*q1dot*m.sin(q1), -h3*q2dot*m.sin(q2)],
[0, 0, h5*q2dot*m.sin(q1-q2)],
[0, -h5*q1dot*m.sin(q1-q2), 0]])
G = np.array([0, -h7*m.sin(q1), -h8*m.sin(q2)])
U = np.array([u, 0, 0])
DQ = np.array([xdot, q1dot, q2dot])
CDQ = C#DQ
b = np.array([xdot.dt()/TF, q1dot.dt()/TF, q2dot.dt()/TF])
Mb = M#b
#Defining the State Space Model
m.Equations([(Mb[i] == U[i] - CDQ[i] - G[i]) for i in range(3)])
m.Equation(x.dt()/TF == xdot)
m.Equation(q1.dt()/TF == q1dot)
m.Equation(q2.dt()/TF == q2dot)
#Defining final condition
m.fix_final(x,val=xf)
m.fix_final(xdot,val=xdotf)
m.fix_final(q1,val=q1f)
m.fix_final(q1dot,val=q1dotf)
m.fix_final(q2,val=q2f)
m.fix_final(q2dot,val=q2dotf)
#Try to minimize final time
m.Minimize(TF)
m.options.IMODE = 6 #MPC
m.solve() #(disp=False)
m.time = np.multiply(TF, m.time)
print('Final time: ', TF.value[0])
This gives a successful solution with 35 iterations of the IPOPT solver:
EXIT: Optimal Solution Found.
The solution was found.
The final value of the objective function is 364.520472987953
---------------------------------------------------
Solver : IPOPT (v3.12)
Solution time : 1.57619999999588 sec
Objective : 364.520472987953
Successful solution
---------------------------------------------------
Final time: 3.6819305146
Related
This is a continuation of a prior question with a slightly different emphasis.
In summary, the prior solution helped with the implementation of the model inputs. The below model is working and provides a solution for the full contact condition. The framework and basic mechanics are in place for the variable contact constraint. However, no solution is available for when the contact switch variable is applied to the geometric constraints, whether as a Param or FV. (Note: no issues when applied to the dynamic equations).
One thing I've noted is that the m.if2 output is not correct in the [0] position. Below is the output of the switch-related variables:
adiff= [0.1, 0.10502512563, 0.11005025126, 0.11507537688, 0.12010050251,...
bdiff= [1.0, 0.99497487437, 0.98994974874, 0.98492462312, 0.97989949749,...
swtch= [0.1, 0.10449736118, 0.10894421858, 0.11334057221, 0.11768642206,...
c= [0.0, 1.000000005, 1.000000005, 1.000000005, 1.000000005, 1.000000005,...
Based on the logic swtch = adiff*bdiff and m.if2(swtch-thres,0,1), c[0] should be ~1.0. I've played with these parameters and haven't found a way to affect that first cell. I can't say for sure that this initial position is causing issues, but this seems like an erroneous output regardless.
Second, given that the m.if() outputs as approximately 0 and 1, I've attempted to soften the geometric constraint as: m.abs2({constraint}) <= {tol}. Even in the case when a generous tolerance is applied and c is excluded, this fails to produce a solution (whereas the hard constraint will).
Any suggestions for correcting either issue are appreciated.
Lastly, in the prior post, the use of m.integral() for setting the value of c was suggested. I'm unclear if that entails using if2 as well. If you can expand on implementing a switch that enables at t=a and switches off at t=b using an integral, that would be appreciated.
Full code:
###Import Libraries
import math
import matplotlib
matplotlib.use("TkAgg")
import matplotlib.animation as animation
import numpy as np
from gekko import GEKKO
###Defining a model
m = GEKKO(remote=True)
v = 1 #set walking speed (m/s)
L1 = .5 #set thigh length (m)
L2 = .5 #set shank length (m)
M = 75 #set mass (kg)
#################################
###Define secondary parameters
D = L1 + L2 #leg length parameter
pi = math.pi #define pi
g = 9.81 #define gravity
###Define initial and final conditions and limits
xmin = -D; xmax = D
xdotmin = .5*v; xdotmax = 1.5*v
ymin = 0*D; ymax = 5*D
q1min = -pi/2; q1max = pi/2
q2min = -pi/2; q2max = -.01
tfmin = .25; tfmax = 10
#amin = 0; amax = .45 #limits for FVs (future capability)
#bmin = .55; bmax = 1
###Defining the time parameter (0, 1)
N = 200
t = np.linspace(0,1,N)
m.time = t
###Final time Fixed Variable
TF = m.FV(1,lb=tfmin,ub=tfmax); TF.STATUS = 1
end_loc = len(m.time)-1
###Defining initial and final condition vectors
init = np.zeros(len(m.time))
final = np.zeros(len(m.time))
init[1] = 1
final[-1] = 1
init = m.Param(value=init)
final = m.Param(value=final)
###Parameters
M = m.Param(value=M) #cart mass
L1 = m.Param(value=L1) #link 1 length
L2 = m.Param(value=L2) #link 1 length
g = m.Const(value=g) #gravity
###Control Input Manipulated Variable
u = m.MV(0, lb=-70, ub=70); u.STATUS = 1
###Ground Contact Fixed Variables
#as fixed variables (future state)
#a = m.FV(0,lb=amin,ub=amax); a.STATUS = 1 #equates to the unscaled time when contact first occurs
#b = m.FV(1,lb=bmin,ub=bmax); b.STATUS = 1 #equates to the unscaled time when contact last occurs
#as fixed parameter
a = m.Param(value=-.1) #a<0 to drive m.time-a positive
b = m.Param(value=1)
###State Variables
x, y, xdot, ydot, q1, q2 = m.Array(m.Var, 6)
#Define BCs
m.free_initial(x)
m.free_final(x)
m.free_initial(xdot)
m.free_final(xdot)
m.free_initial(y)
m.free_initial(ydot)
#Define Limits
y.LOWER = ymin; y.UPPER = ymax
x.LOWER = xmin; x.UPPER = xmax
xdot.LOWER = xdotmin; xdot.UPPER = xdotmax
q1.LOWER = q1min; q1.UPPER = q1max
q2.LOWER = q2min; q2.UPPER = q2max
###Intermediates
xdot_int = m.Intermediate(final*m.integral(xdot)) #for average velocity constraint
adiff = m.Param(m.time-a.VALUE) #positive if m.time>a
bdiff = m.Param(b.VALUE-m.time) #positive if m.time<b
swtch = m.Intermediate(adiff*bdiff) #positive if m.time > a AND m.time < b
thres = .001
c = m.if2(swtch-thres,0,1) #c=0 if swtch <0, c=1 if swtch >0
###Defining the State Space Model
m.Equation(xdot.dt()/TF == -c*u*(L1*m.sin(q1)
+L2*m.sin(q1+q2))
/(M*L1*L2*m.sin(q2)))
m.Equation(ydot.dt()/TF == c*u*(L1*m.cos(q1)
+L2*m.cos(q1+q2))
/(M*L1*L2*m.sin(q2))-g)
m.Equation(x.dt()/TF == xdot)
m.Equation(y.dt()/TF == ydot)
m.periodic(y) #initial and final y position must be equal
m.periodic(ydot) #initial and final y velocity must be equal
m.periodic(xdot) #initial and final x velocity must be equal
m.Equation(m.abs2(xdot_int*final - v*final) <= .02) #soft constraint for average velocity ~= v
###Geometric constraints
#with no contact switch, this works
m.Equation(x + L1*m.sin(q1) + L2*m.sin(q1+q2) == 0) #x geometric constraint when in contact
m.Equation(y - L1*m.cos(q1) - L2*m.cos(q1+q2) == 0) #y geometric constraint when in contact
#soft constraint for contact switch. Produces no solution, with or without c, abs2 or abs3:
#m.Equation(c*m.abs2(x + L1*m.sin(q1) + L2*m.sin(q1+q2)) <= .01) #x geometric constraint when in contact
#.Equation(c*m.abs2(y - L1*m.cos(q1) - L2*m.cos(q1+q2)) <= .01) #y geometric constraint when in contact
###Objectives
#Maximize stride length
m.Maximize(100*final*x)
m.Minimize(100*init*x)
#Minimize torque
m.Obj(0.01*u**2)
###Solve
m.options.IMODE = 6
m.options.SOLVER = 3
m.solve()
###Scale time vector
m.time = np.multiply(TF, m.time)
###Display Outputs
print("adiff=", adiff.VALUE)
print("bdiff=", bdiff.VALUE)
print("swtch=", swtch.VALUE)
print("c=", c.VALUE)
########################################
####Plotting the results
import matplotlib.pyplot as plt
plt.close('all')
fig1 = plt.figure()
fig2 = plt.figure()
fig3 = plt.figure()
fig4 = plt.figure()
ax1 = fig1.add_subplot()
ax2 = fig2.add_subplot(221)
ax3 = fig2.add_subplot(222)
ax4 = fig2.add_subplot(223)
ax5 = fig2.add_subplot(224)
ax6 = fig3.add_subplot()
ax7 = fig4.add_subplot(121)
ax8 = fig4.add_subplot(122)
ax1.plot(m.time,u.value,'m',lw=2)
ax1.legend([r'$u$'],loc=1)
ax1.set_title('Control Input')
ax1.set_ylabel('Torque (N-m)')
ax1.set_xlabel('Time (s)')
ax1.set_xlim(m.time[0],m.time[-1])
ax1.grid(True)
ax2.plot(m.time,x.value,'r',lw=2)
ax2.set_ylabel('X Position (m)')
ax2.set_xlabel('Time (s)')
ax2.legend([r'$x$'],loc='upper left')
ax2.set_xlim(m.time[0],m.time[-1])
ax2.grid(True)
ax2.set_title('Mass X-Position')
ax3.plot(m.time,xdot.value,'g',lw=2)
ax3.set_ylabel('X Velocity (m/s)')
ax3.set_xlabel('Time (s)')
ax3.legend([r'$xdot$'],loc='upper left')
ax3.set_xlim(m.time[0],m.time[-1])
ax3.grid(True)
ax3.set_title('Mass X-Velocity')
ax4.plot(m.time,y.value,'r',lw=2)
ax4.set_ylabel('Y Position (m)')
ax4.set_xlabel('Time (s)')
ax4.legend([r'$y$'],loc='upper left')
ax4.set_xlim(m.time[0],m.time[-1])
ax4.grid(True)
ax4.set_title('Mass Y-Position')
ax5.plot(m.time,ydot.value,'g',lw=2)
ax5.set_ylabel('Y Velocity (m/s)')
ax5.set_xlabel('Time (s)')
ax5.legend([r'$ydot$'],loc='upper left')
ax5.set_xlim(m.time[0],m.time[-1])
ax5.grid(True)
ax5.set_title('Mass Y-Velocity')
ax6.plot(x.value, y.value,'g',lw=2)
ax6.set_ylabel('Y-Position (m)')
ax6.set_xlabel('X-Position (m)')
ax6.legend([r'$mass coordinate$'],loc='upper left')
ax6.set_xlim(x.value[0],x.value[-1])
ax6.set_ylim(0,1.1)
ax6.grid(True)
ax6.set_title('Mass Position')
ax7.plot(m.time,q1.value,'r',lw=2)
ax7.set_ylabel('q1 Position (rad)')
ax7.set_xlabel('Time (s)')
ax7.legend([r'$q1$'],loc='upper left')
ax7.set_xlim(m.time[0],m.time[-1])
ax7.grid(True)
ax7.set_title('Hip Joint Angle')
ax8.plot(m.time,q2.value,'r',lw=2)
ax8.set_ylabel('q2 Position (rad)')
ax8.set_xlabel('Time (s)')
ax8.legend([r'$q2$'],loc='upper left')
ax8.set_xlim(m.time[0],m.time[-1])
ax8.grid(True)
ax8.set_title('Knee Joint Angle')
plt.show()
The m.if2() function is a Mathematical Program with Complementarity Constraints (MPCC). It does not use a binary variable like the m.if3() function and therefore can be solved with any NLP solver, such as IPOPT. The disadvantage is that it has a saddle point at the switching condition and often gets stuck at the local solution. One way to overcome this issue is to use m.if3() with the IPOPT solver for initialization and then switch to the APOPT solver to generate an exact MINLP solution.
m.options.SOLVER=3 # IPOPT
m.solve()
m.options.SOLVER=1 # APOPT
m.options.TIME_SHIFT = 0 # don't update initial conditions
m.solve()
Additional information on MPCCs and binary conditional statements is in the Design Optimization course section on Logical Conditions.
I'm having trouble passing my equations of motion to the solver on my control optimization problem.
Just a little explanation on what I'm attempting to do here, because I think there are two problem areas:
First, I'm defining a contact switch c that is used to turn on and off portions of the dynamic equations based on the value a, which is a FV between 0 and .45. I have a loop which sets the value of c[i] based on the value of the time parameter relative to a.
c = [None]*N
for i in range(N):
difference = m.Intermediate(.5-m.time[i])
abs = m.if3(difference, -difference, difference)
c[i] = m.Intermediate(m.if3(abs-(.5-a), 1, 0))
It should resemble a vector of length N:
c= [0, 0, 0, 1, 1, ...., 1, 1, 0, 0, 0]
It's not clear if this was implemented properly, but it's not throwing me errors at this point. (Note: I'm aware that this can be easily implemented as a mixed-integer variable, but I really want to use IPOPT, so I'm using the m.if3() method to create the binary switch.)
Second, I'm getting an error when passing the equations of motion. This exists whether the c is included, so, at least for right now, I know that is not the issue.
m.Equations(xdot.dt()/TF == c*u*(L1*m.sin(q1)-L2*m.sin(q1+q2))/(M*L1*L2*m.sin(2*q1+q2)))
m.Equations(ydot.dt()/TF == -c*u*(L1*m.cos(q1)+L2*m.cos(q1+q2))/(M*L1*L2*m.sin(2*q1+q2))-g/m)
m.Equation(x.dt()/TF == xdot)
m.Equation(y.dt()/TF == ydot)
m.Equation(y*init == y*final) #initial and final y position must be equal
TypeError: 'int' object is not subscriptable
I've attempted to set up an intermediate loop to handle the RH of the equation to no avail:
RH = [None]*N
RH = m.Intermediate([c[i]*u[i]*(L1*m.sin(q1[i])-2*m.sin(q1[i]+q2[i]))/(M*L1*L2*m.sin(2*q1[i]+q2[i])) for i in range(N)])
m.Equations(xdot.dt()/TF == RH)
Below is the full code. Note: there are probably other issues both in my code and problem definition, but I'm just looking to find a way to successfully pass these equations of motion. Much appreciated!
Full code:
import math
import numpy as np
from gekko import GEKKO
#Defining a model
m = GEKKO(remote=True)
v = 1 #set walking speed (m/s)
L1 = .5 #set thigh length (m)
L2 = .5 #set shank length (m)
M = 75 #set mass (kg)
#################################
#Define secondary parameters
D = L1 + L2 #leg length parameter
pi = math.pi #define pi
g = 9.81 #define gravity
#Define initial and final conditions and limits
x0 = -v/2; xf = v/2
xdot0 = v; xdotf = v
ydot0 = 0; ydotf = 0
ymin = .5*D; ymax = 1.5*D
q1min = -pi/2; q1max = pi/2
q2min = -pi/2; q2max = 0
tfmin = D/(2*v); tfmax = 3*D/(2*v)
#Defining the time parameter (0, 1)
N = 100
t = np.linspace(0,1,N)
m.time = t
#Final time Fixed Variable
TF = m.FV(1,lb=tfmin,ub=tfmax); TF.STATUS = 1
end_loc = len(m.time)-1
amin = 0; amax = .45
#Defining initial and final condition vectors
init = np.zeros(len(m.time))
final = np.zeros(len(m.time))
init[1] = 1
final[-1] = 1
init = m.Param(value=init)
final = m.Param(value=final)
#Parameters
M = m.Param(value=M) #cart mass
L1 = m.Param(value=L1) #link 1 length
L2 = m.Param(value=L2) #link 1 length
g = m.Const(value=g) #gravity
#Control Input Manipulated Variable
u = m.MV(0); u.STATUS = 1
#Ground Contact Fixed Variable
a = m.FV(0,lb=amin,ub=amax) #equates to the unscaled time when contact first occurs
#State Variables
x, y, xdot, ydot, q1, q2 = m.Array(m.Var, 6)
x.value = x0;
xdot.value = xdot0; ydot.value = ydot0
y.LOWER = ymin; y.UPPER = ymax
q1.LOWER = q1min; q1.UPPER = q1max
q2.LOWER = q2min; q2.UPPER = q2max
#Intermediates
c = [None]*N
for i in range(N):
difference = m.Intermediate(.5-m.time[i])
abs = m.if3(difference, -difference, difference)
c[i] = m.Intermediate(m.if3(abs-(.5-a), 1, 0))
#Defining the State Space Model
m.Equations(xdot.dt()/TF == c*u*(L1*m.sin(q1)-L2*m.sin(q1+q2))/(M*L1*L2*m.sin(2*q1+q2))) ####This produces the error
m.Equations(ydot.dt()/TF == -c*u*(L1*m.cos(q1)+L2*m.cos(q1+q2))/(M*L1*L2*m.sin(2*q1+q2))-g/m)
m.Equation(x.dt()/TF == xdot)
m.Equation(y.dt()/TF == ydot)
m.Equation(y*init == y*final) #initial and final y position must be equal
#Defining final condition
m.fix_final(x,val=xf)
m.fix_final(xdot,val=xdotf)
m.fix_final(xdot,val=ydotf)
#Try to minimize final time and torque
m.Obj(TF)
m.Obj(0.001*u**2)
m.options.IMODE = 6 #MPC
m.options.SOLVER = 3
m.solve()
m.time = np.multiply(TF, m.time)
Nice application. Here are a few corrections and ideas:
Use a switch condition that uses a NumPy array. There is no need to define the individual points in the horizon with c[i].
#Intermediates
#c = [None]*N
#for i in range(N):
# difference = m.Intermediate(.5-m.time[i])
# abs = m.if3(difference, -difference, difference)
# c[i] = m.Intermediate(m.if3(abs-(.5-a), 1, 0))
diff = 0.5 - m.time
adiff = m.Param(np.abs(diff))
swtch = m.Intermediate(adiff-(0.5-a))
c = m.if3(swtch,1,0)
You may be able to use the m.integral() function to set the value of c to 1 and keep it there when contact is made.
Use the m.periodic(y) function to set the initial value of y equal to the final value of y.
#m.Equation(y*init == y*final) #initial and final y position must be equal
m.periodic(y)
Try using soft constraints instead of hard constraints if there is a problem with finding a feasible solution.
#Defining final condition
#m.fix_final(x,val=xf)
#m.fix_final(xdot,val=xdotf)
#m.fix_final(ydot,val=ydotf)
m.Minimize(final*(x-xf)**2)
m.Minimize(final*(xdot-xdotf)**2)
m.Minimize(final*(ydot-ydotf)**2)
The m.if3() function requires the APOPT solver. Try m.if2() for the continuous version that uses MPCCs instead of binary variables to define the switch. The integral function may be alternative way to avoid a binary variable.
Here is the final code that attempts a solution, but the solver can't yet find a solution. I hope this helps you get a little further on your optimization problem. You may need to use a shooting (sequential method) to find an initial feasible solution.
import math
import numpy as np
from gekko import GEKKO
#Defining a model
m = GEKKO(remote=True)
v = 1 #set walking speed (m/s)
L1 = .5 #set thigh length (m)
L2 = .5 #set shank length (m)
M = 75 #set mass (kg)
#################################
#Define secondary parameters
D = L1 + L2 #leg length parameter
pi = math.pi #define pi
g = 9.81 #define gravity
#Define initial and final conditions and limits
x0 = -v/2; xf = v/2
xdot0 = v; xdotf = v
ydot0 = 0; ydotf = 0
ymin = .5*D; ymax = 1.5*D
q1min = -pi/2; q1max = pi/2
q2min = -pi/2; q2max = 0
tfmin = D/(2*v); tfmax = 3*D/(2*v)
#Defining the time parameter (0, 1)
N = 100
t = np.linspace(0,1,N)
m.time = t
#Final time Fixed Variable
TF = m.FV(1,lb=tfmin,ub=tfmax); TF.STATUS = 1
end_loc = len(m.time)-1
amin = 0; amax = .45
#Defining initial and final condition vectors
init = np.zeros(len(m.time))
final = np.zeros(len(m.time))
init[1] = 1
final[-1] = 1
init = m.Param(value=init)
final = m.Param(value=final)
#Parameters
M = m.Param(value=M) #cart mass
L1 = m.Param(value=L1) #link 1 length
L2 = m.Param(value=L2) #link 1 length
g = m.Const(value=g) #gravity
#Control Input Manipulated Variable
u = m.MV(0); u.STATUS = 1
#Ground Contact Fixed Variable
a = m.FV(0,lb=amin,ub=amax) #equates to the unscaled time when contact first occurs
#State Variables
x, y, xdot, ydot, q1, q2 = m.Array(m.Var, 6)
x.value = x0;
xdot.value = xdot0; ydot.value = ydot0
y.LOWER = ymin; y.UPPER = ymax
q1.LOWER = q1min; q1.UPPER = q1max
q2.LOWER = q2min; q2.UPPER = q2max
#Intermediates
#c = [None]*N
#for i in range(N):
# difference = m.Intermediate(.5-m.time[i])
# abs = m.if3(difference, -difference, difference)
# c[i] = m.Intermediate(m.if3(abs-(.5-a), 1, 0))
diff = 0.5 - m.time
adiff = m.Param(np.abs(diff))
swtch = m.Intermediate(adiff-(0.5-a))
c = m.if3(swtch,1,0)
#Defining the State Space Model
m.Equation(xdot.dt()/TF == c*u*(L1*m.sin(q1)
-L2*m.sin(q1+q2))
/(M*L1*L2*m.sin(2*q1+q2)))
m.Equation(ydot.dt()/TF == -c*u*(L1*m.cos(q1)
+L2*m.cos(q1+q2))
/(M*L1*L2*m.sin(2*q1+q2))-g/M)
m.Equation(x.dt()/TF == xdot)
m.Equation(y.dt()/TF == ydot)
#m.Equation(y*init == y*final) #initial and final y position must be equal
m.periodic(y)
#Defining final condition
#m.fix_final(x,val=xf)
#m.fix_final(xdot,val=xdotf)
#m.fix_final(ydot,val=ydotf)
m.Minimize(final*(x-xf)**2)
m.Minimize(final*(xdot-xdotf)**2)
m.Minimize(final*(ydot-ydotf)**2)
#Try to minimize final time and torque
m.Minimize(TF)
m.Minimize(0.001*u**2)
m.options.IMODE = 6 #MPC
m.options.SOLVER = 1
m.solve()
m.time = np.multiply(TF, m.time)
I am trying to solve a low thrust optimal control problem for Earth orbits, i.e. going from one orbit to another. The formulation of the problem includes six states (r_1, r_2, r_3, v_1, v_2, v_3) and 3 controls (u_1, u_2, u_3) with a simplified point model of gravity. When I specify the full initial state and half of the final state, the solver converges and yields a good solution. When I try the full final state, the problem is over constrained.
My thought on how to remedy this is to allow the trajectory to depart the initial orbit at any point along the orbital curve and join the final orbit an any point along the final orbital curve, giving it more degrees of freedom. Is there a way to constrain the initial and final values of all 6 states to a cspline curve? This is what I have tried so far:
m = GEKKO(remote=True)
m.time = np.linspace(0, tof, t_steps)
theta_i = m.FV(lb = 0, ub = 360)
theta_f = m.FV(lb = 0, ub = 360)
rx_i = m.Param()
ry_i = m.Param()
rz_i = m.Param()
vx_i = m.Param()
vy_i = m.Param()
vz_i = m.Param()
rx_f = m.Param()
ry_f = m.Param()
rz_f = m.Param()
vx_f = m.Param()
vy_f = m.Param()
vz_f = m.Param()
m.cspline(theta_i, rx_i, initial.theta, initial.r[:, 0])
m.cspline(theta_i, ry_i, initial.theta, initial.r[:, 1])
m.cspline(theta_i, rz_i, initial.theta, initial.r[:, 2])
m.cspline(theta_i, vx_i, initial.theta, initial.v[:, 0])
m.cspline(theta_i, vy_i, initial.theta, initial.v[:, 1])
m.cspline(theta_i, vz_i, initial.theta, initial.v[:, 2])
m.cspline(theta_f, rx_f, final.theta, final.r[:, 0])
m.cspline(theta_f, ry_f, final.theta, final.r[:, 1])
m.cspline(theta_f, rz_f, final.theta, final.r[:, 2])
m.cspline(theta_f, vx_f, final.theta, final.v[:, 0])
m.cspline(theta_f, vy_f, final.theta, final.v[:, 1])
m.cspline(theta_f, vz_f, final.theta, final.v[:, 2])
r1 = m.Var(rx_i)
r2 = m.Var(ry_i)
r3 = m.Var(rz_i)
r1dot = m.Var(vx_i)
r2dot = m.Var(vy_i)
r3dot = m.Var(vz_i)
u1 = m.Var(lb = -max_u, ub = max_u)
u2 = m.Var(lb = -max_u, ub = max_u)
u3 = m.Var(lb = -max_u, ub = max_u)
m.Equation(r1.dt() == r1dot)
m.Equation(r2.dt() == r2dot)
m.Equation(r3.dt() == r3dot)
r = m.Intermediate(m.sqrt(r1**2 + r2**2 + r3**2))
v = m.Intermediate(m.sqrt(r1dot**2 + r2dot**2 + r3dot**3))
m.Equation(-mu*r1/r**3 == r1dot.dt() + u1)
m.Equation(-mu*r2/r**3 == r2dot.dt() + u2)
m.Equation(-mu*r3/r**3 == r3dot.dt() + u3)
m.fix_final(r1, rx_f)
m.fix_final(r2, ry_f)
m.fix_final(r3, rz_f)
m.fix_final(r1dot, vx_f)
m.fix_final(r2dot, vy_f)
m.fix_final(r3dot, vz_f)
m.Minimize(m.integral(u1**2 + u2**2 + u3**2))
m.options.IMODE = 6
m.options.solver = 3
#m.options.ATOL = 1e-3
m.options.MAX_ITER = 300
m.solve(disp=True) # solve
With cspline, I am trying to allow GEKKO to pick a fixed value of the true anomaly (the parameter that determines how far along the orbit you are) from data that I have generated about the states at sampled true anomalies that would have associated position and velocity states. Any help would be much appreciated!
SOLUTION:
I implemented the end constraints as "soft constraints", i.e. quantities to be minimized, instead of hard constraints which would use fix_final. Specific implementation is as follows.
final = np.zeros(len(m.time))
final[-1] = 1
final = m.Param(value=final)
m.Obj(final*(r1-final_o.r[0, 0])**2)
m.Obj(final*(r2-final_o.r[0, 1])**2)
m.Obj(final*(r3-final_o.r[0, 2])**2)
m.Obj(final*(r1dot-final_o.v[0, 0])**2)
m.Obj(final*(r2dot-final_o.v[0, 1])**2)
m.Obj(final*(r3dot-final_o.v[0, 2])**2)
final makes the solver only consider the last item (the end boundary) when multiplied.
It is generally much harder for an optimizer to exactly reach a fixed endpoint, especially when it depends on a complex sequence of moves. This often leads to infeasible solutions. An alternative is to create a soft constraint (objective minimization) to penalize deviations from the final trajectory. Here is an example that is similar:
import matplotlib.animation as animation
import numpy as np
from gekko import GEKKO
#Defining a model
m = GEKKO()
#################################
#Weight of item
m2 = 1
#################################
#Defining the time, we will go beyond the 6.2s
#to check if the objective was achieved
m.time = np.linspace(0,8,100)
end_loc = int(100.0*6.2/8.0)
#Parameters
m1a = m.Param(value=10)
m2a = m.Param(value=m2)
final = np.zeros(len(m.time))
for i in range(len(m.time)):
if m.time[i] < 6.2:
final[i] = 0
else:
final[i] = 1
final = m.Param(value=final)
#MV
ua = m.Var(value=0)
#State Variables
theta_a = m.Var(value=0)
qa = m.Var(value=0)
ya = m.Var(value=-1)
va = m.Var(value=0)
#Intermediates
epsilon = m.Intermediate(m2a/(m1a+m2a))
#Defining the State Space Model
m.Equation(ya.dt() == va)
m.Equation(va.dt() == -epsilon*theta_a + ua)
m.Equation(theta_a.dt() == qa)
m.Equation(qa.dt() == theta_a -ua)
#Define the Objectives
#Make all the state variables be zero at time >= 6.2
m.Obj(final*ya**2)
m.Obj(final*va**2)
m.Obj(final*theta_a**2)
m.Obj(final*qa**2)
m.fix(ya,pos=end_loc,val=0.0)
m.fix(va,pos=end_loc,val=0.0)
m.fix(theta_a,pos=end_loc,val=0.0)
m.fix(qa,pos=end_loc,val=0.0)
#Try to minimize change of MV over all horizon
m.Obj(0.001*ua**2)
m.options.IMODE = 6 #MPC
m.solve() #(disp=False)
This example uses a combination of soft and hard constraints to help it find the optimal solution.
I am making a numerical problem to show as an example and am trying to find an optimal control using gekko for the following problem:
minimize the integral of a*x(t) from 0 to T, where T is the first time x(t) is 0, i.e., it is a random time. The constraints are such that x(t) follows some dynamic f(x(t),u(t)), x(t) >= 0, and u(t) is between 0 and 1.
I followed the tutorials on GEKKO website and youtube for fixed final time, but I could not find any information on a random final time. The following is the current code I have, but how would I be able to move from a fixed final time to a random final time? Any help would be appreciated! Thanks!
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import odeint
from gekko import GEKKO
# Initial conditions
xhh0 = 3; xhi0 = 0;
xvh0 = 30; xvi0 = 0;
hin0 = 0; vin0 = 0;
tt0 = 0
# Parameters
a1 = 0.1; a2 = 0.1;
b1 = 0.01; b2 = 0.5;
delta1 = 0.1; delta2 = 0.5;
rho1 = 0.3; rho2 = 0.01
mu = 1
# Gekko
m = GEKKO()
# Control variable
u = m.MV(0.5, lb = 0, ub = 1)
# Final time <------------------------ currently a fixed final time
T = 10
# Initialize
xhh, xhi, xvh, xvi, Ah, Av = m.Array(m.Var, 6)
xhh.value = xhh0; xhi.value = xhi0;
xvh.value = xvh0; xvi.value = xvi0;
Ah.value = hin0; Av.value = vin0;
# System dynamics
m.Equations([xhh.dt() == -a1*xhh - mu*u - b1*xhi*xhh,\
xhi.dt() == a1*xhh + b1*xhi*xhh - delta1*xhi - rho1*xhi,\
xvh.dt() == -a2*xvh - mu*(1-u) - b2*xvi*xvh,\
xvi.dt() == a2*xvh + b2*xvi*xvh - delta2*xvi - rho2*xvi,\
Ah.dt() == a1*xhh,\
Av.dt() == a2*xvh])
# Time space
t = np.linspace(0, T, 101)
m.time = t
# initialize with simulation
m.options.IMODE = 7
m.options.NODES = 3
m.solve(disp = False)
# optimization
m.options.IMODE = 6
xhh.LOWER = 0; xhi.LOWER = 0; xvh.LOWER = 0; xvi.LOWER = 0
u.STATUS = 1
m.options.SOLVER = 3
xhh.value = xhh.value.value
xhi.value = xhi.value.value
xvh.value = xvh.value.value
xvi.value = xvi.value.value
Ah.value = Ah.value.value
Av.value = Av.value.value
# Objective function
m.Minimize(Ah + Av)
m.solve()
The final time is adjustable with T = m.FV() and T.STATUS=1 when each differential is divided by T. This scales the problem to any arbitrary final time when t = np.linspace(0,1).
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import odeint
from gekko import GEKKO
# Initial conditions
xhh0 = 3; xhi0 = 0;
xvh0 = 30; xvi0 = 0;
hin0 = 0; vin0 = 0;
tt0 = 0
# Parameters
a1 = 0.1; a2 = 0.1;
b1 = 0.01; b2 = 0.5;
delta1 = 0.1; delta2 = 0.5;
rho1 = 0.3; rho2 = 0.01; mu = 1
# Gekko
m = GEKKO()
# Control variable
u = m.MV(0.5, lb = 0, ub = 1)
# Final time
T = m.FV(10,lb=1e-2,ub=100); T.STATUS = 1
# Initialize
xhh, xhi, xvh, xvi, Ah, Av = m.Array(m.Var, 6)
xhh.value = xhh0; xhi.value = xhi0;
xvh.value = xvh0; xvi.value = xvi0;
Ah.value = hin0; Av.value = vin0;
xhh.LOWER = 0; xhi.LOWER = 0; xvh.LOWER = 0; xvi.LOWER = 0
u.STATUS = 1
# System dynamics
m.Equations([xhh.dt()/T == -a1*xhh - mu*u - b1*xhi*xhh,\
xhi.dt()/T == a1*xhh + b1*xhi*xhh - delta1*xhi - rho1*xhi,\
xvh.dt()/T == -a2*xvh - mu*(1-u) - b2*xvi*xvh,\
xvi.dt()/T == a2*xvh + b2*xvi*xvh - delta2*xvi - rho2*xvi,\
Ah.dt()/T == a1*xhh,\
Av.dt()/T == a2*xvh])
# Time space
t = np.linspace(0, 1, 101)
m.time = t
# optimization
m.options.IMODE = 6
m.options.SOLVER = 3
# Objective function
m.Minimize(Ah + Av)
m.solve()
print('Final time: ', T.value[0])
There may be a missing constraint or some other information because the optimal final time always goes to the lower bound. The Jennings problem is a related example with variable final time.
I am getting an error when I run this code for disc waves. The code is attached.
The Error is in line 137 and 292. Please help in resolving issue.
function waves
% WAVES Wave equation in one and two space dimensions.
% The two-dimensional domains include a pi-by-pi square, a unit disc,
% a three-quarter circular sector and the L-shaped union of three squares.
% The eigenfunctions of the square are sin(m*x)*sin(n*y). With polar
% coordinates, the eigenfunctions of the disc and the sector involve Bessel
% functions. The eigenfunctions of the L-shaped domain also involve
% Bessel functions and are computed by the MATLAB function membranetx.m.
% 2-D eigenvalues and eigenfunctions
m = 11; % Determines number of grid points
speed = 1;
bvals = [1; 0; 0; 0; 0];
t = 0;
while bvals(5) == 0
% Initialize figure
shg
clf reset
set(gcf,'doublebuffer','on','menubar','none','tag','', ...
'numbertitle','off','name','Waves','colormap',hot(64));
for k= 1:5
b(k) = uicontrol('style','toggle','value',bvals(k), ...
'units','normal','position',[.15*k .01 .14 .05]);
end
set(b(1),'style','pop','string', ...
{'1-d','square','disc','sector'})
set(b(2),'string','modes/wave')
set(b(3),'string','slower')
set(b(4),'string','faster')
set(b(5),'string','close')
if bvals(3)==1
speed = speed/sqrt(2);
set(b(3),'value',0);
end
if bvals(4)==1
speed = speed*sqrt(2);
set(b(4),'value',0);
end
bvals = cell2mat(get(b,'value'));
region = bvals(1);
modes = bvals(2)==0;
if region == 1
% 1-D
x = (0:4*m)/(4*m)*pi;
orange = [1 1/3 0];
gray = get(gcf,'color');
if modes
% 1-D modes
for k = 1:4
subplot(2,2,k)
h(k) = plot(x,zeros(size(x)));
axis([0 pi -3/2 3/2])
set(h(k),'color',orange,'linewidth',3)
set(gca,'color',gray','xtick',[],'ytick',[])
end
delta = 0.005*speed;
bvs = bvals;
while all(bvs == bvals)
t = t + delta;
for k = 1:4
u = sin(k*t)*sin(k*x);
set(h(k),'ydata',u)
end
drawnow
bvs = cell2mat(get(b,'value'));
end
else
% 1-D wave
h = plot(x,zeros(size(x)));
axis([0 pi -9/4 9/4])
set(h,'color',orange,'linewidth',3)
set(gca,'color',gray','xtick',[],'ytick',[])
delta = 0.005*speed;
a = 1./(1:4);
bvs = bvals;
while all(bvs == bvals)
t = t + delta;
u = zeros(size(x));
for k = 1:4
u = u + a(k)*sin(k*t)*sin(k*x);
end
set(h,'ydata',u)
drawnow
bvs = cell2mat(get(b,'value'));
end
end
elseif region <= 5
switch region
case 2
% Square
x = (0:2*m)/(2*m)*pi;
y = x';
lambda = zeros(4,1);
V = cell(4,1);
k = 0;
for i = 1:2
for j = 1:2
k = k+1;
lambda(k) = i^2 + j^2;
V{k} = sin(i*y)*sin(j*x);
end
end
ax = [0 pi 0 pi -1.75 1.75];
case 3
% Disc, mu = zeros of J_0(r) and J_1(r)
mu = [bjzeros(0,2) bjzeros(1,2)];
[r,theta] = meshgrid((0:m)/m,(-m:m)/m*pi);
x = r.*cos(theta);
y = r.*sin(theta);
V = cell(4,1);
k = 0;
for j = 0:1
for i = 1:2
k = k+1;
if j == 0
V{k} = besselj(0,mu(k)*r);
else
V{k} = besselj(j,mu(k)*r).*sin(j*theta);
end
V{k} = V{k}/max(max(abs(V{k})));
end
end
lambda = mu.^2;
ax = [-1 1 -1 1 -1.75 1.75];
case 4
% Circular sector , mu = zeros of J_(2/3)(r) and J_(4/3)(r)
mu = [bjzeros(2/3,2) bjzeros(4/3,2)];
[r,theta] = meshgrid((0:m)/m,(3/4)*(0:2*m)/m*pi);
x = r.*cos(theta+pi);
y = r.*sin(theta+pi);
V = cell(4,1);
k = 0;
for j = 1:2
for i = 1:2
k = k+1;
alpha = 2*j/3;
V{k} = besselj(alpha,mu(k)*r).*sin(alpha*theta);
V{k} = V{k}/max(max(abs(V{k})));
end
end
lambda = mu.^2;
ax = [-1 1 -1 1 -1.75 1.75];
case 5\
% L-membrane
x = (-m:m)/m;
y = x';
lambda = zeros(4,1);
V = cell(4,1);
for k = 1:4
[L lambda(k)] = membranetx(k,m,9,9);
L(m+2:2*m+1,m+2:2*m+1) = NaN;
V{k} = rot90(L,-1);
end
ax = [-1 1 -1 1 -1.75 1.75];
end
if modes
% 2-D modes
p = [.02 .52 .02 .52];
q = [.52 .52 .02 .02];
for k = 1:4
axes('position',[p(k) q(k) .46 .46]);
h(k) = surf(x,y,zeros(size(V{k})));
axis(ax)
axis off
view(225,30);
caxis([-1.5 1]);
end
delta = .08*speed;
mu = sqrt(lambda(:));
bvs = bvals;
while all(bvs == bvals)
t = t + delta;
for k = 1:4
U = 1.5*sin(mu(k)*t)*V{k};
set(h(k),'zdata',U)
set(h(k),'cdata',U)
end
drawnow
bvs = cell2mat(get(b,'value'));
end
else
% 2-D wave
h = surf(x,y,zeros(size(V{1})));
axis(ax);
axis off
view(225,30);
caxis([-1.5 1]);
delta = .02*speed;
mu = sqrt(lambda(:));
a = 1.25./(1:4);
bvs = bvals;
while all(bvs == bvals)
t = t + delta;
U = zeros(size(V{1}));
for k = 1:4
U = U + a(k)*sin(mu(k)*t)*V{k};
end
set(h,'zdata',U)
set(h,'cdata',U)
drawnow
bvs = cell2mat(get(b,'value'));
end
end
elseif region == 6
figure
bizcard
set(b(1),'value',1)
end
% Retain uicontrol values
bvals = cell2mat(get(b,'value'));
end
close
% -------------------------------
function z = bjzeros(n,k)
% BJZEROS Zeros of the Bessel function.
% z = bjzeros(n,k) is the first k zeros of besselj(n,x)
% delta must be chosen so that the linear search can take
% steps as large as possible without skipping any zeros.
% delta is approx bjzero(0,2)-bjzero(0,1)
delta = .99*pi;
Jsubn = inline('besselj(n,x)''x','n');
a = n+1;
fa = besselj(n,a);
z = zeros(1,k);
j = 0;
while j < k
b = a + delta;
fb = besselj(n,b);
if sign(fb) ~= sign(fa)
j = j+1;
z(j) = fzerotx(Jsubn,[a b],n);
end
a = b;
fa = fb;
end