無人車系統(十):c++與python非線性規(優)劃(化)工具

本篇針對非線性規(優)劃(化)問題,介紹兩個好用的模型構建不求解工具:CppADpyomo 。這兩個工具都可以只構建目標函數與約束函數的形式,而不需要求其雅可比、海森矩陣。兩個工具都可以接受非線性模型形式,並且有與各種優化求解器的接口。

系統:ubuntu 18.04 gcc-7.4.0 g++-7.4.0

1. 背景

系統的真實模型都是非線性,在實際模型構建或使用過程中,我們會對模型進行線性化。例如,我們根據已有的模型求解最優控制問題:讓機器人根據當前的狀態輸出最優的動作,從而使成本函數取最小值。

常見的優化庫都需要我們求目標函數與約速函數的導數、雅可比矩陣以及海森矩陣。如果模型簡單,姑且可以手動求解,模型一旦複雜些(例如大型的非線性模型,約束函數爲微分方程or差分方程形式),非常容易出錯。雖然也有一些庫能夠用於自動微分,例如python的:sympy、autograd、tangent等,c++的:Automatic differentiation(AD)。這些庫僅僅用來微分求導的話是非常合適的,如果要跟最優化求解器(也即優化庫)結合,需要相當多的結口函數。同樣的情況,當問題變得複雜,頭就大了,非常容易出錯,且不好調試。

針對上面的問題,本篇介紹c++與python中將自動微分與優化求解器融合一體的庫:CppAD (for c++), pyomo (for python)。我們只需要確定模型的目標函數、約束函數的形式,這些工具就可以幫我們算出在約束條件下,使目標函數取最值的解。重點:這兩個工具都不需要我們對模型進行線性化,不需要手動求雅可比、海森矩陣等

2. CppAD [for C++]

2.1 CppAD簡介

CppADBradley M. Bell針對運籌學COIN-OR的計算基礎部分進行的一個開源項目。CppAD特點如下:

  • 支持正向、反向的優化求解模式;
  • 任何階數的導數;
  • 使用自動微分(AD)的功能;
  • 稀疏模式;
  • 數值庫模版(可以與其它AD庫一起用);
  • 很多使用例程;
  • 支持windows 與 Linux系統

CppAD源碼:https://github.com/coin-or/CppAD
說明文檔:https://coin-or.github.io/CppAD/doc/cppad.htm#Features.Base%20Type
應用例程集錦:https://coin-or.github.io/CppAD/doc/listallexamples.htm

CppAD: A Package for Differentiation of C++ Algorithms

2.2 CppAD安裝

Ubuntu : sudo apt-get install cppad
CppAD需要調用系統中已經安裝好的優化求解器,如非線性優化器ipoptGurobiGLPK。由於本篇介紹使用CppAD的例子中用到ipopt,此處先介紹一下ipopt的安裝.

Ubuntu下:

sudo apt-get install gfortran
sudo apt-get install unzip
wget https://www.coin-or.org/download/source/Ipopt/Ipopt-3.12.7.zip && unzip Ipopt-3.12.7.zip && rm Ipopt-3.12.7.zip
sudo bash install_ipopt.sh Ipopt-3.12.7

2.3 一個例子

這個例子利用CppAD與Ipopt求解以下非線性規劃問題(這個問題類似於各編程語言的print(“hello world”)):

minimize x1x4(x1+x2+x3)+x3s.t. x1x2x3x425x12+x22+x32+x42=401x1,x2,x3,x45(1) \begin{array}{cc} \text{minimize } &x_1 x_4 (x_1+x_2+x_3)+x_3 \\ \text{s.t. } &x_1 x_2 x_3 x_4 \geq 25 \\ &x_1^2+x_2^2+x_3^2+x_4^2=40 \\ & 1\leq x_1,x_2,x_3,x_4 \leq 5 \end{array} \tag{1}

完整c++代碼如下

#include <iostream>
#include <cppad/ipopt/solve.hpp>

using namespace std;

namespace {
using CppAD::AD;
class FG_eval {
public:
    typedef CPPAD_TESTVECTOR(AD<double>) ADvector;
    void operator()(ADvector& fg, const ADvector& x)
    {
        assert(fg.size() == 3);
        assert(x.size() == 4);
        // variables
        AD<double> x1 = x[0];
        AD<double> x2 = x[1];
        AD<double> x3 = x[2];
        AD<double> x4 = x[3];
        // f(x) objective function
        fg[0] = x1 * x4 * (x1 + x2 + x3) + x3;
        // constraints
        fg[1] = x1 * x2 * x3 * x4;
        fg[2] = x1 * x1 + x2 * x2 + x3 * x3 + x4 * x4;
        return;
    }

};

}

