g2o的基础使用

以下为ubuntu18版本的g2o对应的代码,和16版不兼容。TEB的常见安装问题就是16版到18版不兼容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 求解器一般有两种, TEB用的是 Levenberg
typedef g2o::BlockSolver<g2o::BlockSolverTraits<3, 3>> Block; // 每个误差项优化变量维度为3,误差值维度为1

Block::LinearSolverType* linearSolver = new g2o::LinearSolverDense<Block::PoseMatrixType>();

Block* solver_ptr = new Block(std::unique_ptr<Block::LinearSolverType>(linearSolver) );

g2o::OptimizationAlgorithmLevenberg* solver = new g2o::OptimizationAlgorithmLevenberg(std::unique_ptr<Block>(solver_ptr) );

g2o::SparseOptimizer optimizer;
// setAlgorithm()需要传递一个优化算法
optimizer.setAlgorithm(solver);
optimizer.setVerbose(true);
optimizer->initMultiThreading();

// ......
optimizer.optimize(maxIterations); //开始优化,设置迭代次数

  • static bool initMultiThreading();

Eigen3.1开始,要求we call an initialize函数,如果从不同线程调用Eigen
如果g2o基于OpenMP编译,而且Eigen版本高于3.1,则这个函数调用 Eigen::initParallel

LinearSolver

它用来求解线性方程 ,可以从PCG、CSparse、Choldmod、Dense、Eigen选择求解方法。

  • LinearSolverCSparse:使用CSparse法。继承自LinearSolverCCS

  • LinearSolverCholmod :使用sparse cholesky分解法。继承自LinearSolverCCS

  • LinearSolverDense :使用dense cholesky分解法。继承自LinearSolver

  • LinearSolverEigen: 依赖项只有eigen,使用eigen中的sparse Cholesky 求解,因此编译好后可以方便的在其他地方使用,性能和CSparse差不多。继承自LinearSolver

  • LinearSolverPCG :使用preconditioned conjugate gradient 法,继承自LinearSolver

选哪个求解器很重要,有一次使用LinearSolverDense,结果260秒才迭代一次,换成LinearSolverCSparse,0.4秒就迭代一次

TEB是涉及到CSparseCholdmod,默认使用前者

解析器solver的另一种创建方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 加载几组 solver
G2O_USE_OPTIMIZATION_LIBRARY(pcg)
G2O_USE_OPTIMIZATION_LIBRARY(cholmod)
G2O_USE_OPTIMIZATION_LIBRARY(csparse)
G2O_USE_OPTIMIZATION_LIBRARY(dense)
G2O_USE_OPTIMIZATION_LIBRARY(eigen)


g2o::OptimizationAlgorithmFactory *solver_factory = g2o::OptimizationAlgorithmFactory::instance();
// 记录所有可用的 solver
std::ofstream fileout("/home/user/test_nodes/solvers.txt");
solver_factory->listSolvers(fileout);
// or lm_var_cparse
g2o::OptimizationAlgorithmProperty solver_property;
g2o::OptimizationAlgorithm *solver = solver_factory->construct("lm_var", solver_property);
std::unique_ptr<g2o::SparseOptimizer> optimizer(new g2o::SparseOptimizer());
optimizer->setAlgorithm(solver);

construct函数比上面的LinearSolverDense系列更灵活,设置求解器更精准。

使用listSolvers输出所有可用solver到文件,这个结果是和使用了多少G2O_USE_OPTIMIZATION_LIBRARY有关的,上面的5个应该涵盖了所有求解器,结果非常多。比如不加载G2O_USE_OPTIMIZATION_LIBRARY(eigen),那么lm_var不可用,会报错 SOLVER FACTORY WARNING: Unable to create solver lm_var

listSolvers会输出所有求解器名称和解释,比如

1
lm_var    Eigen 	Levenberg: Cholesky solver using Eigen's Sparse Cholesky methods (variable blocksize)

