
做机器人控制、运动规划或者轨迹优化的朋友大概率都躲不开这三个库Eigen、OSQP、osqp-eigen。这个组合在模型预测控制MPC里几乎是标配Eigen负责矩阵运算OSQP负责二次规划求解osqp-eigen把两者的接口缝起来让C开发者可以像写Eigen代码一样去调求解器。但问题来了这三个库的安装顺序、CMake查找路径、版本匹配关系第一次折腾的时候是真的容易把人磨到怀疑人生。我自己在Ubuntu和几个国产Linux发行版上都装过不止一次踩过不少坑也帮实验室同学处理过各种奇奇怪怪的报错这篇就把我多次实操下来最顺的一条完整路径写出来顺便把那些官网文档里不会写的坑都摊开讲清楚。这篇文章适合刚接触控制优化方向、需要在Linux上搭建求解环境的读者也适合已经会用这些库但每次换机器都要重新折腾安装的朋友参考我会尽量把每一步的原理和排查逻辑也讲明白保证你装完一遍之后再遇到类似问题能自己动手定位而不是又回去查半天教程。1. 先说清楚这三个库是什么、为什么总是被装在一起很多第一次接触这个组合的人会有一个疑问既然OSQP就能求解二次规划为什么还要Eigen还要osqp-eigen直接调OSQP的C接口不行吗这个问题问得非常好理解了三者之间的分工你安装的时候就会明白为什么顺序不能乱。1.1 Eigen头文件即库的线性代数模板库Eigen是一个纯头文件的C模板库核心功能就是线性代数计算矩阵运算、向量运算、矩阵分解、特征值求解、稀疏矩阵操作等等。它的关键特性是header-only不需要单独编译一个so库出来代码里面#include Eigen/Dense就能直接用这也是它能在C科学计算领域这么流行的原因之一。性能方面Eigen使用了模板表达式和惰性求值机制代码写起来直观的同时编译器会在编译期把表达式优化成高效的循环指令实际效率和手写的BLAS级别代码差距不大。不过需要留意一点Eigen并不是“安装”一个可执行的东西它的安装本质上是把头文件放到系统include路径下让编译器能找到。这个理解很重要后面很多CMake配置问题其实就是路径问题。1.2 OSQP专门为凸二次规划设计的C语言求解器OSQPOperator Splitting Quadratic Program是一个基于ADMM交替方向乘子法的一阶求解器专门用来求解带线性约束的凸二次规划问题。它用纯C语言实现轻量、可嵌入式部署适合求解大规模稀疏问题。拿最简单的二次规划问题来说$$ \min_x \quad \frac{1}{2}x^T P x q^T x $$约束条件为 $ l \le Ax \le u $其中P是半正定矩阵。OSQP就是解决这类问题的。它采用稀疏矩阵存储格式CSC和Eigen配合的时候需要把Eigen矩阵转成稀疏格式再喂进去这也是osqp-eigen这个封装库存在的核心原因之一。相比内点法这类二阶方法OSQP这种一阶方法在求解大规模、低精度要求的优化问题时有明显速度优势非常适合MPC这种每一拍都要在线求解的实时控制场景。1.3 osqp-eigen把前两者粘起来的C封装osqp-eigen是一个第三方开源库它做的事情非常直接给OSQP的C API包了一层C的类接口同时让数据输入输出可以直接用Eigen的Matrix和Vector类型避免开发者手写稀疏矩阵转换和数据拷贝的代码。比如用原生OSQP的C接口你需要先定义CSC格式的稀疏矩阵把非零元素、列指针、行索引一个个填好再调用osqp_setup函数去配置求解器流程繁琐而且非常容易在索引计算上出边界错误。而用osqp-eigen只需要构造Eigen矩阵然后传给对应的set方法代码逻辑清晰很多。所以这个安装组合的完整链路就是Eigen提供矩阵数据结构 → osqp-eigen把Eigen数据转成OSQP需要的格式 → OSQP完成数值求解再把结果传回来。这也是为什么安装顺序通常是Eigen → OSQP → osqp-eigen。2. 装之前的环境检查与版本选择老实说很多人安装失败不是因为操作步骤不对而是环境本身有问题。系统里gcc版本太老、CMake版本不够、之前装过旧版本库导致路径残留这些问题都会让安装过程变得莫名其妙。所以先花几分钟做一遍环境检查能省掉后面几个小时的头疼。2.1 开发工具链检查我以Ubuntu 20.04/22.04和部分国产Linux发行版为例先确认系统里有没有gcc、g、make和cmakegcc --version g --version cmake --version make --version如果提示找不到命令执行sudo apt update sudo apt install -y build-essential cmake git有几个点值得专门提醒一下。第一Eigen 3.4版本要求编译器支持C17如果你的项目或者系统编译器比较老建议先确认一下版本但绝大多数现代Linux发行版自带gcc 7以上的版本问题不大。第二CMake版本至少要在3.13以上否则很多现代find_package配置检测不到尤其是OSQP和osqp-eigen的CMake config文件。如果检查发现cmake版本过低可以走源码编译安装新版本或者用pip装一个cmake包拉高版本。2.2 版本怎么挑为什么不要盲目追新版本选型这块我吃过亏。有一段时间为了用最新特性直接拉了osqp-eigen的master分支结果发现它要求的OSQP版本和我本机装的0.6.0对不上编译期报了一堆缺符号的链接错误后来排查半天才搞清楚是版本不匹配。我目前稳定使用的组合是库推荐版本备注Eigen3.4.0稳定版支持C11/C17OSQP0.6.30.6.x系列的最后一个稳定版osqp-eigen0.8.0配合OSQP 0.6.3完全兼容不要盲目追master分支的新版本尤其是OSQP和osqp-eigen这种底层数值库API变化可能带来不小的迁移成本。另外还要注意如果系统之前用apt装过libosqp-dev或者libeigen3-dev后面再源码安装新版时容易产生冲突这个在第七章会专门讲。3. Eigen安装Header-only也有讲究Eigen虽然安装起来最“轻”但它反而是很多初学者第一次卡住的地方。因为header-only库的特殊性很多人搞不清楚到底怎么才算“装好了”。下面两种方法我都用过各有适用场景。3.1 最省事的apt安装法与隐藏坑对于Ubuntu或基于Debian的发行版用系统包管理器安装是最快的方式sudo apt update sudo apt install -y libeigen3-dev装完之后头文件位于/usr/include/eigen3。这里就有一个很经典的坑你在代码里写#include Eigen/Dense编译器默认搜索的路径是/usr/include而Eigen实际路径是/usr/include/eigen3/Eigen所以直接编译会报找不到头文件。解决办法有两个。第一种最简单粗暴做一个软链接把Eigen目录链接到系统include路径下sudo ln -s /usr/include/eigen3/Eigen /usr/include/Eigen这样代码里就能直接#include Eigen/Dense了。第二种是在CMakeLists.txt里通过find_package找到Eigen的位置find_package(Eigen3 REQUIRED NO_MODULE) target_link_libraries(your_target PRIVATE Eigen3::Eigen)Eigen3::Eigen这个target是CMake的config模式提供的用起来会自动带上头文件搜索路径不需要你手动写include_directories。但如果你的CMake版本太低或者没找到config文件也可能报Eigen3_DIR-NOTFOUND的错误这时可以手动指定路径cmake -DEigen3_DIR/usr/lib/cmake/eigen3 ..3.2 源码安装一劳永逸的完整路径apt的版本可能滞后如果需要指定版本或者机器是离线环境那源码安装更合适。从官网或GitHub拉源码git clone https://gitlab.com/libeigen/eigen.git cd eigen mkdir build cd build cmake .. sudo make install注意Eigen是header-only库cmake和make install其实不会编译太多东西主要工作是生成安装配置信息把一堆.h头文件拷贝到系统目录。默认安装结果头文件在/usr/local/include/eigen3CMake配置文件在/usr/local/share/eigen3/cmake这里同样存在include路径嵌套的问题。源码安装后Eigen/Dense的查找路径其实是不存在的因为Eigen变体头文件在/usr/local/include/eigen3/Eigen。我建议也做一个软链接sudo ln -s /usr/local/include/eigen3/Eigen /usr/local/include/Eigen如果你用的不是Ubuntu也没关系这个逻辑是通用的只要你最终能让编译器在默认搜索路径下找到名为Eigen的目录并且这个目录里面有Dense、Core、Geometry等头文件Eigen就算是装好了。这也是为什么有些人下载Eigen压缩包后只是解压到某个目录、然后手动指定-I参数也能正常使用的底层原因。3.3 验证Eigen是否可用装完之后写个最简单的测试程序确认头文件路径和编译环境都没问题#include Eigen/Dense #include iostream int main() { Eigen::Matrix3d mat Eigen::Matrix3d::Identity(); std::cout Eigen test matrix:\n mat std::endl; return 0; }用g直接编译g -I/usr/local/include/eigen3 test_eigen.cpp -o test_eigen ./test_eigen如果输出一个3x3的单位矩阵说明Eigen已经可以正常使用了。编译命令里-I参数的位置取决于你实际安装的头文件根路径如果前面做了软链接这句话可以简化为g test_eigen.cpp -o test_eigen。4. OSQP安装真正需要编译的C求解器OSQP的安装比Eigen略麻烦因为它是一个真正的C库需要编译生成libosqp.so动态库和若干头文件。但整个流程其实并不复杂核心是把编译选项理清楚。4.1 源码编译安装完整步骤我推荐直接从GitHub克隆源码编译这样版本可控也方便排查问题git clone --recursive https://github.com/osqp/osqp.git cd osqp mkdir build cd build cmake -G Unix Makefiles -DCMAKE_BUILD_TYPERelease .. sudo cmake --build . --target install执行完install之后OSQP的库文件会安装到默认路径/usr/local/lib头文件在/usr/local/include/osqp同时会生成一份CMake config文件通常在/usr/local/lib/cmake/osqp这是后面osqp-eigen能通过find_package(osqp)找到它的关键。这里有个细节cmake --build . --target install等价于make sudo make install只不过在CMake的新工作流里更通用尤其当生成器不是Makefile时也能正常工作。4.2 安装后验证与链接配置验证OSQP是否可用最直接的方法是在编译期和运行期各测一道。先写个简单的C程序测试#include osqp.h #include stdio.h int main() { printf(OSQP version: %d.%d.%d\n, OSQP_VERSION_MAJOR, OSQP_VERSION_MINOR, OSQP_VERSION_BUGFIX); return 0; }编译时指定头文件和库路径gcc test_osqp.c -I/usr/local/include/osqp -L/usr/local/lib -losqp -o test_osqp编译通过后运行如果提示找不到libosqp.so说明动态链接库路径没配置。Linux默认搜索/usr/lib、/lib这些目录而/usr/local/lib不一定会被搜索。解决办法是执行一次ldconfig或者手动把路径加进去echo /usr/local/lib | sudo tee /etc/ld.so.conf.d/osqp.conf sudo ldconfig4.3 关于OSQP的编译选项和浮点精度OSQP在CMake配置阶段有一些选项需要关注。默认情况下CMAKE_BUILD_TYPERelease会加上优化选项求解性能更好。如果你的应用场景需要单精度浮点比如嵌入式环境可以在cmake时加cmake -DDFLOATON ..双精度和单精度的选择会影响数值求解的精度和速度嵌入式平台上单精度能省一半内存带宽但收敛精度会差一些。如果你只是做桌面端的MPC仿真用默认双精度就好别折腾这个选项。另外OSQP可以启用qdldl内置的LDLT分解模块默认是开启的它比早期版本依赖外部的SuiteSparse要省事很多。我遇到过有人在旧教程里看到要安装libsuitesparse-dev于是在新版本的OSQP里也这么搞结果白白装了一堆用不上的系统依赖。这一点新版本已经做了集成不需要额外装了。5. osqp-eigen安装把接口做得像Eigen一样丝滑osqp-eigen是这套环境里最晚装的一个因为它依赖前两个库都可用。安装过程中最常出现的问题就是CMake找不到OSQP路径所以要重点理解它的CMake查找机制。5.1 为什么需要这个第三方封装我直接说结论用原生OSQP的C接口写MPC求解器不是不能写但非常痛苦。你需要在代码里手动维护CSC稀疏矩阵的三元组数组values, row_indices, col_indices每次约束矩阵发生变化还要自己重新构建整个稀疏结构出了bug还特别难查。osqp-eigen把这些底层数据结构完全封装起来你只需要把数据按Eigen的方式组织好调用对应的set方法设置进去就行。这个封装带来的可读性提升非常明显实际项目里省下的开发时间远超安装它花掉的时间。5.2 编译安装步骤和路径传递细节源码编译git clone https://github.comrobotology/osqp-eigen.git cd osqp-eigen mkdir build cd build cmake .. sudo make install在cmake配置阶段起作用的几个关键查找路径包括Eigen3_DIR和osqp_DIR。如果前面Eigen和OSQP都装到默认路径一般CMake能自动找到。但如果你的系统里有多个版本或者安装路径不是标准路径就需要显式指定。我踩过的一个坑是OSQP装到了/usr/local但它生成的cmake配置文件在/usr/local/lib/cmake/osqp部分CMake版本在find_package(osqp)时不会自动去这个目录下找需要在cmake命令里手动指定cmake -Dosqp_DIR/usr/local/lib/cmake/osqp ..osqp-eigen本身还依赖find_package(Eigen3)如果报错找不到Eigen同样手动指定-DEigen3_DIR路径即可。5.3 验证安装跑通自带示例osqp-eigen仓库里带了示例程序编译安装完成后可以用仓库自带的demo验证整体环境是否工作正常cd osqp-eigen mkdir -p build_example cd build_example cmake ../examples make ./mpc_example这里的mpc_example是一个非常简化的MPC示例代码里会演示OsqpEigen::Solver的基本使用流程。如果能跑出结果说明Eigen、OSQP、osqp-eigen三个库已经形成了完整的工具链可以进入实际项目开发了。6. 一个完整的最小工程三个库协同求解二次规划安装完成后我强烈建议自己从零写一个最小的工程亲手把三个库串起来。这一步不仅是验证安装正确也是理解整个数据流的关键。下面这个例子我精简了项目里常见的MPC代码保留核心逻辑方便你直接照着跑通。6.1 项目结构minimal_qp_demo/ ├── CMakeLists.txt └── main.cpp6.2 CMakeLists.txt逐行拆解cmake_minimum_required(VERSION 3.13) project(MinimalQPDemo) set(CMAKE_CXX_STANDARD 17) set(CMAKE_BUILD_TYPE Release) find_package(Eigen3 REQUIRED) find_package(osqp REQUIRED) find_package(osqp_eigen REQUIRED) add_executable(minimal_qp_demo main.cpp) target_link_libraries(minimal_qp_demo PRIVATE Eigen3::Eigen osqp::osqp osqp_eigen::osqp_eigen )这里有几个地方值得展开说。find_package(osqp REQUIRED)查找的其实是OSQP安装时生成的osqpConfig.cmake它定义了一个名为osqp::osqp的导入目标链接这个目标会自动带上头文件路径和库文件路径不需要手动写include_directories和target_link_libraries的路径参数。osqp_eigen::osqp_eigen同理。如果你的系统提示找不到osqp_eigen大部分原因是osqp-eigen安装时没有把CMake config文件放到CMake默认搜索路径下可以使用cmake -DCMAKE_PREFIX_PATH/usr/local ..把/usr/local加入CMake的查找前缀。另外说明一下这里OSQP是C库但CMake的链接器会自动处理C和C的混合链接不需要特殊设置唯一注意的是可执行文件需要链接标准C库CMake在C项目里默认会处理。6.3 求解器代码与API说明下面用osqp-eigen求解一个二维二次规划问题$$ \min \quad 0.5 x^T \begin{bmatrix} 4 1 \ 1 2 \end{bmatrix} x \begin{bmatrix} 1 \ 1 \end{bmatrix}^T x $$约束为 $ -1 \le x_i \le 1 $同时加上 $ x_1 x_2 0.5 $ 这个等式约束。#include Eigen/Dense #include Eigen/Sparse #include OsqpEigen/OsqpEigen.h #include iostream int main() { // 1. 定义目标函数的二次项系数矩阵P Hessian Eigen::MatrixXd P(2, 2); P 4, 1, 1, 2; // 2. 定义目标函数的一次项系数向量q Eigen::VectorXd q(2); q 1, 1; // 3. 定义线性约束矩阵A行数是约束个数 Eigen::MatrixXd A(3, 2); A 1, 0, 0, 1, 1, 1; // 4. 定义约束上下界 (第一行对应 -1 x1 1, 第三行对应 x1x20.5) Eigen::VectorXd lower_bound(3); Eigen::VectorXd upper_bound(3); lower_bound -1, -1, 0.5; upper_bound 1, 1, 0.5; // 5. 创建求解器对象 OsqpEigen::Solver solver; // 6. 配置问题基本参数 solver.settings()-setVerbosity(true); solver.settings()-setWarmStart(true); // 7. 设置问题维度 solver.data()-setNumberOfVariables(2); solver.data()-setNumberOfConstraints(3); // 8. 将Eigen稠密矩阵转为稀疏矩阵然后传给求解器 Eigen::SparseMatrixdouble P_sparse P.sparseView(); Eigen::SparseMatrixdouble A_sparse A.sparseView(); if (!solver.data()-setHessianMatrix(P_sparse)) { std::cerr Failed to set Hessian matrix std::endl; return 1; } if (!solver.data()-setGradient(q)) { std::cerr Failed to set gradient std::endl; return 1; } if (!solver.data()-setLinearConstraintsMatrix(A_sparse)) { std::cerr Failed to set constraint matrix std::endl; return 1; } if (!solver.data()-setLowerBound(lower_bound)) { std::cerr Failed to set lower bound std::endl; return 1; } if (!solver.data()-setUpperBound(upper_bound)) { std::cerr Failed to set upper bound std::endl; return 1; } // 9. 初始化并求解 if (!solver.initSolver()) { std::cerr Solver initialization failed std::endl; return 1; } if (!solver.solve()) { std::cerr Solver solve failed std::endl; return 1; } Eigen::VectorXd solution solver.getSolution(); std::cout Optimal solution: solution.transpose() std::endl; return 0; }这段代码我把每一步的注释都写清楚了有几个点需要特别说明。setHessianMatrix和setLinearConstraintsMatrix接收的是Eigen::SparseMatrix类型这就是为什么需要调用sparseView()把稠密矩阵转成稀疏矩阵。在真正的MPC项目里Hessian矩阵P通常是对角块结构A矩阵是带状的用稀疏矩阵存储能大幅减少内存占用和求解时间。setLowerBound和setUpperBound接收的是稠密VectorXd因为约束边界本质上是稠密向量不需要用稀疏结构。特别提醒solver.data()-setNumberOfVariables()和setNumberOfConstraints()必须在设置具体矩阵值之前调用否则后面的set方法大概率会失败。这个顺序问题在osqp-eigen的文档里没有特意强调我自己第一次用的时候在这个地方卡了好久。求解完成之后getSolution()返回的向量就是最优解。代码里如果输出[0.08333, 0.41667]这个数值说明你的整个环境已经完全跑通了。6.4 编译运行与结果验证在你项目目录下执行mkdir build cd build cmake .. make ./minimal_qp_demo如果一切正常你会看到类似这样的输出具体迭代信息取决于求解过程Optimal solution: 0.08333 0.41667这个结果可以用数学验证把拉格朗日乘子法手算一遍也能得到同样的值说明求解器工作正常。从这里开始你就可以把这三块工具应用到自己的优化任务里去了。7. 常见问题与排查技巧实录最后这部分我整理了自己和身边人安装这套库时最常遇到的几个问题按“症状 → 原因 → 解法”的方式列出来建议直接收藏下次遇到问题对照着查。7.1 速查表编译找不到文件/链接不到库报错表现常见原因解决方案fatal error: Eigen/Dense: No such file or directoryEigen头文件路径不在默认搜索目录中使用-I手动指定或确认/usr/include/eigen3下存在Eigen目录做软链接CMake Error: Could not find a package configuration file named Eigen3没安装Eigen或CMake搜索路径不对安装libeigen3-dev或cmake -DEigen3_DIR/usr/lib/cmake/eigen3 ..CMake Error: Could not find a package configuration file named osqpOSQP没有生成cmake config或路径不在默认搜索范围确认OSQP安装成功cmake -Dosqp_DIR/usr/local/lib/cmake/osqp ..cannot find -losqp链接器找不到OSQP库文件确认/usr/local/lib下存在libosqp.so检查链接路径error while loading shared libraries: libosqp.so: cannot open shared object file运行时动态库路径未配置echo /usr/local/lib | sudo tee /etc/ld.so.conf.d/osqp.conf sudo ldconfig编译时报错OSQP_DEBUG未定义或找不到osqp/constants.hOSQP头文件路径没配好检查CMake中osqp链接的target是否正确或手动指定include路径7.2 多版本库冲突与卸载残留这个问题在长期开发的机器上特别容易遇到。比如你之前用apt装了libosqp-dev后来又源码编译安装了新版OSQP两个版本的头文件和库文件会同时存在于系统不同位置CMake查找时可能随机选一个编译倒是能过但运行时因为动态库版本不符直接崩溃而且崩溃位置在数值计算内部非常难定位。我的建议是尽量保持一种安装方式贯穿始终。如果确实需要切换先彻底卸载旧版本sudo apt remove --purge libosqp-dev sudo rm -rf /usr/local/include/osqp sudo rm -f /usr/local/lib/libosqp.so* /usr/local/lib/cmake/osqpEigen也一样apt和源码版本不要混用。混用最典型的症状是代码里同时存在两套Eigen一个通过eigen3/Eigen/Dense找到旧版本另一个通过Eigen/Dense找到新版本两个版本的矩阵类型在ABI上不兼容编译器有时不会报错但运行时内存布局对不上结果就是莫名其妙的数据错乱这类问题排查起来特别痛苦。7.3 跨平台与离线安装补充如果你的目标是嵌入式平台比如ARM开发板或者某些轻量Linux发行版有几个额外要注意的地方。交叉编译时需要在CMake命令中指定工具链文件例如cmake -DCMAKE_TOOLCHAIN_FILE/path/to/your/toolchain.cmake ..因为OSQP和osqp-eigen都是纯C/C代码没有平台相关的依赖交叉编译一般很顺利重点检查编译器是否支持C17即可。Eigen更是header-only库天然支持跨平台只要头文件路径正确连编译都不用。离线环境建议提前下载好源码包最好把--recursive也需要拉取的子模块一并下载。比如OSQP用--recursive是因为早期版本依赖qdldl子模块如果只clone主仓库不拉子模块编译时会报找不到qdldl.h或者qdldl.c的错。离线下载可以用GitHub的Download ZIP功能但子模块需要单独下载并手动放到对应目录稍微麻烦一些建议如果条件允许还是在联网环境下先完整clone一遍。国产Linux发行版比如统信UOS、麒麟等的安装步骤和Ubuntu基本一致最大的差异在于它们的软件源里可能没有libeigen3-dev和libosqp-dev这两个包所以更推荐走源码编译这条通用路径。另外这些系统的默认CMake版本可能偏低如果find_package命令返回的是找不到package config而不是找到了但路径不对建议先查一下cmake版本低于3.13的话优先升级cmake再排查其他问题。我个人在实际操作中的一个体会是装这套环境最核心的问题不是“敲什么命令”而是“搞清楚每个库的头文件和库文件到底被安装到了哪里以及CMake是如何查找它们的”。只要把这两个底层逻辑弄透无论换成什么发行版、什么编译环境你都能在三分钟之内定位问题。最后再分享一个小技巧如果你经常在多个项目里用这套库可以把这三个库的头文件和CMake config文件路径写进一个环境变量脚本做到“一次安装、处处复用”省去每次都要配置路径的麻烦。但要注意这样做的前提还是你搞清楚了每个路径到底指向哪个版本千万别让环境变量把CMake引到旧版本的坑里去。