bool get_started(void)
{
    bool ok = true;
    size_t i;
    typedef CPPAD_TESTVECTOR(double) Dvector;

    size_t nx = 4; // number of varibles
    size_t ng = 2; // number of constraints
    Dvector x0(nx); // initial condition of varibles
    x0[0] = 1.0;
    x0[1] = 5.0;
    x0[2] = 5.0;
    x0[3] = 1.0;

    // lower and upper bounds for varibles
    Dvector xl(nx), xu(nx);
    for(i = 0; i < nx; i++)
    {
        xl[i] = 1.0;
        xu[i] = 5.0;
    }
    Dvector gl(ng), gu(ng);
    gl[0] = 25.0;    gu[0] = 1.0e19;
    gl[1] = 40.0;    gu[1] = 40.0;
    // object that computes objective and constraints
    FG_eval fg_eval;

    // options
    string options;
    // turn off any printing
    options += "Integer print_level  0\n";
    options += "String sb            yes\n";
    // maximum iterations
    options += "Integer max_iter     10\n";
    //approximate accuracy in first order necessary conditions;
    // see Mathematical Programming, Volume 106, Number 1,
    // Pages 25-57, Equation (6)
    options += "Numeric tol          1e-6\n";
    //derivative tesing
    options += "String derivative_test   second-order\n";
    // maximum amount of random pertubation; e.g.,
    // when evaluation finite diff
    options += "Numeric point_perturbation_radius   0.\n";


    CppAD::ipopt::solve_result<Dvector> solution; // solution
    CppAD::ipopt::solve<Dvector, FG_eval>(options, x0, xl, xu, gl, gu, fg_eval, solution); // solve the problem

    cout<<"solution: "<<solution.x<<endl;

    //
    //check some of the solution values
    //
    ok &= solution.status == CppAD::ipopt::solve_result<Dvector>::success;
    //
    double check_x[]  = {1.000000, 4.743000, 3.82115, 1.379408};
    double check_zl[] = {1.087871, 0.,       0.,       0.      };
    double check_zu[] = {0.,       0.,       0.,       0.      };
    double rel_tol    = 1e-6; // relative tolerance
    double abs_tol    = 1e-6; // absolute tolerance
    for(i = 0; i < nx; i++)
    {
        ok &= CppAD::NearEqual(
                    check_x[i], solution.x[i], rel_tol, abs_tol);
        ok &= CppAD::NearEqual(
                    check_zl[i], solution.zl[i], rel_tol, abs_tol);
        ok &= CppAD::NearEqual(
                    check_zu[i], solution.zu[i], rel_tol, abs_tol);
    }

    return ok;
}

int main()
{
    cout << "CppAD : Hello World Demo!" << endl;
    get_started();
    return 0;
}

輸出如下:
在這裏插入圖片描述

我們只需要設置初始參數、初始狀態、狀態約束值、等式與不等式約束值、優化目標方程。其它的就交給CppAD吧!

3. pyomo [for Python]

3.1 pyomo簡介

pyomo與CppAD的功能一致,最終我們僅僅需要把待求解問題構建好(可以是非線性,不需要手動求微分),然後扔給它就行了。但是,pyomo比CppAD好的一點是:pyomo具有深刻的模型概念,它把整個問題就當作是一個模型。CppAD雖然功能與pyomo相似,但是卻沒有這樣一個比較讓容易把握整體的概念。

3.2 pyomo安裝

強列建議使用anaconda環境! 強列建議使用anaconda環境! 強列建議使用anaconda環境!

本人環境:Ubuntu 18.04 + Anaconda2.7 (python 2.7)
本人開始是嘗試pip install來安裝配置pyomo,出現很多問題,例如:能裝上包,但是運行出錯(可能是版本問題)。因此,本人乾脆安裝pyomo安裝教程使用Anaconda環境。然後一切非常順利。Anaconda的一個比較全的安裝教程

在Ubuntu + Anaconda環境下安裝pyomo與ipopt:

conda update conda
conda update anaconda
conda install -c conda-forge pyomo
conda install -c conda-forge pyomo.extras
conda install -c conda-forge coincbc
conda install -c conda-forge ipopt

當然,也可以安裝基它可能會用到的優化求解庫:

conda install -c conda-forge glpk

3.3 一個例子

此處仍採用式(1)作爲求解對象來給出pyomo的一個應用例子。

import numpy as np
from pyomo.environ import *
from pyomo.dae import *

model = ConcreteModel()
model.x = Var(RangeSet(1, 4), bounds=(1, 25))
model.cons1 = Constraint(rule=lambda model: 40==model.x[1]**2+model.x[2]**2+model.x[3]**2+model.x[4]**2)
model.cons2 = Constraint(rule=lambda model: 25<=model.x[1]*model.x[2]*model.x[3]*model.x[4])
model.obj = Objective(expr = model.x[1]*model.x[4]*(model.x[1] + model.x[2] + model.x[3]) + model.x[3], sense=minimize) 
SolverFactory('ipopt').solve(model)
solutions = [model.x[i]() for i in range(1, 5)]
print("solutins is :"+str(solutions))

在這裏插入圖片描述

對比CppAD與pyomo求解的結果(如下),兩者是一致的(但是,pyomo的代碼更爲簡潔):
在這裏插入圖片描述

CppAD與pyomo時間成本比較:

pyomo:time cost is:0.0659720897675
CppAD: time cost is:0.004138

總結

爲了簡化求解最優問題的過程,本篇介紹了兩種非線性規劃工具:CppAD for C++;pyomo for python。兩者都能省去求雅可比、海森矩陣的過程。我們只需要定義好求解的模型即可。兩個工具在同一問題上求解結果一致,但是CppAD比pyomo時間成本更小。但是,pyomo在實現代碼量上明顯小於CppAD,這是pyomo的優點。如果系統對實時性要求高,建議採用C++編程+CppAD,如果只是平時做簡單算法測試,建議採用更簡潔的python+pyomo組合。


以上
by windSeS 2019-12-20

發佈了67 篇原創文章 · 獲贊 122 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章