I'm trying to solve a simple minimum time optimal control problem using double integrator dynamics of the form,
dx1/dt = x2
dx2/dt = u
with the GEKKO optimization framework as follows:
from gekko import GEKKO
import numpy as np
import matplotlib.pyplot as plt
model = GEKKO(remote=False)
x1_initial = 0.0
x1_final = 10.0
x2_initial = 0.0
x2_final = 0.0
t_initial = 0.0
t_final = 25.0
num_timesteps = 1000
dt = (t_final - t_initial) / num_timesteps
x = model.Array(model.Var, (2, num_timesteps + 1))
u = model.Array(model.Var, num_timesteps + 1)
tf = model.Var()
for k in range(num_timesteps + 1):
u[k].lower = -0.4
u[k].upper = 0.4
u[k].value = 0.0
for k in range(num_timesteps + 1):
x[0, k].value = 5.0
x[1, k].value = 0.0
tf.lower = t_initial
tf.upper = t_final
tf.value = t_final
dt = (tf - t_initial) / num_timesteps
def f(x, u, k):
return np.array([x[1,k], u[k]])
for k in range(num_timesteps):
model.Equations([x[:, k + 1] == x[:, k] + (dt/2.0)*(f(x, u, k + 1) + f(x, u, k))])
# model.Equation(x[0, k + 1] == x[0, k] + (dt/2.0)*(x[1, k + 1] + x[1, k]))
# model.Equation(x[1, k + 1] == x[1, k] + (dt/2.0)*(u[k + 1] + u[k]))
model.Equation(x[0, 0] == x1_initial)
model.Equation(x[0, num_timesteps] == x1_final)
model.Equation(x[1, 0] == x2_initial)
model.Equation(x[1, num_timesteps] == x2_final)
model.Minimize(tf)
model.options.solver = 3
model.solve()
# Plotting results
t = np.linspace(t_initial, tf.value, num_timesteps + 1)
u_optimal = []
for k in range(num_timesteps + 1):
u_optimal.append(u[k].value)
x1_optimal = []
for k in range(num_timesteps + 1):
x1_optimal.append(x[0, k].value)
x2_optimal = []
for k in range(num_timesteps + 1):
x2_optimal.append(x[1, k].value)
plt.figure()
plt.plot(t, u_optimal)
plt.xlabel('time (s)')
plt.ylabel('u(t)')
plt.grid()
plt.figure()
plt.plot(t, x1_optimal)
plt.xlabel('time (s)')
plt.ylabel('x1(t)')
plt.grid()
plt.figure()
plt.plot(t, x2_optimal)
plt.xlabel('time (s)')
plt.ylabel('x2(t)')
plt.grid()
plt.show()
What I'm trying to do is to form a system of equality constraints using trapezoidal integration and then solve this system for the optimal control inputs using GEKKO. However, using the function definition,
def f(x, u, k):
return np.array([x[1,k], u[k]])
in conjunction with the system of equality constraints,
for k in range(num_timesteps):
model.Equations([x[:, k + 1] == x[:, k] + (dt/2.0)*(f(x, u, k + 1) + f(x, u, k))])
gives me the following error,
Exception: #error: Equation Definition
Equation without an equality (=) or inequality (>,<)
false
STOPPING...
I've added two commented lines of code in the above code snippet that will allow the program to run correctly, but I'm hoping to avoid having to separate each equation out, since I'd like to extend this to problems that deal with more complicated system dynamics, and to also use more sophisticated collocation methods instead of the trapezoidal approach.
I know that GEKKO has some nice features for dynamic optimization, but I'm looking to try and implement various direct collocation methods myself to understand the theory a bit better.
There are some examples of orthogonal collocation on finite elements in the Machine Learning and Dynamic Optimization course.
from __future__ import division
import numpy as np
from scipy.optimize import fsolve
from scipy.integrate import odeint
import matplotlib.pyplot as plt
# final time
tf = 1.0
# solve with ODEINT (for comparison)
def model(x,t):
u = 4.0
return (-x**2 + u)/5.0
t = np.linspace(0,tf,20)
y0 = 0
y = odeint(model,y0,t)
plt.figure(1)
plt.plot(t,y,'r-',label='ODEINT')
# ----------------------------------------------------
# Approach #1 - Write the model equations in Python
# ----------------------------------------------------
# define collocation matrices
def colloc(n):
if (n==2):
NC = np.array([[1.0]])
if (n==3):
NC = np.array([[0.75,-0.25], \
[1.00, 0.00]])
if (n==4):
NC = np.array([[0.436,-0.281, 0.121], \
[0.614, 0.064, 0.0461], \
[0.603, 0.230, 0.167]])
if (n==5):
NC = np.array([[0.278, -0.202, 0.169, -0.071], \
[0.398, 0.069, 0.064, -0.031], \
[0.387, 0.234, 0.278, -0.071], \
[0.389, 0.222, 0.389, 0.000]])
if (n==6):
NC = np.array([[0.191, -0.147, 0.139, -0.113, 0.047],
[0.276, 0.059, 0.051, -0.050, 0.022],
[0.267, 0.193, 0.252, -0.114, 0.045],
[0.269, 0.178, 0.384, 0.032, 0.019],
[0.269, 0.181, 0.374, 0.110, 0.067]])
return NC
# define collocation points from Lobatto quadrature
def tc(n):
if (n==2):
time = np.array([0.0,1.0])
if (n==3):
time = np.array([0.0,0.5,1.0])
if (n==4):
time = np.array([0.0, \
0.5-np.sqrt(5)/10.0, \
0.5+np.sqrt(5)/10.0, \
1.0])
if (n==5):
time = np.array([0.0,0.5-np.sqrt(21)/14.0, \
0.5,0.5+np.sqrt(21)/14.0, 1])
if (n==6):
time = np.array([0.0, \
0.5-np.sqrt((7.0+2.0*np.sqrt(7.0))/21.0)/2.0, \
0.5-np.sqrt((7.0-2.0*np.sqrt(7.0))/21.0)/2.0, \
0.5+np.sqrt((7.0-2.0*np.sqrt(7.0))/21.0)/2.0, \
0.5+np.sqrt((7.0+2.0*np.sqrt(7.0))/21.0)/2.0, \
1.0])
return time*tf
# solve with SciPy fsolve
def myFunction(z,*param):
n = param[0]
m = param[1]
# rename z as x and xdot variables
x = np.empty(n-1)
xdot = np.empty(n-1)
x[0:n-1] = z[0:n-1]
xdot[0:n-1] = z[n-1:m]
# initial condition (x0)
x0 = 0.0
# input parameter (u)
u = 4.0
# final time
tn = tf
# function evaluation residuals
F = np.empty(m)
# nonlinear differential equations at each node
# 5 dx/dt = -x^2 + u
F[0:n-1] = 5.0 * xdot[0:n-1] + x[0:n-1]**2 - u
# collocation equations
# tn * NC * xdot = x - x0
NC = colloc(n)
F[n-1:m] = tn * np.dot(NC,xdot) - x + x0 * np.ones(n-1)
return F
sol_py = np.empty(5) # store 5 results
for i in range(2,7):
n = i
m = (i-1)*2
zGuess = np.ones(m)
z = fsolve(myFunction,zGuess,args=(n,m))
# add to plot
yc = np.insert(z[0:n-1],0,0)
plt.plot(tc(n),yc,'o',markersize=10,label='Nodes = '+str(i))
# store just the last x[n] value
sol_py[i-2] = z[n-2]
plt.legend(loc='best')
# ----------------------------------------------------
# Approach #2 - Write model in APMonitor and let
# modeling language create the collocation equations
# ----------------------------------------------------
# load GEKKO
from gekko import GEKKO
sol_apm = np.empty(5) # store 5 results
i = 0
for nodes in range(2,7):
m = GEKKO(remote=False)
u = m.Param(value=4)
x = m.Var(value=0)
m.Equation(5*x.dt() == -x**2 + u)
m.time = [0,tf]
m.options.imode = 4
m.options.time_shift = 0
m.options.nodes = nodes
m.solve() # solve problem
sol_apm[i] = x.value[-1] # store solution (last point)
i += 1
# print the solutions
print(sol_py)
print(sol_apm)
# show plot
plt.ylabel('x(t)')
plt.xlabel('time')
plt.show()
You can define variables with the same name (such as x) or use m.Array(m.Var,n) to define the variables. One thing to look at is the model file by opening the run folder with m.open_folder() before you send the m.solve() command. Look at the .apm file in that folder with a text editor.
Related
I am trying to solve this equation using Runge Kutta 4th order:
applying d2Q/dt2=F(y,x,v) and dQ/dt=u Q=y in my program.
I try to run the code but i get this error:
Traceback (most recent call last):
File "C:\Users\Egw\Desktop\Analysh\Askhsh1\asdasda.py", line 28, in <module>
k1 = F(y, u, x) #(x, v, t)
File "C:\Users\Egw\Desktop\Analysh\Askhsh1\asdasda.py", line 13, in F
return ((Vo/L -(R0/L)*u -(R1/L)*u**3 - y*(1/L*C)))
OverflowError: (34, 'Result too large')
I tried using the decimal library but I still couldnt make it work properly.I might have not used it properly tho.
My code is this one:
import numpy as np
from math import pi
from numpy import arange
from matplotlib.pyplot import plot, show
#parameters
R0 = 200
R1 = 250
L = 15
h = 0.002
Vo=1000
C=4.2*10**(-6)
t=0.93
def F(y, u, x):
return ((Vo/L -(R0/L)*u -(R1/L)*u**3 - y*(1/L*C)))
xpoints = arange(0,t,h)
ypoints = []
upoints = []
y = 0.0
u = Vo/L
for x in xpoints:
ypoints.append(y)
upoints.append(u)
m1 = u
k1 = F(y, u, x) #(x, v, t)
m2 = h*(u + 0.5*k1)
k2 = (h*F(y+0.5*m1, u+0.5*k1, x+0.5*h))
m3 = h*(u + 0.5*k2)
k3 = h*F(y+0.5*m2, u+0.5*k2, x+0.5*h)
m4 = h*(u + k3)
k4 = h*F(y+m3, u+k3, x+h)
y += (m1 + 2*m2 + 2*m3 + m4)/6
u += (k1 + 2*k2 + 2*k3 + k4)/6
plot(xpoints, upoints)
show()
plot(xpoints, ypoints)
show()
I expected to get the plots of u and y against t.
Turns out I messed up with the equations I was using for Runge Kutta
The correct code is the following:
import numpy as np
from math import pi
from numpy import arange
from matplotlib.pyplot import plot, show
#parameters
R0 = 200
R1 = 250
L = 15
h = 0.002
Vo=1000
C=4.2*10**(-6)
t0=0
#dz/dz
def G(x,y,z):
return Vo/L -(R0/L)*z -(R1/L)*z**3 - y/(L*C)
#dy/dx
def F(x,y,z):
return z
t = np.arange(t0, 0.93, h)
x = np.zeros(len(t))
y = np.zeros(len(t))
z = np.zeros(len(t))
y[0] = 0.0
z[0] = 0
for i in range(1, len(t)):
k0=h*F(x[i-1],y[i-1],z[i-1])
l0=h*G(x[i-1],y[i-1],z[i-1])
k1=h*F(x[i-1]+h*0.5,y[i-1]+k0*0.5,z[i-1]+l0*0.5)
l1=h*G(x[i-1]+h*0.5,y[i-1]+k0*0.5,z[i-1]+l0*0.5)
k2=h*F(x[i-1]+h*0.5,y[i-1]+k1*0.5,z[i-1]+l1*0.5)
l2=h*G(x[i-1]+h*0.5,y[i-1]+k1*0.5,z[i-1]+l1*0.5)
k3=h*F(x[i-1]+h,y[i-1]+k2,z[i-1]+l2)
l3 = h * G(x[i - 1] + h, y[i - 1] + k2, z[i - 1] + l2)
y[i]=y[i-1]+(k0+2*k1+2*k2+k3)/6
z[i] = z[i - 1] + (l0 + 2 * l1 + 2 * l2 + l3) / 6
Q=y
I=z
plot(t, Q)
show()
plot(t, I)
show()
If I may draw your attention to these 4 lines
m1 = u
k1 = F(y, u, x) #(x, v, t)
m2 = h*(u + 0.5*k1)
k2 = (h*F(y+0.5*m1, u+0.5*k1, x+0.5*h))
You should note a fundamental structural difference between the first two lines and the second pair of lines.
You need to multiply with the step size h also in the first pair.
The next problem is the step size and the cubic term. It contributes a term of size 3*(R1/L)*u^2 ~ 50*u^2 to the Lipschitz constant. In the original IVP per the question with u=Vo/L ~ 70 this term is of size 2.5e+5. To compensate only that term to stay in the stability region of the method, the step size has to be smaller 1e-5.
In the corrected initial conditions with u=0 at the start the velocity u remains below 0.001 so the cubic term does not determine stability, this is now governed by the last term contributing a Lipschitz term of 1/sqrt(L*C) ~ 125. The step size for stability is now 0.02, with 0.002 one can expect quantitatively useful results.
You can use decimal libary for more precision (handle more digits), but it's kind of annoying every value should be the same class (decimal.Decimal).
For example:
import numpy as np
from math import pi
from numpy import arange
from matplotlib.pyplot import plot, show
# Import decimal.Decimal as D
import decimal
from decimal import Decimal as D
# Precision
decimal.getcontext().prec = 10_000_000
#parameters
# Every value should be D class (decimal.Decimal class)
R0 = D(200)
R1 = D(250)
L = D(15)
h = D(0.002)
Vo = D(1000)
C = D(4.2*10**(-6))
t = D(0.93)
def F(y, u, x):
# Decomposed for use D
a = D(Vo/L)
b = D(-(R0/L)*u)
c = D(-(R1/L)*u**D(3))
d = D(-y*(D(1)/L*C))
return ((a + b + c + d ))
xpoints = arange(0,t,h)
ypoints = []
upoints = []
y = D(0.0)
u = D(Vo/L)
for x in xpoints:
ypoints.append(y)
upoints.append(u)
m1 = u
k1 = F(y, u, x) #(x, v, t)
m2 = (h*(u + D(0.5)*k1))
k2 = (h*F(y+D(0.5)*m1, u+D(0.5)*k1, x+D(0.5)*h))
m3 = h*(u + D(0.5)*k2)
k3 = h*F(y+D(0.5)*m2, u+D(0.5)*k2, x+D(0.5)*h)
m4 = h*(u + k3)
k4 = h*F(y+m3, u+k3, x+h)
y += (m1 + D(2)*m2 + D(2)*m3 + m4)/D(6)
u += (k1 + D(2)*k2 + D(2)*k3 + k4)/D(6)
plot(xpoints, upoints)
show()
plot(xpoints, ypoints)
show()
But even with ten million of precision I still get an overflow error. Check the components of the formula, their values are way too high. You can increase precision for handle them, but you'll notice it takes time to calculate them.
Problem implementation using scipy.integrate.odeint and scipy.integrate.solve_ivp.
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import odeint, solve_ivp
# Input data initial conditions
ti = 0.0
tf = 0.5
N = 100000
h = (tf-ti)/N
# Initial conditions
u0 = 0.0
Q0 = 0.0
t_span = np.linspace(ti,tf,N)
r0 = np.array([Q0,u0])
# Parameters
R0 = 200
R1 = 250
L = 15
C = 4.2*10**(-6)
V0 = 1000
# Systems of First Order Equations
# This function is used with odeint, as specified in the documentation for scipy.integrate.odeint
def f(r,t,R0,R1,L,C,V0):
Q,u = r
ode1 = u
ode2 = -((R0/L)*u)-((R1/L)*u**3)-((1/(L*C))*Q)+(V0/L)
return np.array([ode1,ode2])
# This function is used in our 4Order Runge-Kutta implementation and in scipy.integrate.solve_ivp
def F(t,r,R0,R1,L,C,V0):
Q,u = r
ode1 = u
ode2 = -((R0/L)*u)-((R1/L)*u**3)-((1/(L*C))*Q)+(V0/L)
return np.array([ode1,ode2])
# Resolution with oedint
sol_1 = odeint(f,r0,t_span,args=(R0,R1,L,C,V0))
sol_2 = solve_ivp(fun=F,t_span=(ti,tf), y0=r0, method='LSODA',args=(R0,R1,L,C,V0))
Q_odeint, u_odeint = sol_1[:,0], sol_1[:,1]
Q_solve_ivp, u_solve_ivp = sol_2.y[0,:], sol_2.y[1,:]
# Figures
plt.figure(figsize=[30.0,10.0])
plt.subplot(3,1,1)
plt.grid(color = 'red',linestyle='--',linewidth=0.4)
plt.plot(t_span,Q_odeint,'r',t_span,u_odeint,'b')
plt.xlabel('t(s)')
plt.ylabel('Q(t), u(t)')
plt.subplot(3,1,2)
plt.plot(sol_2.t,Q_solve_ivp,'g',sol_2.t,u_solve_ivp,'y')
plt.grid(color = 'yellow',linestyle='--',linewidth=0.4)
plt.xlabel('t(s)')
plt.ylabel('Q(t), u(t)')
plt.subplot(3,1,3)
plt.plot(Q_solve_ivp,u_solve_ivp,'green')
plt.grid(color = 'yellow',linestyle='--',linewidth=0.4)
plt.xlabel('Q(t)')
plt.ylabel('u(t)')
plt.show()
Runge-Kutta 4th
# Code development of Runge-Kutta 4 Order
# Parameters
R0 = 200
R1 = 250
L = 15
C = 4.2*10**(-6)
V0 = 1000
# Input data initial conditions #
ti = 0.0
tf = 0.5
N = 100000
h = (tf-ti)/N
# Initial conditions
u0 = 0.0
Q0 = 0.0
# First order ordinary differential equations
def f1(t,Q,u):
return u
def f2(t,Q,u):
return -((R0/L)*u)-((R1/L)*u**3)-((1/(L*C))*Q)+(V0/L)
t = np.zeros(N); Q = np.zeros(N); u = np.zeros(N)
t[0] = ti
Q[0] = Q0
u[0] = u0
for i in range(0,N-1,1):
k1 = h*f1(t[i],Q[i],u[i])
l1 = h*f2(t[i],Q[i],u[i])
k2 = h*f1(t[i]+(h/2),Q[i]+(k1/2),u[i]+(l1/2))
l2 = h*f2(t[i]+(h/2),Q[i]+(k1/2),u[i]+(l1/2))
k3 = h*f1(t[i]+(h/2),Q[i]+(k2/2),u[i]+(l2/2))
l3 = h*f2(t[i]+(h/2),Q[i]+(k2/2),u[i]+(l2/2))
k4 = h*f1(t[i]+h,Q[i]+k3,u[i]+l3)
l4 = h*f2(t[i]+h,Q[i]+k3,u[i]+l3)
Q[i+1] = Q[i] + ((k1+2*k2+2*k3+k4)/6)
u[i+1] = u[i] + ((l1+2*l2+2*l3+l4)/6)
t[i+1] = t[i] + h
plt.figure(figsize=[20.0,10.0])
plt.subplot(1,2,1)
plt.plot(t,Q_solve_ivp,'r',t,Q_odeint,'y',t,Q,'b')
plt.grid(color = 'yellow',linestyle='--',linewidth=0.4)
plt.xlabel('t(s)')
plt.ylabel(r'$Q(t)_{Odeint}$, $Q(t)_{RK4}$')
plt.subplot(1,2,2)
plt.plot(t,Q_solve_ivp,'g',t,Q_odeint,'y',t,Q,'b')
plt.grid(color = 'yellow',linestyle='--',linewidth=0.4)
plt.xlabel('t(s)')
plt.ylabel(r'$Q(t)_{solve_ivp}$, $Q(t)_{RK4}$')
I need help to solve a ODE system using odeint or any workaround that works. I`m trying to model a ODE system descriving the beheaviour of a cilinder on a mechanichal arm, using Lagrangian mechanics I got the following 2nd order ODE system:
$$\ddot{x} = x*\dot{\phi}^2 - g*(sin(\phi) +cos(\phi))$$
$$\ddot{\phi} = \frac{1}{2*\dot{x}*m_c}*(\frac{k*i}{x}-\frac{m_b*L*\dot{\phi}}{2}-g*cos(\phi)*(m_c*x-m_b*L/2))$$
Then I transformed it to a 4x4 1st order ODE system y linearized 2 equations aroun initial condition, to make it more "solveable".
In python I`wrote the folllowing:
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
# source: https://apmonitor.com/pdc/index.php/Main/SolveDifferentialEquations
g, mc, mb, k, L = (9.8, 0.5, 1.3, 2, 1.2)
def model(z, t, i, z0):
global g, mc, mb, k, L
#ODE variables
x1 = z[0]
y1 = z[1]
x2 = z[2]
y2 = z[3]
#ODE initial conditions
zx1 = z0[0]
zy1 = z0[1]
zx2 = z0[2]
zy2 = z0[3]
#Diferential for linealization around I.C.
dx1 = z[0]-z0[0]
dy1 = z[1]-z0[1]
dx2 = z[2]-z0[2]
dy2 = z[3]-z0[3]
def w1(zx1, zy1, zx2, zy2):
global g, mc, mb, k, L
return zx2*zy1**2-g*(np.sin(zy2)+np.cos(zy2))
def w2(zx1, zy1, zx2, zy2):
global g, mc, mb, k, L
return 1/(2*zx1*mc)*((k*i)/(zx2)-mb*zy2*L/2-g*np.cos(zy2*(mc*zx2-mb*L/2)))
dx2dt = x1
#dx1dt = x2*y1**2-g*(np.sin(y2)+np.cos(y2))
dx1dt = w1(zx1, zy1, zx2, zy2) + zy1**2*dx2+2*zx2 * \
zy1*dy1-g*(np.cos(zy2)-np.sin(zy2))*dy2
dy2dt = y1
#dy1dt = 1/(2*x1*mc)*((k*i)/(x2)-mb*y2*L/2-g*np.cos(y2*(mc*x2-mb*L/2)))
dy1dt = w2(zx1, zy1, zx2, zy2) - 1/(4*zx1**2*mc) * \
((k*i)/(zx2)-mb*zy2*L/2-g*np.cos(zy2*(mc*zx2-mb*L/2)))*dx1
+(k*i*np.log(abs(zx2))/(2*zx1*mc)-g*np.cos(zy2)*mc)*dx2-(mb*L) / \
(4*zx1*mc)*dy1+g*np.sin(zy2)*(mc*zx2-mb*L/2)*(2*zx1*mc)*dy2
dzdt = [dx1dt, dy1dt, dx2dt, dy2dt]
return dzdt
# initial condition
z0 = [0.01, 0.01, 0.8, np.pi/8]
# number of time points
n = 30
# time points
t = np.linspace(0, 1, n)
# step input
u = np.zeros(n)
# change to 2.0 at time = 5.0
u[:] = 1
# store solution
x1 = np.zeros(n)
y1 = np.zeros(n)
x2 = np.zeros(n)
y2 = np.zeros(n)
# record initial conditions
x1[0] = z0[0]
y1[0] = z0[1]
x2[0] = z0[2]
y2[0] = z0[3]
# solve ODE
for i in range(1, n):
# span for next time step
tspan = [t[i-1], t[i]]
# solve for next step
z = odeint(model, z0, tspan, args=((u[i], z0)) )
# store solution for plotting
if -np.pi/4 <= z[1][3] <= np.pi/4: # Angulo +/- Pi/4
u[i] = u[i-1] + 1
else:
u[i] = u[i-1]
if 0 <= z[1][2] <= L: # Se mantiene en el brazo
print("Se salió")
x1[i] = z[1][0]
y1[i] = z[1][1]
x1[i] = z[1][2]
y1[i] = z[1][3]
# Siguientes CI
z0 = z[1]
# plot results
plt.plot(t, u, 'g:', label='u(t)')
plt.plot(t, x2, 'b-', label='x(t)')
plt.plot(t, y2, 'r--', label='phi(t)')
plt.ylabel('Cilindro(x,phi)')
plt.xlabel('Tiempo(s)')
plt.legend(loc='best')
plt.show()
Then I`ve got the following error, telling me than too much accuracy is needed to solve my system:
lsoda-- at start of problem, too much accuracy
requested for precision of machine.. see tolsf (=r1)
in above message, r1 = NaN
lsoda-- repeated occurrences of illegal input
lsoda-- at start of problem, too much accuracy
requested for precision of machine.. see tolsf (=r1)
in above message, r1 = NaN
dy1dt = w2(zx1, zy1, zx2, zy2) - 1/(4*zx1**2*mc) * \
lsoda-- warning..internal t (=r1) and h (=r2) are
such that in the machine, t + h = t on the next step
(h = step size). solver will continue anyway
in above, r1 = 0.3448275862069D+00 r2 = 0.1555030227910D-28
I have a problem with time and path optimization. I couldn't define two objectives for both time and path simultaneously. Python reads the last objective and gives result according to that way.
Could you please help me to solve this optimization problem? Thanks..
import matplotlib.pyplot as plt
from gekko import GEKKO
# Gekko model
m = GEKKO(remote=False)
# Time points
nt = 501 # nt=101
tm = np.linspace(0, 1, nt) # tm = np.linspace(0, 100, nt)
m.time = tm
# Variables
g = m.Const(value=9.80665)
V = m.Const(value=200) # velocity
Xi = m.Var(value=0, lb=-2*np.pi, ub=2*np.pi) # Heading angle value=-np.pi dene
x = m.Var(value=0, lb=-100000, ub=100000) # x position
y = m.Var(value=0, lb=-100000, ub=100000) # y position
pathx = m.Const(value=70000) # intended distance in x direction
pathy = m.Const(value=20000) # intended distance in y direction
p = np.zeros(nt) # final time=1
p[-1] = 1.0
final = m.Param(value=p)
m.options.MAX_ITER = 1000000 # iteration number
# Optimize Final Time
tf = m.FV(value=1.0, lb=0.0001, ub=1000.0)
tf.STATUS = 1
# Controlled parameters
Mu = m.MV(value=0, lb=-1, ub=1) # solver controls bank angle
Mu.STATUS = 1
Mu.DCOST = 1e-3
# Equations
m.Equation(x.dt() == tf * (V * (m.cos(Xi))))
m.Equation(y.dt() == tf * (V * (m.sin(Xi))))
m.Equation(Xi.dt() == tf * (g * m.tan(Mu)) / V )
# Objective Function
w = 1e4
m.Minimize(w * (x * final - pathx) ** 2) # 1D part (x)
m.Minimize(w * (pathy - y * final) ** 2) # 2D part (y)
m.Obj(tf)
'''
Multiple objectives are combined into a single objective with summation in Gekko. If there are multiple separate objectives that should be considered separately then a Pareto front is a possible approach to evaluate the tradeoff (see Section 6.5 of the Optimization book). However, this isn't implemented in Gekko.
One correct is that m.Equation(y * final <= pathy ) should be m.Equation((y-pathy) * final <= 0) to avoid a potential infeasible solution if pathy<0.
import matplotlib.pyplot as plt
from gekko import GEKKO
import numpy as np
# Gekko model
m = GEKKO(remote=False)
# Time points
nt = 51 # nt=101
tm = np.linspace(0, 1, nt) # tm = np.linspace(0, 100, nt)
m.time = tm
# Variables
g = m.Const(value=9.80665)
V = m.Const(value=200) # velocity
Xi = m.Var(value=0, lb=-2*np.pi, ub=2*np.pi) # Heading angle value=-np.pi dene
x = m.Var(value=0, lb=-100000, ub=100000) # x position
y = m.Var(value=0, lb=-100000, ub=100000) # y position
pathx = m.Const(value=70000) # intended distance in x direction
pathy = m.Const(value=20000) # intended distance in y direction
p = np.zeros(nt) # final time=1
p[-1] = 1.0
final = m.Param(value=p)
m.options.MAX_ITER = 1000000 # iteration number
# Optimize Final Time
tf = m.FV(value=1.0, lb=0.0001, ub=1000.0)
tf.STATUS = 1
# Controlled parameters
Mu = m.MV(value=0, lb=-1, ub=1) # solver controls bank angle
Mu.STATUS = 1
Mu.DCOST = 1e-3
# Equations
m.Equation(x.dt() == tf * (V * (m.cos(Xi))))
m.Equation(y.dt() == tf * (V * (m.sin(Xi))))
m.Equation(Xi.dt() == tf * (g * m.tan(Mu)) / V )
m.Equation((x-pathx) * final <= 0)
m.Equation((y-pathy) * final <= 0)
# Objective Function
w = 1e4
m.Minimize(w * (x * final - pathx) ** 2) # 1D part (x)
m.Minimize(w * (y * final - pathy) ** 2) # 2D part (y)
#in here python reads the last objective how can i run this two objectives simultaneously.
#m.Obj(Xi * final)
m.Minimize(tf)
#m.Maximize(0.2 * mass * final) # objective function
m.options.IMODE = 6
#m.options.NODES = 2
#m.options.MV_TYPE = 1
#m.options.SOLVER = 3
# m.open_folder() # to search for infeasibilities
m.solve(disp=False)
print('Final Time: ' + str(tf.value[0]))
tm = np.linspace(0,tf.value[0],nt)
#tm = tm * tf.value[0]
fig, axs = plt.subplots(3)
fig.suptitle('Results')
axs[0].plot(tm, Mu.value, 'b-', lw=2, label=r'$Bank$')
axs[0].legend(loc='best')
axs[1].plot(tm, x.value, 'r--', lw=2, label=r'$x$')
axs[1].legend(loc='best')
axs[2].plot(tm, y.value, 'p-', lw=2, label=r'$y$')
axs[2].legend(loc='best')
plt.xlabel('Time')
#plt.ylabel('Value')
plt.show()
plt.figure()
plt.plot(x.value, y.value)
plt.show()
I have a system of nonlinear Ordinary Differential Equations that has 3 parameters as inputs: mu, target, and omega. I am trying the estimate these parameters by minimizing the least squared error between my observed values and the output of the ODE system (x5). I have tried different objective functions as well as different IMODES but the solution never converged. Besides, as I always get no solution found error, I cannot see what the closest best parameters are. Do you have any suggestions for my optimization problem? Thanks!
Here is my yData
My code is:
import numpy as np
import matplotlib.pyplot as plt
from gekko import GEKKO
import pickle
with open('y_observed','rb') as f:
yData = pickle.load(f)
m = GEKKO()
dt = 0.001
m.time = np.arange(0,1.5,dt)
# Parameters
mu = m.FV(value=0.2, lb=-0.1, ub=1)
omega = m.FV(value=70, lb=0, ub=100)
target = m.FV(value=0.1, lb=-1, ub=3)
# Variables
x1 = m.Var(value=0)
x2 = m.Var(value=0.05)
x3 = m.Var(value=0)
x4 = m.Var(value=0)
x5 = m.Var(value=0.05)
x6 = m.Var(value=0)
y = m.Param(value = yData)
m.Minimize((y-x5)**2)
# Constants
A = 75
B = 70
C = 45
# ODEs
m.Equation(x1.dt()== 1-x1)
m.Equation(x2.dt()== x3)
m.Equation(x3.dt()== x1*70*(x1*B*0.25*(target-x2) - x3))
m.Equation(x4.dt()== C*(mu - x4))
m.Equation(x5.dt()== A*1.0/np.abs(mu)*(x4 - ((x5-x2)**2 + x6**2))*(x5-x2) - omega*x6)
m.Equation(x6.dt()== A*1.0/np.abs(mu)*(x4 - ((x5-x2)**2 + x6**2))*x6 + omega*(x5-x2))
#Application options
m.options.SOLVER = 3 #APOPT solver
m.options.IMODE = 5 #Dynamic Simultaneous - estimation
m.options.EV_TYPE = 2 #absolute error
#m.options.NODES = 3 #collocation nodes (2,5)
if True:
mu.STATUS=1
target.STATUS=1
omega.STATUS=1
m.solve(disp=True)
print('Objective: ' + str((x5-yData)**2))
print('Solution')
print('mu = ' + str(mu.value[0]))
print('omega = ' + str(omega.value[0]))
print('target = ' + str(target.value[0]))
plt.figure(1)
plt.subplot(2,1,1)
plt.plot(m.time,x5.value,'k:',LineWidth=2,label=r'$x_5$')
plt.plot(m.time,yData,'b-',LineWidth=2,label=r'$yData$')
plt.ylabel('Value')
plt.legend(loc='best')
plt.subplot(2,1,2)
plt.plot(m.time,mu.value,'r-',LineWidth=2,label=r'$mu$')
plt.plot(m.time,target.value,'r-',LineWidth=2,label=r'$target$')
plt.plot(m.time,omega.value,'r-',LineWidth=2,label=r'$omega$')
plt.legend(loc='best')
plt.xlabel('Time')
plt.ylabel('Value')
plt.show()
Use a Gekko function (m.abs2() or m.abs3()) for absolute value instead of the Numpy np.abs() function.
amu = m.abs3(mu)
This requires a switch to the APOPT solver m.options.SOLVER=1 to ensure that the Mixed Integer part of the model is satisfied. Also, it avoids divide by zero by reformulating your equations as:
m.Equation(amu*x5.dt()== A*(x4 - ((x5-x2)**2 + x6**2))*(x5-x2) - omega*x6)
m.Equation(amu*x6.dt()== A*(x4 - ((x5-x2)**2 + x6**2))*x6 + omega*(x5-x2))
The full problem has quite a few variables:
Number of state variables: 47971
Number of total equations: - 44970
Number of slack variables: - 5996
---------------------------------------
Degrees of freedom : -2995
You can ignore the Warning: DOF <= 0 because not all of the inequality constraints are active. This gives a successful solution that may be a local solution. It may require better parameter initial guess to get global solution such as if you need it to follow the cyclic data.
Objective: 260.68493685
Solution
mu = 0.0
omega = 70.0
target = 0.13906027337
Here is the full source code with results that use your PKL file for retrieving the data.
import numpy as np
import matplotlib.pyplot as plt
from gekko import GEKKO
import pickle
with open('y_observed.pkl','rb') as f:
yData = pickle.load(f)
m = GEKKO()
dt = 0.001
m.time = np.arange(0,1.5,dt)
# Parameters
mu = m.FV(value=0.2, lb=-0.1, ub=1)
omega = m.FV(value=70, lb=0, ub=100)
target = m.FV(value=0.1, lb=-1, ub=3)
# Variables
x1 = m.Var(value=0)
x2 = m.Var(value=0.05)
x3 = m.Var(value=0)
x4 = m.Var(value=0)
x5 = m.Var(value=0.05)
x6 = m.Var(value=0)
y = m.Param(value = yData)
m.Minimize((y-x5)**2)
# Constants
A = 75
B = 70
C = 45
# abs(mu)
amu = m.abs3(mu)
# ODEs
m.Equation(x1.dt()== 1-x1)
m.Equation(x2.dt()== x3)
m.Equation(x3.dt()== x1*70*(x1*B*0.25*(target-x2) - x3))
m.Equation(x4.dt()== C*(mu - x4))
m.Equation(amu*x5.dt()== A*(x4 - ((x5-x2)**2 + x6**2))*(x5-x2) - omega*x6)
m.Equation(amu*x6.dt()== A*(x4 - ((x5-x2)**2 + x6**2))*x6 + omega*(x5-x2))
#Application options
m.options.SOLVER = 1 # APOPT MINLP solver
m.options.IMODE = 5 # Dynamic Simultaneous - estimation
m.options.EV_TYPE = 2 # squared error
m.options.NODES = 3 #collocation nodes (2,5)
if True:
mu.STATUS=1
target.STATUS=1
omega.STATUS=1
m.solve(disp=True)
print('Objective: ' + str(m.options.OBJFCNVAL))
print('Solution')
print('mu = ' + str(mu.value[0]))
print('omega = ' + str(omega.value[0]))
print('target = ' + str(target.value[0]))
plt.figure(1)
plt.subplot(2,1,1)
plt.plot(m.time,x5.value,'k:',lw=2,label=r'$x_5$')
plt.plot(m.time,yData,'b-',lw=2,label=r'$yData$')
plt.ylabel('Value')
plt.legend(loc='best')
plt.subplot(2,1,2)
plt.plot(m.time,mu.value,'b-',lw=2,label=r'$mu$')
plt.plot(m.time,target.value,'r--',lw=2,label=r'$target$')
plt.plot(m.time,omega.value,'g:',lw=2,label=r'$omega$')
plt.legend(loc='best')
plt.xlabel('Time')
plt.ylabel('Value')
plt.show()
One of the complicated things about this problem is allowing mu to be negative. It is easier to solve if you give it a lower bound of a small number (such as 0.01) to avoid the mu=0 solution.
# Parameters
mu = m.FV(value=0.2, lb=0.01, ub=1)
Objective: 260.68477646
Solution
mu = 0.042062519923
omega = 69.992948651
target = 0.13871376807
I have a complexed valued system from a PDE problem, the odeint() in Python cannot deal with it. I wrote a RK4 module to solve my system. It seems to work, however, the computed values are obvious incorrect. At the second time step, whole computed values are zero. Here are my code:
import matplotlib.pyplot as plt
import numpy as np
import drawnow
import time
import math
### Parameters ###
L = 20
n = 64
delta_t = 1.
tmax = 10
miu = 1e-6
x2 = np.linspace(-L/2,L/2, n+1)
x = x2[:n] # periodic B.C. #0 = #n
kx1 = np.linspace(0,n/2-1,n/2)
kx2 = np.linspace(1,n/2, n/2)
kx2 = -1*kx2[::-1]
kx = (2.*math.pi/L)*np.concatenate((kx1,kx2)); kx[0] = 1e-6
ky = kx; y = x
X, Y = np.meshgrid(x, y)
KX,KY = np.meshgrid(kx,ky)
K = KX**2 + KY**2
K2 = np.reshape(K, n**2,1)
### Initial Condition ###
vorticity = np.exp(-0.25*X**2 - 2.*Y**2)
wt = np.fft.fft2(vorticity)
wt2 = np.reshape(wt, n**2, 1) # wt2 is initial condition
### Define ODE ###
def yprime(t,rhs):
global miu, K, K2,n,KX, KY, wt2, wt
psit = -wt/ K
psix = np.real(np.fft.ifft2(1j*KX*psit))
psiy = np.real(np.fft.ifft2(1j*KY*psit))
wx = np.real(np.fft.ifft2(1j*KX*wt))
wy = np.real(np.fft.ifft2(1j*KY*wt))
rhs = -miu * K2 * wt2 + np.reshape(np.fft.fft2(wx*psiy - wy*psix), n**2,1)
return rhs
def RK4(domain,wt2,tmax):
w = np.empty((tmax+1,n**2))
w = w + 0j
t = np.empty(tmax+1) # length
w[0,:] = wt2 # enter initial conditions in y
t[0] = domain[0]
for i in range(1,tmax):
t[i+1] = t[i]+delta_t
w[i+1,:] = RK4Step(t[i], w[i,:],delta_t)
return w
def RK4Step(t,w,delta_t):
k1 = yprime(t,w)
k2 = yprime(t+0.5*delta_t, w+0.5*k1*delta_t)
k3 = yprime(t+0.5*delta_t, w+0.5*k2*delta_t)
k4 = yprime(t+delta_t, w+k3*delta_t)
return w + (k1+2*k2+2*k3+k4)*delta_t/6.
### Prediction ###
TimeStart = 0.
TimeEnd = tmax+1
TimeSpan = np.arange(TimeStart, TimeEnd, delta_t)
wt2_sol = RK4(TimeSpan, wt2, tmax)
for i in TimeSpan:
w = np.real(np.fft.ifft2(np.reshape(wt2_sol[i,:], (n, n))))
plt.pcolor(X,Y,w,shading = 'interp',cmap='jet')
drawnow
time.sleep(0.2)
plt.show()
Any idea why it doesn't work? In addition, I like to make a short video based on the solution. the function 'drawnow' and 'time.sleep() do not seem to work here.
Thank you!
My cleaned up version. Changing the number of inner steps does not change the quality of the result.
Make the Runge-Kutta solver (more) universal, input time array with (times[0],y0) being the initial point of the IVP
replace def yprime(t,rhs): with def yprime(t,wt):, since wt is your state variable, and rhs is the result. So wt is a local variable in yprime. Eliminate rhs by direct assembly in the return statement.
remove all reshape operation, act on the vector space of 2D arrays, numpy is good in treating matrices as some other kind of vector
add matplotlib.animate for the pre-generated image sequence. The tutorial for that seemed easier than the function-based animation
Played with arange to replace linspace in generation of kx. Better option is probably to use fftshift to swap the halves of the frequency array
.
import numpy as np
import math
from matplotlib import pyplot as plt
from matplotlib import animation
#----- Numerical integration of ODE via fixed-step classical Runge-Kutta -----
def RK4Step(yprime,t,y,dt):
k1 = yprime(t , y )
k2 = yprime(t+0.5*dt, y+0.5*k1*dt)
k3 = yprime(t+0.5*dt, y+0.5*k2*dt)
k4 = yprime(t+ dt, y+ k3*dt)
return y + (k1+2*k2+2*k3+k4)*dt/6.
def RK4(yprime,times,y0):
y = np.empty(times.shape+y0.shape,dtype=y0.dtype)
y[0,:] = y0 # enter initial conditions in y
steps = 4
for i in range(times.size-1):
dt = (times[i+1]-times[i])/steps
y[i+1,:] = y[i,:]
for k in range(steps):
y[i+1,:] = RK4Step(yprime, times[i]+k*dt, y[i+1,:], dt)
return y
#====================================================================
#----- Parameters for PDE -----
L = 20
n = 64
delta_t = 1.
tmax = 10
miu = 1e-6
#----- Constructing the grid and kernel functions
x2 = np.linspace(-L/2,L/2, n+1)
x = x2[:n] # periodic B.C. #0 = #n
y = x
kx = np.linspace( -n/2 , n/2-1, n)
kx = (2.*math.pi/L)*np.concatenate((np.arange(0,n/2),np.arange(-n/2,0)));
kx[0] = 1e-6
ky = kx;
X, Y = np.meshgrid(x, y)
KX,KY = np.meshgrid(kx,ky)
K = KX**2 + KY**2
#----- Initial Condition -----
vorticity = np.exp(-0.25*X**2 - 2.*Y**2)
wt0 = np.fft.fft2(vorticity)
#----- Define ODE -----
def wprime(t,wt):
global miu, K, K2,n,KX, KY
psit = -wt / K
psix = np.real(np.fft.ifft2(1j*KX*psit))
psiy = np.real(np.fft.ifft2(1j*KY*psit))
wx = np.real(np.fft.ifft2(1j*KX*wt))
wy = np.real(np.fft.ifft2(1j*KY*wt))
return -miu * K * wt + np.fft.fft2(wx*psiy - wy*psix)
#====================================================================
#----- Compute the numerical solution -----
TimeStart = 0.
TimeEnd = tmax+delta_t
TimeSpan = np.arange(TimeStart, TimeEnd, delta_t)
wt_sol = RK4(wprime,TimeSpan, wt0)
#----- Animate the numerical solution -----
fig = plt.figure()
ims = []
for i in TimeSpan:
w = np.real(np.fft.ifft2(wt_sol[i,:]))
im = plt.pcolor(X,Y,w,edgecolors='none',cmap='jet')
ims.append([im])
ani = animation.ArtistAnimation(fig, ims, interval=50, blit=True,
repeat_delay=1000)
#ani.save('PDE-animation.mp4')
plt.show()