Code_export_directory for multiphase ocp

Hi :wave:
I want to set a custom code_export_directory for my AcadosMultiphaseOcp, but it seems like when I specify the code_export_directory attribute, some of the generated code is still put into the default code_export_directory (c_generated_code). I pasted an example below:

Here is a function that exports the double integrator model, in a file called “acados_double_integrator_model.py”.

from acados_template import AcadosModel
from casadi import SX, vertcat, sin, cos

def export_double_integrator_model(dim_q, dt) -> AcadosModel:
    model_name = 'double_integrator_disc_dyn'

    # set up states & controls
    q = SX.sym('q', dim_q)
    v = SX.sym('v', dim_q)

    x = vertcat(q, v)

    u = SX.sym('u', dim_q)

    vnext = v + u*dt
    qnext = q + vnext*dt

    model = AcadosModel()

    model.disc_dyn_expr = vertcat(qnext, vnext)
    model.x = x
    model.u = u
    model.name = model_name

    return model

The script that imports the above function is called “double_integrator_multiphase_example.py”, pasted below:

from acados_template import AcadosOcp, AcadosOcpSolver, AcadosMultiphaseOcp
from acados_double_integrator_model import export_double_integrator_model
import numpy as np

from casadi import Function

def main():
    # Horizon definition
    N = 20 # Number of time intervals
    Tf = 1.0 # Duration
    dt = Tf/N # Time step

    # Dimension of double integrator configuration and velocity
    nq = 1
    nv = 1

    # State and control dimensions
    nx = nq + nv
    nu = nv

    # Initial state
    x0 = np.concatenate((np.ones(nq), 0.25*np.ones(nv)))

    # Cost matrices
    Q_mat = np.diag(np.concatenate((np.ones(nq), 10*np.ones(nv))))
    R_mat = np.diag(10*np.ones(nu))

    # Number of intervals in each phase
    N_list = [10, 10]
    # Multiphase ocp
    multiphase_ocp = AcadosMultiphaseOcp(N_list=N_list)

    #### PHASE 0: velocity can take any value ####

    # Dynamics
    phase_idx = 0
    acados_model = export_double_integrator_model(nq, dt)

    ocp = AcadosOcp()
    ocp.model = acados_model
    ocp.dims.N = N_list[phase_idx]

    ocp.cost.cost_type = 'EXTERNAL'
    ocp.cost.cost_type_e = 'EXTERNAL'

    # Quadratic state and control cost
    ocp.model.cost_expr_ext_cost = acados_model.x.T @ Q_mat @ acados_model.x + acados_model.u.T @ R_mat @ acados_model.u
    ocp.model.cost_expr_ext_cost_e = acados_model.x.T @ Q_mat @ acados_model.x

    # Control limits
    Fmax = 1
    ocp.constraints.lbu = -Fmax*np.ones(nu)
    ocp.constraints.ubu = Fmax*np.ones(nu)
    ocp.constraints.idxbu = np.arange(nu)

    # Initial state constraint
    ocp.constraints.x0 = x0
    ocp.code_export_directory = 'c_generated_code_double_integrator'

    multiphase_ocp.set_phase(ocp, phase_idx)

    #### PHASE 1: velocity must be nonpositive ####
    phase_idx += 1

    # Dynamics
    acados_model = export_double_integrator_model(nq, dt)

    # Nonlinear constraint: velocity must be nonpositive (yes, this is actually
    # a linear constraint, but we're enforcing it using the acados nonlinear constraint interface)
    h_expr = acados_model.x[nq:]
    h_lb = -10*np.ones(nv)
    h_ub = np.zeros(nv)

    acados_model.con_h_expr = h_expr

    ocp = AcadosOcp()
    ocp.model = acados_model
    ocp.dims.N = N_list[phase_idx]

    ocp.cost.cost_type = 'EXTERNAL'
    ocp.cost.cost_type_e = 'EXTERNAL'

    # Quadratic state and control cost
    ocp.model.cost_expr_ext_cost = acados_model.x.T @ Q_mat @ acados_model.x + acados_model.u.T @ R_mat @ acados_model.u
    ocp.model.cost_expr_ext_cost_e = acados_model.x.T @ Q_mat @ acados_model.x

    # Control limits
    Fmax = 1
    ocp.constraints.lbu = -Fmax*np.ones(nu)
    ocp.constraints.ubu = Fmax*np.ones(nu)
    ocp.constraints.idxbu = np.arange(nu)

    # Set nonlinear constraint sizes and bounds
    ocp.dims.nh = h_expr.shape[0]
    ocp.constraints.lh = h_lb
    ocp.constraints.uh = h_ub
    ocp.code_export_directory = 'c_generated_code_double_integrator'

    multiphase_ocp.set_phase(ocp, phase_idx)

    # Set options
    multiphase_ocp.solver_options.qp_solver = 'PARTIAL_CONDENSING_HPIPM'
    multiphase_ocp.solver_options.qp_solver_cond_N = N
    multiphase_ocp.solver_options.hessian_approx = 'GAUSS_NEWTON'
    multiphase_ocp.solver_options.nlp_solver_type = 'SQP'
    multiphase_ocp.solver_options.tf = Tf
    multiphase_ocp.solver_options.nlp_solver_tol_eq = 1e-4
    multiphase_ocp.solver_options.nlp_solver_tol_ineq = 1e-4
    multiphase_ocp.mocp_opts.integrator_type = ['DISCRETE', 'DISCRETE']
    multiphase_ocp.code_export_directory = 'c_generated_code_double_integrator'

    ocp_solver = AcadosOcpSolver(multiphase_ocp, json_file = 'acados_ocp_double_integrator.json')

    status = ocp_solver.solve()
    ocp_solver.print_statistics() # encapsulates: stat = ocp_solver.get_stats("statistics")

if __name__ == '__main__':
    main()

When I run the script (i.e. using

python3 double_integrator_multiphase_example.py

in the terminal, I get the error

make: *** No rule to make target 'double_integrator_disc_dyn_0_cost/double_integrator_disc_dyn_0_cost_ext_cost_fun.o', needed by 'ocp_shared_lib'.  Stop.

The c_generated_code_double_integrator directory is generated, but the double_integrator_disc_dyn_0_cost directory is still put into the c_generated_code directory, which I think is the issue. I’m able to specify custom export directories with the non-multiphase interface just fine.

Thanks,
Anoop