优化结果

optimizer.setVerbose(true)后才能看到优化过程

或者g2o::SparseOptimizer::chi2(),优化前后各调用一次,可得到优化前后误差变化

1
2
3
4
5
iteration= 0	 chi2= 6973711.783675	 time= 0.228985	 cumTime= 0.228985	 edges= 3995	 schur= 0	 lambda= 42.450053	 levenbergIter= 1
iteration= 1 chi2= 477444.019837 time= 0.219176 cumTime= 0.448161 edges= 3995 schur= 0 lambda= 14.150018 levenbergIter= 1
......
iteration= 20 chi2= 10344.665262 time= 0.200148 cumTime= 4.63974 edges= 3995 schur= 0 lambda= 1.634041 levenbergIter= 1
iteration= 21 chi2= 10344.665262 time= 0.461369 cumTime= 5.10111 edges= 3995 schur= 0 lambda= 112290463347.367386 levenbergIter= 8

time是每次的优化时间,cumTime是累计用时。可以看到chi2最后几次迭代不怎么变化了,说明iteration设置大了


非线性最小二乘

照搬其他人的讲解。

方程个数 > 未知数个数,也就是”超定”线性方程组。方程个数比未知数个数多,可以想象不同方程之间会出现”冲突”的现象,也就是说导致整个方程组没有精确解。但我们只要设定一个误差标准,找到一个让这个误差标准值最小的近似解也是完全可以接收的。即针对超定不相容线性方程组,利用最小二乘误差标准求解近似值。






1.1 首先构建位姿之间的关系,即图的边

下面和开头的非线性最小二乘的推导一样了




问题:用最小二乘拟合直线,什么时候会失败?

答: 矩阵 不可逆的情况。 我的理解是把最小二乘按正规方程解法,也就是先获得 ,这是一个 n×n
的线性方程组,称为正规方程组。显然解为


图优化

图优化SLAM通过减去原始传感器的测量,构建了简化的估计问题。这些原始的测量由图中的边取代,边可以看做“虚拟测量”(virtual measurements)。图中的两个节点之间的边是用两个位姿之间的相对位置的概率分布表示的,这一概率分布的条件是它们的相互测量。观测模型 是多模态的,因此高斯分布的假设不成立。这意味着一次观测 可能影响图中连接不同位姿的多条边。 graph connectivity 应当用概率分布来描述。如果直接处理这个多模态问题,将出现极大的复杂度。我们需要将地图变成node和edge的拓扑结构。因此,需要确定不同node之间的edge约束。

我们应当判断一次观测导致的最可能约束,这取决于机器人位姿的概率分布。这一问题称作data association,常由SLAM后端解决。前端需要对机器人轨迹 上的条件先验进行一致估计,这就要求机器人运动过程中对前端和后端穿插地执行。因此后端的准确性和效率对SLAM系统非常重要。

如果观测值受局部高斯噪声影响,而且data association已知,基于图的建图算法的目标就是机器人轨迹的高斯估计后验。包括计算这个高斯分布的均值,然后以均值作为节点的configuration,这样最大化了观测的似然。

  • 图优化中,顶点是优化项,而边是约束项。优化过程就是通过全局调整优化项,使约束项的和最小。
  • Pose(x)Landmark(m)构成了图的顶点集
  • 边(Edge)存在两种。第一种是Pose到Pose,另一种是Pose到 Landmark
  • 图优化中边表示约束,Pose到Pose的边与机器人的运动模型相关,机器人从运动到,形成了这条边。同时在运动模型的作用下,可以获得机器人在,控制量为时的状态 g。该值与“真实”的Pose 的差,构成了这条边的约束。由于边的约束是标量,因此取这个差的平方。R表示为运动模型的协方差。公式可以写作

  • Pose到Landmark的边与机器人的观测模型相关,机器人从 观测到 ,就会形成一条边。同时在观测模型 的作用下,可以获得机器人在 时对 的预测 。该值与此时真正观测到的值 的差,构成了这条边的约束。同样的,取这个差的平方作为约束值。 表示为观测模型的协方差。公式

  • 完整的约束为两种约束的和

