MPC Convergence Issue

Hi
This is my first time using acados, I modified minimal_example_closed_loop.py example and replaced export_pendulum_ode_model function with my desired dynamics (bicycle model). I have gone through the examples several times but I haven’t found the issue yet. xcurrent never converges to y_ref. I tried increasing max iteration to 1000 but it didn’t solve the issue. When I increase the prediction horizon to 1s, it returns immediately with error status 3.
I would be very grateful if someone could take a look at my code to see if I have done something wrong.

pendulum_model.py

#
# Copyright 2019 Gianluca Frison, Dimitris Kouzoupis, Robin Verschueren,
# Andrea Zanelli, Niels van Duijkeren, Jonathan Frey, Tommaso Sartor,
# Branimir Novoselnik, Rien Quirynen, Rezart Qelibari, Dang Doan,
# Jonas Koenemann, Yutao Chen, Tobias Schöls, Jonas Schlagenhauf, Moritz Diehl
#
# This file is part of acados.
#
# The 2-Clause BSD License
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.;
#

from acados_template import AcadosModel
from casadi import SX, MX, vertcat, sin, cos, atan, Function

def export_pendulum_ode_model():

    model_name = 'pendulum_ode'

    # constants
    m = 0.041
    C1 = 0.5
    C2 = 15.5
    Cm1 = 0.287
    Cm2 = 0.05
    Cr0 = 0.0518
    Cr2 = 0.00035
    Dr = 0.1737
    Cr = 1.2691
    Br = 3.3852
    Df = 0.192
    Cf = 1.2
    Bf = 2.579
    lr = 0.033
    lf = 0.033
    Ptv = 0.1
    Iz = 27.8E-6

    # set up states & controls
    px = MX.sym("px")
    py = MX.sym("py")
    phi = MX.sym("phi")
    vx = MX.sym("vx")
    vy = MX.sym("vy")
    r = MX.sym("r")
    delta = MX.sym("delta")
    T = MX.sym("T")
    x = vertcat(px, py, phi, vx, vy, r, delta, T)

    # controls
    ddelta = MX.sym("ddelta")
    dT = MX.sym("dT")
    u = vertcat(ddelta, dT)
    
    # xdot
    px_dot = MX.sym("px_dot")
    py_dot = MX.sym("py_dot")
    phi_dot = MX.sym("phi_dot")
    vx_dot = MX.sym("vx_dot")
    vy_dot = MX.sym("vy_dot")
    r_dot = MX.sym("r_dot")
    delta_dot = MX.sym("delta_dot")
    T_dot = MX.sym("_Tdot")
    xdot = vertcat(px_dot, py_dot, phi_dot, vx_dot, vy_dot, r_dot, delta_dot, T_dot)

    # algebraic variables
    # z = None

    # parameters
    p = []
    
    # dynamics
    Fx = Cm1 * T - Cr0 - Cr2 * vx * vx
    alphar = atan((vy - lr * r) / vx)
    alphaf = atan((vy + lf * r) / vx) - delta
    Fry = Dr * sin(Cr * atan(Br * alphar))
    Ffy = Df * sin(Cf * atan(Bf * alphaf)) 
    rtarget = delta * vx / (lf + lr)
    Tou = (rtarget - r) * Ptv 
    f_expl = vertcat(
        vx * cos(phi) - vy * sin(phi),
        vx * sin(phi) + vy * cos(phi),
        r,
        (Fx - Ffy * sin(delta) + m * vy * r) / m,
        (Fry + Ffy * cos(delta) - m * vx * r) / m,
        (Ffy * lf * cos(delta) - Fry * lr + Tou) / Iz,
        ddelta,
        dT
    )

    f_impl = xdot - f_expl

    model = AcadosModel()

    model.f_impl_expr = f_impl
    model.f_expl_expr = f_expl
    model.x = x
    model.xdot = xdot
    model.u = u
    # model.z = z
    model.p = p
    model.name = model_name

    return model

