天天看點

ACADOS學習(2)ACADOS學習(2)

ACADOS學習(2)

這一篇将介紹如果在C/C++環境下配置和使用ACADOS。官網的方法是使用MATLAB來配置和生成C代碼,因為本人不是很喜歡用MATLAB,是以這裡展示如何使用Python生成的代碼後用C++來實作。這個可能不是最佳的實作方法,歡迎大家讨論。

代碼介紹

首先,我們仍然需要Python作為前端生成c代碼,具體内容詳見ACADOS學習(1)。在執行Python代碼後,會自動生成c代碼,預設會在一個c_generated_code的檔案夾中。在我的GitHub中,提供了一個簡易的c++實作,代碼詳見下面解釋

int main()
{
    // 讀取非線性優化器
    nlp_solver_capsule *acados_ocp_capsule = mobile_robot_acados_create_capsule();

    // 載入優化器
    status = mobile_robot_acados_create(acados_ocp_capsule);
	// 确認是否載入成功
    if (status)
    {
        printf("mobile_robot_acados_create() returned status %d. Exiting.\n", status);
        exit(1);
    }
	// 使用仿真器
    status = mobile_robot_acados_sim_create();

    if (status)
    {
        printf("acados_create() simulator returned status %d. Exiting.\n", status);
        exit(1);
    }

    // 獲得一些NLP相關結構體
    ocp_nlp_config *nlp_config = mobile_robot_acados_get_nlp_config(acados_ocp_capsule);
    ocp_nlp_dims *nlp_dims = mobile_robot_acados_get_nlp_dims(acados_ocp_capsule);
    ocp_nlp_in *nlp_in = mobile_robot_acados_get_nlp_in(acados_ocp_capsule);
    ocp_nlp_out *nlp_out = mobile_robot_acados_get_nlp_out(acados_ocp_capsule);

    // 一些相關變量
    N = nlp_dims->N;
    nx = *nlp_dims->nx;
    nu = *nlp_dims->nu;
    printf("time horizion is %d, with state %d and input %d \n", N, nx, nu);

    // 這裡我使用Eigen來存儲資料,這個不是ACADOS必須的
    Eigen::MatrixXd simX((N+1), nx);
    Eigen::MatrixXd simU(N, nu);
    Eigen::VectorXd time_record(N);

    x_current[0] = 0.0;
    x_current[1] = 0.0;
    x_current[2] = 0.0;

    x_target[0] = 2.0;
    x_target[1] = 2.0;
    x_target[2] = 1.0;

    x_state[0] = 2.0;
    x_state[1] = 2.0;
    x_state[2] = 1.0;
    x_state[3] = 0.0;
    x_state[4] = 0.0;

    // 設定N期望目标狀态
    ocp_nlp_cost_model_set(nlp_config, nlp_dims, nlp_in, N, "yref", x_target);
	// 設定0-N-1期望狀态
    for(int i=0; i<nx; i++)
        simX(0, i)  = x_current[i];

    for (int i=0; i<N; i++)
        ocp_nlp_cost_model_set(nlp_config, nlp_dims, nlp_in, i, "yref", x_state);

    // 閉環仿真
    for (int ii=0; ii<N; ii++)
    {
        // 計時器
        auto t_start = std::chrono::high_resolution_clock::now();
        // 設定x0
        ocp_nlp_constraints_model_set(nlp_config, nlp_dims, nlp_in, 0, "lbx", x_current);
        ocp_nlp_constraints_model_set(nlp_config, nlp_dims, nlp_in, 0, "ubx", x_current);
		// 求解
        status = mobile_robot_acados_solve(acados_ocp_capsule);
        
        // 得到u0
        ocp_nlp_out_get(nlp_config, nlp_dims, nlp_out, 0, "u", &u_current);
        // 隻是為了記錄
        for (int i=0; i<nu; i++)
            simU(ii, i) = u_current[i];
		// 計算運作時間
        auto t_end = std::chrono::high_resolution_clock::now();
        double elapsed_time_ms = std::chrono::duration<double, std::milli>(t_end-t_start).count();
        time_record(ii) = elapsed_time_ms;

        // 通過仿真器計算下一個可能的狀态
        sim_in_set(mobile_robot_sim_config, mobile_robot_sim_dims,
                   mobile_robot_sim_in, "u", u_current);
        sim_in_set(mobile_robot_sim_config, mobile_robot_sim_dims,
                   mobile_robot_sim_in, "x", x_current);

        status = mobile_robot_acados_sim_solve();
        if (status != ACADOS_SUCCESS)
        {
            printf("acados_solve() failed with status %d.\n", status);
        }
		// 獲得更新後機器人位置
        sim_out_get(mobile_robot_sim_config, mobile_robot_sim_dims,
                    mobile_robot_sim_out, "x", x_current);

        for (int i=0; i<nx; i++)
            simX(ii+1, i) = x_current[i];



    }

    // 計算運作時間
    for (int i=0; i<N+1; i++)
        printf("Final result index %d %f, %f, %f \n", i, simX(i, 0), simX(i, 1), simX(i, 2));

    printf("average estimation time %f ms \n", time_record.mean());
    printf("max estimation time %f ms \n", time_record.maxCoeff());
    printf("min estimation time %f ms \n", time_record.minCoeff());
    return status;
}
           