SLAM的图优化问题就是寻找合适的 Pose(x), Landmark(m),使 最小。



看到预测值和观测值的差,很容易想到Cartographer,PoseExtrapolator提供预测值,Scan Matcher提供观测值。

或者可以这样理解:
Pose Graph

我们用 表示预测值和观测值之间的差,假设其满足高斯分布 ,或者说观测值的分布是以真值为中心的多元高斯分布。 我们的目标是让预测值和观测值尽可能趋近于0(均值),也就是让 出现在概率最大的地方,这就是个最大似然的问题。 我们让概率密度函数取最大值

把图中的x换成 , 均值取0,得到下面的最小二乘形式

最小二乘问题的目标函数的变量维度很高,但每个误差项都是简单的,仅与少量变量有关。因此为了直观的展示变量与变量之间的关系,可以把优化问题用图的方式来展现。其中顶点表示优化变量,边表示误差项。

图优化的流程

  1. 选择你想要的图里的顶点与边的类型,确定它们的参数化形式

  2. 往图里加入实际的顶点和边

  3. 选择初值,开始迭代

  4. 每一步迭代中,计算对应于当前估计值的雅可比矩阵和海塞矩阵

  5. 求解稀疏线性方程 ,得到梯度方向

  6. 继续用高斯牛顿法(GN)或列文列文伯格-马夸尔特法(LM)进行迭代。如果迭代结束,返回优化值。

SLAM中的图优化

滤波类算法最大的问题,是无法处理大尺度场景的建图。由于滤波类算法是基于递归计算的,下一时刻的估计值依赖于上一时刻的估计,因而在大尺度场景下,由于系统参数和传感器观测数据的不准确性,会造成误差慢慢累积,一旦当前时刻的估计出现偏差,无法修正该误差,最终无法获得一致性的地图。

另外滤波方法对计算量和内存消耗都很大,使用图优化方法则效率较高,这种方法位姿不是实时纠正,而是将数据记录下来,最后进行计算优化。

机器人的位姿是一个顶点(vertex),位姿之间的关系构成边(edge),边表示两个位姿的空间约束(相对位姿和对应方差)。比如t+1时刻和t时刻之间的里程计关系构成边,或者由视觉计算出来的位姿转换矩阵也可以构成边。图构建完成后,就要调整机器人的位姿去尽量满足这些边构成的约束。图优化就是构建图并调整各顶点的位姿,使预测和观测的误差最小。每一条边的信息矩阵就是协方差矩阵的逆。协方差越小,说明这次测量越准,信息权重就越大。

前端: 从里程计和激光雷达数据中求得位姿,构建图。机器人位姿当做顶点,位姿间关系当做边,常常是传感器信息的堆积
后端: 在图构建好以后,采用回环检测,出现了回环边说明有误差。构造闭环约束,调整机器人位姿,通过最小化观测和估计残差求得优化后的位姿。

pose graph的概念
帧间边
回环边

简单的回环检测 1
通过帧间匹配得到 之间的相对位姿,称为观测值

简单的回环检测 2

观测方程也有很多形式,如:

  • 机器人两个Pose之间的变换;
  • 机器人在某个Pose处用激光测量到了某个空间点,得到了它离自己的距离与角度;
  • 机器人在某个Pose处用相机观测到了某个空间点,得到了它的像素坐标;

同样,它们的具体形式很多样化,这允许我们在讨论slam问题时,不局限于某种特定的传感器或姿态表达方式。

这个减法只是通用的,在SLAM里可能没有定义减法。


梯度, 海森矩阵和凸函数

梯度


梯度是一个矢量,曲面上每点的梯度是常数。