# def export_pendulum_ode_model():

#     model_name = 'pendulum_ode'

#     # constants
#     M = 1. # mass of the cart [kg] -> now estimated
#     m = 0.1 # mass of the ball [kg]
#     g = 9.81 # length of the rod [m]
#     l = 0.8 # gravity constant [m/s^2]

#     # set up states & controls
#     x1      = SX.sym('x1')
#     theta   = SX.sym('theta')
#     v1      = SX.sym('v1')
#     dtheta  = SX.sym('dtheta')
    
#     x = vertcat(x1, theta, v1, dtheta)

#     # controls
#     F = SX.sym('F')
#     u = vertcat(F)
    
#     # xdot
#     x1_dot      = SX.sym('x1_dot')
#     theta_dot   = SX.sym('theta_dot')
#     v1_dot      = SX.sym('v1_dot')
#     dtheta_dot  = SX.sym('dtheta_dot')

#     xdot = vertcat(x1_dot, theta_dot, v1_dot, dtheta_dot)

#     # algebraic variables
#     # z = None

#     # parameters
#     p = []
    
#     # dynamics
#     denominator = M + m - m*cos(theta)*cos(theta)
#     f_expl = vertcat(v1,
#                      dtheta,
#                      (-m*l*sin(theta)*dtheta*dtheta + m*g*cos(theta)*sin(theta)+F)/denominator,
#                      (-m*l*cos(theta)*sin(theta)*dtheta*dtheta + F*cos(theta)+(M+m)*g*sin(theta))/(l*denominator)
#                      )

#     f_impl = xdot - f_expl

#     model = AcadosModel()

#     model.f_impl_expr = f_impl
#     model.f_expl_expr = f_expl
#     model.x = x
#     model.xdot = xdot
#     model.u = u
#     # model.z = z
#     model.p = p
#     model.name = model_name

#     return model


def export_pendulum_ode_model_with_discrete_rk4(dT):

    model = export_pendulum_ode_model()

    x = model.x
    u = model.u
    nx = x.size()[0]

    ode = Function('ode', [x, u], [model.f_expl_expr])
    # set up RK4
    k1 = ode(x,       u)
    k2 = ode(x+dT/2*k1,u)
    k3 = ode(x+dT/2*k2,u)
    k4 = ode(x+dT*k3,  u)
    xf = x + dT/6 * (k1 + 2*k2 + 2*k3 + k4)

    model.disc_dyn_expr = xf
    print("built RK4 for pendulum model with dT = ", dT)
    print(xf)
    return model


def export_augmented_pendulum_model():
    # pendulum model augmented with algebraic variable just for testing
    model = export_pendulum_ode_model()
    model_name = 'augmented_pendulum'

    z = SX.sym('z')

    f_impl = vertcat( model.xdot - model.f_expl_expr, \
        z - model.u*model.u)

    model.f_impl_expr = f_impl
    model.z = z
    model.name = model_name

    return model

minimal_example_closed_loop.py

#
# Copyright 2019 Gianluca Frison, Dimitris Kouzoupis, Robin Verschueren,
# Andrea Zanelli, Niels van Duijkeren, Jonathan Frey, Tommaso Sartor,
# Branimir Novoselnik, Rien Quirynen, Rezart Qelibari, Dang Doan,
# Jonas Koenemann, Yutao Chen, Tobias Schöls, Jonas Schlagenhauf, Moritz Diehl
#
# This file is part of acados.
#
# The 2-Clause BSD License
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.;
#

import sys
sys.path.insert(0, 'common')

from acados_template import AcadosOcp, AcadosOcpSolver, AcadosSimSolver
from pendulum_model import export_pendulum_ode_model
from utils import plot_pendulum
import numpy as np
import scipy.linalg

# create ocp object to formulate the OCP
ocp = AcadosOcp()

# set model
model = export_pendulum_ode_model()
ocp.model = model