代碼的結構和Python部分是基本一緻的。運作時間上看C++代碼并沒有對于Python代碼有明顯優勢,提升隻是ms級别。究其原因,Python其實後端運作也是c代碼。

接下來稍微解釋一下CMakeList.txt的設定

cmake_minimum_required(VERSION 3.1)
project(acados_test LANGUAGES C CXX)

# for macOS 這個隻是為了macOS,Linux下會自動忽略
set(CMAKE_MACOSX_RPATH 1)

# 設定編譯器标準
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -DEXT_DEP -fdiagnostics-show-option")

# 使用Eigen3(非必須)
find_package(PkgConfig)
pkg_search_module(Eigen3 REQUIRED eigen3)
include_directories(${Eigen3_INCLUDE_DIRS})

# 設定引用變量,注意在安裝時候配置好$ENV{ACADOS_SOURCE_DIR}變量
include_directories($ENV{ACADOS_SOURCE_DIR}/include)
include_directories($ENV{ACADOS_SOURCE_DIR}/include/blasfeo/include)
include_directories($ENV{ACADOS_SOURCE_DIR}/include/hpipm/include)
include_directories($ENV{ACADOS_SOURCE_DIR}/include/acados)
include_directories(../python/c_generated_code)

# 載入ACADOS預設庫
link_directories($ENV{ACADOS_SOURCE_DIR}/lib)

file(GLOB ocp_solver
        ../python/c_generated_code/acados_solver_mobile_robot.c
        )

file(GLOB casadi_fun
        ../python/c_generated_code/mobile_robot_model/mobile_robot_expl_ode_fun.c
        ../python/c_generated_code/mobile_robot_model/mobile_robot_expl_vde_forw.c)

file(GLOB sim_solver
        ../python/c_generated_code/acados_sim_solver_mobile_robot.c
        )

# 将原來C代碼設定為庫
add_library(ocp_shared_lib SHARED ${ocp_solver} ${casadi_fun} )
target_link_libraries(ocp_shared_lib acados hpipm blasfeo)
## 這裡使用到仿真器,如果在實際實作的時候這部分就不需要了
add_library(sim_shared_lib SHARED ${sim_solver} ${casadi_fun})
target_link_libraries(sim_shared_lib acados hpipm blasfeo)

# 編譯自己的例子
add_executable(mobile_robot_app src/mobile_robot_app.cpp )
target_link_libraries(mobile_robot_app ocp_shared_lib sim_shared_lib)
           

總結

C++代碼比Python代碼的優勢不算明顯,好處是不需要每次都編譯一次模型,而是直接載入編譯好的檔案(暫時我還不知道如何用ACADOS直接讀取編譯好的動态連結庫而跳過編譯),計算時間在本例中大概比Python快1ms左右。缺點是Debug和展示資料比Python來說麻煩很多,請大家自行取舍。

繼續閱讀