曲面中点的方向导数有无数个,当方向导数与梯度方向一致时,该导数值取得最大,等价于该点在梯度方向具有最快的变化值。梯度方向是函数值增加最快的方向,梯度的反方向是函数值减小最快的方向。

参考:如何直观形象地理解方向导数与梯度以及它们之间的关系?

海森矩阵

Hessian矩阵是半正定的

对一元函数f(x)来说,就极值而言,一阶导数为0是极值点的必要但不充分条件,一阶导数为0且二阶导数大于0是极小值的充要条件。用二阶泰勒展开就能理解。

对于多元变量,二阶高斯公式展开如下:

作为海森矩阵,也可以写作,可以理解为把梯度向量推广为二阶形式,梯度向量本身也是Jacobian矩阵的一种特例。

写作 ,类似一元函数,我们希望二次项 对任意的 成立,这就等价于 半正定。

时,也就是海森矩阵正定,的局部极小点。但这是充分不必要条件,有的点x可能是极小点,但该点的海森矩阵不是正定的。

但是当时,无法判断此点是否为极值点,所以牛顿法及阻尼牛顿法的缺陷就在这里,因为是半正定的,所以不能保证每次都能收敛到极小值点。

海森矩阵正定,则函数为凸函数

凸函数的任何局部极小点 都是该函数的一个全局极小点。


速度约束的源码及雅格比矩阵的推导

所有约束在teb_local_planner\include\teb_local_planner\g2o_types,都是头文件。 动态障碍约束EdgeDynamicObstacle,最短路径约束EdgeShortestPath,优先转向约束EdgePreferRotDir,路径点约束EdgeViaPoint没有求雅格比矩阵。

速度约束

最小化的函数是

penaltyInterval表示惩罚函数,见penaltyBoundToInterval()函数,它只用于速度约束、加速度约束和edge_velocity_obstacle_ratio