Tf = 0.001
nx = model.x.size()[0]
nu = model.u.size()[0]
ny = nx + nu
ny_e = nx
N = 20

# set dimensions
ocp.dims.N = N
# NOTE: all dimensions but N are now detected automatically in the Python
#  interface, all other dimensions will be overwritten by the detection.

# set cost module
ocp.cost.cost_type = 'LINEAR_LS'
ocp.cost.cost_type_e = 'LINEAR_LS'

# Q = 2*np.diag([1e3, 1e3, 1e-2, 1e-2])
# R = 2*np.diag([1e-2])

Q = 2*np.diag([1e4, 1e4, 1e-2, 1e-2, 1e-2, 1e-2, 1e-2, 1e-1])
R = 2*np.diag([1e-2, 1e-2])

ocp.cost.W = scipy.linalg.block_diag(Q, R)

ocp.cost.W_e = Q

ocp.cost.Vx = np.zeros((ny, nx))
ocp.cost.Vx[:nx,:nx] = np.eye(nx)

Vu = np.zeros((ny, nu))
# Vu[4,0] = 1.0
Vu[8, 0] = 1.0
Vu[9, 1] = 1.0

ocp.cost.Vu = Vu

ocp.cost.Vx_e = np.eye(nx)

ocp.cost.yref  = np.zeros((ny, ))
ocp.cost.yref_e = np.zeros((ny_e, ))

# set constraints

# Fmax = 80

x0 = np.array([10.0, 5.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0])
ocp.constraints.constr_type = 'BGH'

# ocp.constraints.lbu = np.array([-Fmax])
# ocp.constraints.ubu = np.array([+Fmax])

ocp.constraints.lbu = np.array([-2.0, -1.0])
ocp.constraints.ubu = np.array([2.0, 1.0])

ocp.constraints.x0 = x0
ocp.constraints.idxbu = np.array([0, 1])

ocp.solver_options.qp_solver = 'PARTIAL_CONDENSING_HPIPM' # FULL_CONDENSING_QPOASES
ocp.solver_options.hessian_approx = 'GAUSS_NEWTON'
ocp.solver_options.integrator_type = 'ERK'
ocp.solver_options.nlp_solver_type = 'SQP' # SQP_RTI
ocp.solver_options.nlp_solver_max_iter = 1000

ocp.solver_options.qp_solver_cond_N = N

# set prediction horizon
ocp.solver_options.tf = Tf

acados_ocp_solver = AcadosOcpSolver(ocp, json_file = 'acados_ocp_' + model.name + '.json')
acados_integrator = AcadosSimSolver(ocp, json_file = 'acados_ocp_' + model.name + '.json')

iter = 10000
simX = np.ndarray((iter+1, nx))
simU = np.ndarray((iter, nu))

xcurrent = x0
simX[0,:] = xcurrent

# closed loop
yref = np.array([20.0, 10.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.6, 0.0, 0.0])

for i in range(iter):

    for k in range(N):
        acados_ocp_solver.set(k, "yref", yref)
    # solve ocp
    acados_ocp_solver.set(0, "lbx", xcurrent)
    acados_ocp_solver.set(0, "ubx", xcurrent)
    acados_ocp_solver.print_statistics()

    status = acados_ocp_solver.solve()

    if status != 0:
        raise Exception('acados acados_ocp_solver returned status {}. Exiting.'.format(status))

    simU[i,:] = acados_ocp_solver.get(0, "u")

    # simulate system
    acados_integrator.set("x", xcurrent)
    acados_integrator.set("u", simU[i,:])

    status = acados_integrator.solve()
    if status != 0:
        raise Exception('acados integrator returned status {}. Exiting.'.format(status))

    # update state
    xcurrent = acados_integrator.get("x")
    simX[i+1,:] = xcurrent
    print(xcurrent)

# plot results
# plot_pendulum(np.linspace(0, Tf, N+1), Fmax, simU, simX)

Cheers,
Babak