I am attempting to translate the MPC implemented in python and cvxpy here in Julia using JuMP and Gurobi. However when attempting to solve the first iteration of the optimization I set up I am getting an unconstrained/infeasible error. I am fairly new to quadradic optimization and think I made an error in one of these areas:
- translation of cvxpy.quad_form(A,B) to the julia transpose(A)BA
for example:
Python:
cost += cvxpy.quad_form(u[:, t], R)
Julia:
cost += transpose(u[:,t])*R*u[:,t]
- translation of LQR formulation (or really any of the Matrix contraints) where I need to use element wise equality in Julia (I don’t think this would be wrong but maybe?)
python:
constraints += [x[:, t + 1] == A @ x[:, t] + B @ u[:, t] + C]
Julia
@constraint(model, x[:, t + 1] .== A*x[:,t] + B * u[:,t] + C)
- contraint and objective vs NLcontraint and NLobjective. the quadradic terms are not linear but the jump documentation states that the solver can tell and @contraint is preferred for quadradic constraints.
I would appreciate any help on this issue. Full code posted below. reference python code from PythonRobotics Linked above.
using LinearAlgebra
using JuMP
import DataFrames
import GLPK
using SparseArrays
using Plots
using Gurobi
NX = 4 # x = x, y, v, yaw
NU = 2 # a = [accel, steer]
T = 5 # horizon length
# mpc parameters
R = Diagonal([0.01, 0.01]) # input cost matrix
Rd = Diagonal([0.01, 1.0]) # input difference cost matrix
Q = Diagonal([1.0, 1.0, 0.5, 0.5]) # state cost matrix
Qf = Q # state final matrix
GOAL_DIS = 1.5 # goal distance
STOP_SPEED = 0.5 / 3.6 # stop speed
MAX_TIME = 500.0 # max simulation time
# iterative paramter
MAX_ITER = 3 # Max iteration
DU_TH = 0.1 # iteration finish param
TARGET_SPEED = 10.0 / 3.6 # [m/s] target speed
N_IND_SEARCH = 10 # Search index number
DT = 0.2 # [s] time tick
# Vehicle parameters
LENGTH = 4.5 # [m]
WIDTH = 2.0 # [m]
BACKTOWHEEL = 1.0 # [m]
WHEEL_LEN = 0.3 # [m]
WHEEL_WIDTH = 0.2 # [m]
TREAD = 0.7 # [m]
WB = 2.5 # [m]
MAX_STEER = deg2rad(45.0) # maximum steering angle [rad]
MAX_DSTEER = deg2rad(30.0) # maximum steering speed [rad/s]
MAX_SPEED = 55.0 / 3.6 # maximum speed [m/s]
MIN_SPEED = -20.0 / 3.6 # minimum speed [m/s]
MAX_ACCEL = 1.0 # maximum accel [m/ss]
struct State
x::Float64
y::Float64
yaw::Float64
v::Float64
predelta
end
function pi_2_pi(angle)
while(angle > pi)
angle = angle - 2.0*pi
end
while(angle < -pi)
angle = angle + 2.0*pi
end
return angle
end
function get_linear_model_matrix(v, phi, delta)
A = zeros(NX,NX)
A[1, 1] = 1.0
A[2, 2] = 1.0
A[3, 3] = 1.0
A[4, 4] = 1.0
A[1, 3] = cos(phi) * DT
A[1, 4] = -v * sin(phi) * DT
A[2, 3] = sin(phi) * DT
A[2, 4] = v * cos(phi) * DT
A[4, 3] = (tan(delta)/WB) * DT
B = zeros(NX, NU)
B[3, 1] = DT
B[4, 2] = (v/(WB*(cos(delta)^2))) * DT
C = zeros(NX)
C[1] = v * sin(phi) * phi * DT
C[2] = -v * cos(phi) * phi * DT
C[4] = -(DT * v * delta)/(WB * (cos(delta)^2))
return A, B, C
end
function update_state(state, a , delta)
if delta >= MAX_STEER
delta = MAX_STEER
elseif delta <= -MAX_STEER
delta = -MAX_STEER
end
state.x = state.x + state.v * cos(state.yaw) * DT
state.y = state.y + state.v * sin(state.yaw) * DT
state.yaw = state.yaw + state.v / WB * tan(delta) * DT
state.v = state.v + a * DT
if state.v > MAX_SPEED
state.v = MAX_SPEED
elseif state.v < MIN_SPEED
state.v = MIN_SPEED
end
return state
end
function predict_motion(x0, oa, od, xref)
#why would you multiply this by 0?
xbar = xref * 0.0
for i in 1:length(x0)
xbar[i,1] = x0[i]
end
state = State(x0[1], x0[2], x0[4], x0[3], nothing)
for (ai, di, i) in zip(oa, od, 2:length(T+1))
state = update_state(state, ai, di)
xbar[1, i] = state.x0
xbar[2, i] = state.y
xbar[3, i] = state.v
xbar[4, i] = state.yaw
end
return xbar
end
function iterative_linear_mpc_control(xref,x0, dref, oa, od)
if isnothing(oa) || isnothing(od)
oa = zeros(T)
od = zeros(T)
end
for i in 1:MAX_ITER
xbar = predict_motion(x0, oa, od, xref)
poa = copy(oa)
pod = copy(od)
oa, od, ox, oy, oyaw, ov = linear_mpc_control(xref, xbar, x0, dref)
du = sum(abs(oa - poa)) + sum(abs(od - pod))
if du <= DU_TH
break
end
end
return oa, od, ox, oy, oyaw, ov
end
"""
linear_mpc_control(xref, xbar, x0, dref)
run the mimiziation problem given the constraints
xref: reference point
xbar: operational point
x0: initial state
dref: reference steer angle
"""
function linear_mpc_control(xref, xbar, x0, dref)
model = Model(Gurobi.Optimizer)
# generate variable matrix for states
@variable(model, x[1:NX, 1:(T+1)])
# generate variable matrix for controls
@variable(model, u[1:NU, 1:(T)])
cost = 0.0
for t in 1:T
# im pretty sure cvxpy.quad_form(u,r) is the same thing as u^T*r*u
#Pthon implimentation: cost += cvxpy.quad_form(u[:, t], R
cost += transpose(u[:,t])*R*u[:,t]
if t != 1
#python implimentation: cvxpy.quad_form(xref[:, t] - x[:, t], Q)
cost += transpose(xref[:, t] - x[:, t]) * Q * (xref[:, t] - x[:, t])
end
A,B,C = get_linear_model_matrix(xbar[3,t],xbar[4,t], dref[1,t])
@constraint(model, x[:, t + 1] .== A*x[:,t] + B * u[:,t] + C)
if t < (T - 1)
cost += transpose(u[:, t + 1] - u[:, t]) * Rd * (u[:, t + 1] - u[:, t])
# display(u[2, t + 1] - u[2, t])
#cant take the absolute value of a contraint
#I think we just add 2 contriants then
# @constraint(model, abs(u[2, t + 1] - u[2, t]) <= MAX_DSTEER * DT)
@constraint(model, u[2, t + 1] - u[2, t] <= MAX_DSTEER * DT)
@constraint(model, u[2, t + 1] - u[2, t] >= -MAX_DSTEER * DT)
end
end
cost += transpose(xref[:, T] - x[:, T]) * Qf * (xref[:, T] - x[:, T])
#add more contraints
@constraint(model, c1, x[:,1] .== x0)
@constraint(model, c2, x[3,:] .<= MAX_SPEED)
@constraint(model, c3, x[3,:] .>= MIN_SPEED)
#cant do abs in this
# @constraint(model, c4, abs.(u[1,:]) <= MAX_ACCEL)
@constraint(model, c4, u[1,:] .<= MAX_ACCEL)
@constraint(model, c5, u[1,:] .>= -MAX_ACCEL)
# @constraint(model, c5, abs.(u[2,:]) >= MAX_STEER)
@constraint(model, c6, (u[2,:]) .>= MAX_STEER)
@constraint(model, c7, (u[2,:]) .<= -MAX_STEER)
JuMP.abs2
#set objective function
@objective(model, Min, cost)
print(model)
optimize!(model)
if termination_status(model) == MOI.OPTIMAL
#solution is optimal
solution_summary(model)
# display(value(x))
# display(value(u))
#get optimzal values from variables
ox = value(x[1,:])
oy = value(x[2,:])
ov = value(x[3,:])
oyaw = value(x[4,:])
oa = value(u[1,:])
odelta = value(i[2,:])
else
print("\n\ncannot solve\n\n\n")
ox = nothing
oy = nothing
ov = nothing
oyaw = nothing
oa = nothing
odelta = nothing
end
return oa, odelta, ox, oy, oyaw, ov
end
function calc_ref_trajectory(state, cx, cy, cyaw, sp, dl, pind)
xref = zeros(NX, T + 1)
dref = zeros(1, T + 1)
ncourse = length(cx) #cx is the course x position list
ind, _ = calc_nearest_index(state, cx, cy, cyaw, pind)
if pind >= ind
ind = pind
end
xref[1, 1] = cx[ind]
xref[2, 1] = cy[ind]
xref[3, 1] = sp[ind]
xref[4, 1] = cyaw[ind]
dref[1, 1] = 0.0 # steer operational point should be 0
travel = 0.0
for i in 1:T+1
travel += abs(state.v) * DT
dind = convert(Int32,round(travel/dl))
if (ind + dind) < ncourse
xref[1, i] = cx[ind + dind]
xref[2, i] = cy[ind + dind]
xref[3, i] = sp[ind + dind]
xref[4, i] = cyaw[ind + dind]
dref[1, i] = 0.0
else #the minus 1's here might mess it up
xref[1, i] = cx[ncourse - 1]
xref[2, i] = cy[ncourse - 1]
xref[3, i] = sp[ncourse - 1]
xref[4, i] = cyaw[ncourse - 1]
dref[1, i] = 0.0
end
end
return xref, ind, dref
end
function calc_nearest_index(state, cx, cy, cyaw, pind)
dx = [state.x - icx for icx in cx[pind:(pind + N_IND_SEARCH)]]
dy = [state.y - icy for icy in cy[pind:(pind + N_IND_SEARCH)]]
d = [idx^2 + idy^2 for (idx, idy) in zip(dx, dy)]
mind = minimum(d)
ind = indexin(mind,d)[1] + (pind - 1)
mind = sqrt(mind)
dxl = cx[ind] - state.x
dyl = cy[ind] - state.yaw
angle = pi_2_pi(cyaw[ind] - atan(dyl, dxl))
if angle < 0
mind = mind * -1
end
return ind, mind
end
"""
do_simulation(cx, cy, cyaw, ck, sp, dl, initial_state)
cx: course x position list
cy: course y position list
cy: course yaw position list
ck: course curvature list
sp: speed profile
dl: course tick [m]
"""
function do_simulation(cx, cy, cyaw, ck, sp, dl, initial_state)
goal = [last(cx), last(cy)]
state = initial_state
if state.yaw - cyaw[1] >= pi
state.yaw -= math.pi * 2.0
elseif state.yaw - cyaw[1] <= -pi
state.yaw += math.pi * 2.0
end
time = 0.0
x = [state.x]
y = [state.y]
yaw = [state.yaw]
v = [state.v]
t = [0.0]
d = [0.0]
a = [0.0]
target_ind, _ = calc_nearest_index(state, cx, cy, cyaw, 0)
odelta = nothing
oa = nothing
cyaw = smooth_yaw(yaw)e
end
function smooth_yaw(yaw)
for i in 1:yaw-1
dyaw = yaw[i + 1] - yaw
while dyaw >= pi / 2.0
yaw[i + 1] -= pi * 2.0
dyaw = yaw[i + 1] - yaw[i]
end
while dyaw <= -pi / 2.0
yaw[i + 1] += pi * 2.0
dyaw = yaw[i + 1] - yaw[i]
end
end
return yaw
end
function calc_speed_profile(cx, cy, cyaw, target_speed)
speed_profile = zeros(length(cx))
speed_profile = speed_profile .+ target_speed
direction = 1.0
#set a stop point
for i in 1:length(cx)-1
dx = cx[i + 1] - cx[i]
dy = cy[i + 1] - cy[i]
move_direction = atan(dy,dx)
if dx != 0 && dy != 0
dangle = abs(pi_2_pi(move_direction - cyaw[i]))
if dangle >= pi/4
direction = -1.0
else
direction = 1.0
end
end
if direction != 1.0
speed_profile[i] = -target_speed
else
speed_profile[i] = target_speed
end
end
speed_profile[lastindex(speed_profile)] = 0.0
return speed_profile
end
function get_CLF_course()
# equation is x(t) = -1(1-0.64t)(1+ 0.64t)
# y(t) = −(1 − 0.64t)(1 − 0.2t − 0.25t2).
#we will just hardcode 50 slices in
T = range(0, pi/2, 50)
ax = []
ay = []
cyaw = []
for t in T
append!(ax,-(1-0.64*t)*(1 + 0.64*t) * 10)
append!(ay, -(1 - 0.64*t)*(1 - 0.2*t - 0.25*t^2) * 10)
append!(cyaw, pi/2 - t)
end
return ax, ay, cyaw
end
############
#start running
#######
print("starting MPC")
#generate the CLF course
cx, cy, cyaw = get_CLF_course()
dl = 1.0 #course tick
#generate the target speeds at each point
sp = calc_speed_profile(cx,cy,cyaw, TARGET_SPEED)
#set the intial state of the vehicle
initial_state = State(cx[1], cy[1], cyaw[1], 0.0, nothing)
#generate optimal acceleration(oa) and steering(od) arrays
oa = zeros(T)
od = zeros(T)
#the final argument of this is just the previous nearest index, so it doesnt have to search every point
target_ind, _ = calc_nearest_index(initial_state, cx, cy, cyaw, 1)
xref, target_ind, dref = calc_ref_trajectory(initial_state, cx, cy, cyaw, sp, dl, target_ind)
x0 = [initial_state.x, initial_state.y, initial_state.v, initial_state.yaw]
xbar = predict_motion(x0, oa, od, xref)
display(xref)
print("\n",xref)
print("\n",x0)
print("\n",dref)
print("\n",oa)
print("\n",od)
oa, od, ox, oy, oyaw, ov = iterative_linear_mpc_control(xref, x0, dref, oa, od)
display(display(plot(cx,cy)))
edit: added optimality check and return variables in linear_mpc_control()