向量的维度是2,第一个元素代表线速度,第二个是角速度。
调用:TebOptimalPlanner::AddEdgesVelocitysetTebConfig()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class EdgeVelocity : public BaseTebMultiEdge<2, double>
{
public:
EdgeVelocity()
{
this->resize(3); // from a g2o::BaseMultiEdge, set the desired number of vertices
}
// 代价函数
void computeError()
{
ROS_ASSERT_MSG(cfg_, "You must call setTebConfig on EdgeVelocity()");
const VertexPose* conf1 = static_cast<const VertexPose*>(_vertices[0]);
const VertexPose* conf2 = static_cast<const VertexPose*>(_vertices[1]);
const VertexTimeDiff* deltaT = static_cast<const VertexTimeDiff*>(_vertices[2]);
// 两点的向量差
const Eigen::Vector2d deltaS = conf2->estimate().position() - conf1->estimate().position();

double dist = deltaS.norm();
const double angle_diff = g2o::normalize_theta(conf2->theta() - conf1->theta());
// 使用实际弧长,而不是两点的直线距离
if (cfg_->trajectory.exact_arc_length && angle_diff != 0)
{
double radius = dist/(2*sin(angle_diff/2));
dist = fabs( angle_diff * radius ); // actual arg length!
}
double vel = dist / deltaT->estimate(); // 线速度

// fast_sigmoid函数是保证速度连续可微
// vel *= g2o::sign(deltaS[0]*cos(conf1->theta()) + deltaS[1]*sin(conf1->theta())); // consider direction
vel *= fast_sigmoid( 100 * (deltaS.x()*cos(conf1->theta()) + deltaS.y()*sin(conf1->theta())) ); // consider direction
const double omega = angle_diff / deltaT->estimate(); // 角速度
// 线速度需要限制在最大和最大倒退的范围内
_error[0] = penaltyBoundToInterval(vel, -cfg_->robot.max_vel_x_backwards, cfg_->robot.max_vel_x, cfg_->optim.penalty_epsilon);
// 角速度只需限制在最大内
_error[1] = penaltyBoundToInterval(omega, cfg_->robot.max_vel_theta, cfg_->optim.penalty_epsilon);

ROS_ASSERT_MSG(std::isfinite(_error[0]), "EdgeVelocity::computeError() _error[0]=%f _error[1]=%f\n",_error[0],_error[1]);
}

人工智能开发中很少用到不是0就是1的阶跃函数,而是用更为平滑过渡的sigmoid的函数,在深度学习里用的很多。这里的fast_sigmoid函数是保证速度连续可微,很有借鉴意义。

Rösmann还使用了sign/sigmoid函数决定当前的运动方向。

计算误差函数用到的penaltyBoundToInterval,比较好理解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
 /*  Linear penalty function for bounding  var to the interval   $-a < var < a $
* var The scalar that should be bounded
* a lower and upper absolute bound
* epsilon safty margin (move bound to the interior of the interval)
* penaltyBoundToIntervalDerivative
* return Penalty / cost value that is nonzero if the constraint is not satisfied
*/

inline double penaltyBoundToInterval(const double& var,const double& a,const double& epsilon)
{
if (var < -a+epsilon)
{
return (-var - (a - epsilon));
}
// 怀疑这里应该是 >=
if (var <= a-epsilon)
{
return 0.;
}
else
{
return (var - (a - epsilon));
}
}

/**
* Linear penalty function for bounding \c var to the interval \f$ a < var < b \f$
* var The scalar that should be bounded
* a lower bound
* b upper bound
* epsilon safty margin (move bound to the interior of the interval)
* penaltyBoundToIntervalDerivative
* return Penalty / cost value that is nonzero if the constraint is not satisfied
*/
inline double penaltyBoundToInterval(const double& var,const double& a, const double& b, const double& epsilon)
{
if (var < a+epsilon)
{
return (-var + (a + epsilon));
}
if (var <= b-epsilon)
{
return 0.;
}
else
{
return (var - (b - epsilon));
}
}

雅格比矩阵的推导

雅格比矩阵的计算被注释了,以为使用重写的雅格比可以加快运算速度,但是Github上有作者的解释,看了似懂非懂:对于一些nontrivial(非显而易见的,难以直观理解的) cost functions (比如 penalizing the distance between a line and a polygon) 最终表达式实在太长了,是否 central difference approximation is slower (2 times evaluating the function) then evaluating the exact Jacobian.

雅克比矩阵的维度为 误差维度 × 优化变量的维度,class EdgeVelocity : public BaseTebMultiEdge<2, double>,是三元边,两个VertexPose顶点,类型PoseSE2(3维)。顶点VertexTimeDiff,类型double(1维)。对于前者,雅格比矩阵是 2x3,对后者是 2x1,也就是如下

1
2
3
_jacobianOplus[0].resize(2,3); // conf1
_jacobianOplus[1].resize(2,3); // conf2
_jacobianOplus[2].resize(2,1); // deltaT

我们把雅格比矩阵简写为 表示第1个VertexPose 表示第2个VertexPose 表示VertexTimeDiff 是两行三列的矩阵,第一行是线速度的误差方程penaltyBoundToInterval, , 求偏导。第二行是角速度的误差方程(penaltyBoundToInterval另一个重载形式)对 , , 求偏导。

以此类推

J[0](0, 0) 和 J[0](1, 2)的推导过程


五次多项式平滑算法
Welcome to my blog, enter password to read.
Read more
二分查找法

不断分段找中点,判断中点是否是要寻找的num

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// 获取 num 在 vector 中的 index
int findIndex(const vector<int>& vector, int num);

int main(int argc, char *argv[])
{
vector<int> v;
for(int i=0; i<9; i++)
{
v.emplace_back();
auto& num = v.back();
num = i*2;
cout << " v.back: "<< v.back();
}
int id = findIndex(v, 6) ;
if(id < 0 )
cout << "no such number !" <<endl;
else
cout << "index of number is : "<< id <<endl;
return 0;
}

int findIndex(const vector<int>& vector, int num)
{
Q_ASSERT(vector.size() >0 );
int size = vector.size();
if(vector.at(0) ==num ) return 0;
if(size==1 && vector.at(0) !=num ) return -1;
if(num == vector.back() ) return size-1;

int begin, mid, end;
begin =0; end = vector.size()-1;
mid = (begin + end) / 2;
int count = 0;
while(num != vector.at(mid) )
{
count++;
if(count > log2(size) )
{
return -1;
break;
}
// 比较mid和num, 按情况只取一段进行递归比较,这样才降低复杂度
if( num > vector.at(mid) )
{
begin = mid; end = end;
mid = (begin + end) / 2;
}
else
{
begin = begin; end = mid;
mid = (begin + end) / 2;
}
}
return mid;
}

Apollo的平滑路径模块

无人车的轨迹优化,都是通过建立优化问题,是非线性规划问题,通过IPopt来求解,

  1. 这个过程有个重要的前提,那就是f(x)是连续可导。如果f(x)这个函数不可导,或者不可微呢?比如说,在SLAM中需要处理姿态估计的问题。而姿态是通过旋转矩阵和平移向量表示的,这显然是不连续的。但是,由于旋转矩阵是一个李群,那么我们可以将其映射到其李代数上。而且,李代数是可以求导的。所以我们就还可以利用非线性优化的方法对姿态进行估计。

  2. 上面的解法求得的只是局部最优解,不一定是全局最优解。因为它只看到下一时刻,在邻域内的最小值,很容易陷入到局部最优,或者说求出的最优值是跟初始位置相关的。即使初始值在同一个位置,也很有可能落入到两个不同的局部最优值,也就是说这个解是很不稳定的。如何解决这个问题,至今仍然存在很大的挑战。

Apollo高精地图给出的道路中心线的平滑性往往都不能满足规划模块的需求,因此规划中是不能拿来直接用的,需要对其进行平滑操作。Apollo目前有三种平滑算法如下,默认为离散点平滑。

离散点平滑三种求解方式,分别是利用不同求解器。如果考虑参考线的曲率约束,其优化问题是非线性的,可以使用ipopt非线性求解器求解(内点法),也可以将曲率约束线性化后使用osqp求解器来用SQP方法求解;如果不考虑曲率约束,则直接用osqp求解二次规划问题。目前Apollo 6.0中默认的参数为不考虑曲率约束-

获取 anchor_s

设置中间点 anchor points

从原始的reference_line来选取中间点,即根据reference_lineanchor_s值进行采样,采样的间隔为0.25m。采样完毕后,就能得到一系列的ref_points,而每个anchor_point就包含了一个ref_point以及横纵向的裕度。

横纵向裕度的默认参数为:

longitudinal_boundary_bound : 2.0

max_lateral_boundary_bound : 0.5

min_lateral_boundary_bound : 0.1

然后再根据自车宽度,车道边界类型(是否为curb)等再对横向裕度进行收缩。AnchorPoint结构中的enforced变量表示该点是否为强约束,在参考线平滑中只有首尾两点为强约束。

计算完anchor points后,将其赋值到smoother对象中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 省略不重要部分
class ReferencePoint : public hdmap::MapPathPoint
{
public:
common::PathPoint ToPathPoint(double s) const;

double kappa() const;
double dkappa() const;

static void RemoveDuplicates(std::vector<ReferencePoint>* points);

private:
double kappa_ = 0.0;
double dkappa_ = 0.0;
};

struct LaneWaypoint {
LaneWaypoint() = default;
LaneWaypoint(LaneInfoConstPtr lane, const double s)
: lane(CHECK_NOTNULL(lane)), s(s) {}
LaneWaypoint(LaneInfoConstPtr lane, const double s, const double l)
: lane(CHECK_NOTNULL(lane)), s(s), l(l) {}

LaneInfoConstPtr lane = nullptr;
double s = 0.0;
double l = 0.0;

std::string DebugString() const;
};
1
2
3
4
5
6
7
8
// lane_info_ptr->accumulate_s(); 的来源
for (size_t i = 0; i + 1 < points_.size(); ++i)
{
segments_.emplace_back(points_[i], points_[i + 1]);
accumulated_s_.push_back(s);
unit_directions_.push_back(segments_.back().unit_direction());
s += segments_.back().length();
}

试验theta和sin(theta), tan(theta)

试验theta在多小的时候,约等于sin(theta)tan(theta)

Matlab结果如下



经过换算,暂时取16°或者说0.28弧度


IPOPT的使用

IPOPT是一种常用的非线性优化求解器,使用内点法进行求解。对于复杂问题,需要借助自动微分工具,帮助求解梯度、雅各比矩阵、Hessian矩阵,如ADOL-C,CppAD

安装

参考非线性优化求解器IPOPTubuntu 环境下 IPOPT 安装与使用

我安装完后,部分文件和官方说明的不同,目录coin在我的电脑上是coin-orsudo vim /usr/include/cppad/ipopt/solve_callback.hpp,修改如下

1
2
# include <coin-or/IpIpoptApplication.hpp>
# include <coin-or/IpTNLP.hpp>

配置

CMake配置

1
2
3
4
5
6
7
8
9
message(status "IPOPT_CFLAGS: " ${IPOPT_CFLAGS})
message(status "IPOPT_CFLAGS_OTHER: " ${IPOPT_CFLAGS_OTHER})
set(CMAKE_CXX_FLAGS "-DHAVE_CSTDDEF -DHAVE_MPI_INITIALIZED")

include_directories(/usr/local/include)
link_directories(/usr/local/lib)

add_executable(solver example.cpp)
target_link_libraries(solver ipopt)

举例

状态变量 $ x = [x_1,x_2,x_3,x_4]^\mathrm{T}$

目标函数

约束条件:

起始点

优化结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#include <cppad/ipopt/solve.hpp>
#include <iostream>
//#include <mpi.h>
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 );

// Fortran style indexing
AD<double> x1 = x[0];
AD<double> x2 = x[1];
AD<double> x3 = x[2];
AD<double> x4 = x[3];
// f(x)
fg[0] = x1 * x4 * (x1 + x2 + x3) + x3;
// g_1 (x)
fg[1] = x1 * x2 * x3 * x4;
// g_2 (x)
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;
// number of independent variables (domain dimension for f and g)
size_t nx = 4;
// number of constraints (range dimension for g)
size_t ng = 2;
// x[i] 设置第i个变量的初始迭代值
Dvector xi(nx);
xi[0] = 1.0;
xi[1] = 1.0;
xi[2] = 1.0;
xi[3] = 1.0;

// x_l[i]设置xi的下界值, x_u[i]设置xi的上界值
Dvector xl(nx), xu(nx);
for(i = 0; i < nx; i++)
{
xl[i] = 1.0;
xu[i] = 5.0;
}
// g_l[i]设置约束i的下界值, g_u[i]设置约束i的上界值
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
std::string options;
// turn off any printing
options += "Integer print_level 0\n";
options += "String sb yes\n";
// maximum number of iterations
options += "Integer max_iter 100\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 testing
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";

// place to return solution
CppAD::ipopt::solve_result<Dvector> solution;

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

// Check some of the solution values
ok &= solution.status == CppAD::ipopt::solve_result<Dvector>::success;
double rel_tol = 1e-6; // relative tolerance
double abs_tol = 1e-6; // absolute tolerance
std::cout << "solution x: "<< std::endl;
for(i = 0; i < nx; i++)
{
std::cout << solution.x[i] << std::endl;
}
return ok;
}

int main(int argc, const char** argv) {
//MPI_Init();
get_started();
return 0;
}