一個輕量級僅頭文件的 C++17 庫,提供針對(無)約束非線性函數及表達式模板的數值優化方法
https://github.com/PatWie/CppNumericalSolvers
CppNumericalSolvers 庫?include
?目錄下的文件及其功能說明
根目錄文件
文件名 | 功能說明 |
---|---|
function.h | (主函數頭文件) ?定義目標函數的核心頭文件。它通常包含了其他? |
function_base.h | (函數基類) ?定義了所有目標函數的基類接口,如? |
function_expressions.h | (函數表達式) ?實現了強大的函數表達式模板功能。它允許你通過運算符重載(如? |
function_penalty.h | (罰函數) ?提供了用于約束優化的罰函數(Penalty Method)相關實現。這是一種將約束問題轉化為無約束問題的方法。 |
function_problem.h | (優化問題定義) ?定義了一個完整的優化問題結構,特別是對于有約束問題,它將目標函數和一系列約束函數(等式和不等式)封裝在一起。 |
linesearch
?目錄 (線搜索算法)
這個目錄包含了用于在一維方向上尋找最優步長的算法。
文件名 | 功能說明 |
---|---|
armijo.h | (Armijo 線搜索) ?實現了 Armijo 回溯線搜索算法,一種簡單而常用的非精確線搜索方法,用于確保每一步迭代都能充分減小目標函數值。 |
more_thuente.h | (Moré-Thuente 線搜索) ?實現了 Moré-Thuente 線搜索算法,這是一種更復雜的線搜索方法,試圖同時滿足 Wolfe 條件,通常能提供更好的收斂性。 |
solver
?目錄 (求解器)
這個目錄包含了庫中實現的核心優化算法。
文件名 | 功能說明 |
---|---|
augmented_lagrangian.h | (增廣拉格朗日法) ?實現了增廣拉格朗日求解器,這是一種強大的處理帶等式和不等式約束優化問題的方法。 |
bfgs.h | (BFGS 求解器) ?實現了 BFGS (Broyden–Fletcher–Goldfarb–Shanno) 擬牛頓法求解器,一種非常流行和高效的無約束優化算法。 |
conjugated_gradient_descent.h | (共軛梯度下降法) ?實現了共軛梯度法求解器,適用于大規模無約束優化問題,因為它不需要存儲海森矩陣。 |
gradient_descent.h | (梯度下降法) ?實現了基本的梯度下降法求解器,沿著梯度的反方向進行迭代。 |
lbfgs.h | (L-BFGS 求解器) ?實現了 L-BFGS (有限內存 BFGS) 求解器,是 BFGS 的內存優化版本,特別適用于變量維度非常高的問題。 |
lbfgsb.h | (L-BFGS-B 求解器) ?實現了 L-BFGS-B 求解器,用于處理帶箱形約束(Bound-constrained)的優化問題,即變量有上下界限制。 |
nelder_mead.h | (Nelder-Mead 求解器) ?實現了 Nelder-Mead 單純形法,這是一種直接搜索方法,不需要計算梯度,適用于目標函數不可微或計算梯度困難的情況。 |
newton_descent.h | (牛頓法) ?實現了牛頓下降法求解器,它使用目標函數的二階導數(海森矩陣),收斂速度快但計算成本較高。 |
progress.h | (進度監控) ?提供了用于監控優化過程的工具,例如 README 中提到的? |
solver.h | (求解器基類) ?定義了所有求解器的通用接口和基類,規定了求解器的基本行為,如? |
utils
?目錄 (工具)
這個目錄包含了一些輔助功能和工具。
文件名 | 功能說明 |
---|---|
derivatives.h | (導數工具) ?提供了數值計算導數的工具,主要通過有限差分法來近似計算梯度和海森矩陣。這對于驗證用戶提供的解析導數的正確性非常有用。 |
示例代碼解析
1. constrained_simple.cc
這份代碼是一個使用?CppNumericalSolvers
?庫解決帶有一個等式約束和一個不等式約束的優化問題的完整示例。它演示了如何定義目標函數、約束函數,如何將它們組合成一個約束問題,以及如何使用?AugmentedLagrangian
?(增廣拉格朗日) 求解器來找到最優解。
// Copyright 2025, https://github.com/PatWie/CppNumericalSolvers
// 版權所有 2025, https://github.com/PatWie/CppNumericalSolvers
#include<iostream>// 引入標準輸入輸出流庫,用于打印結果#include"cppoptlib/function.h"// 引入庫中定義函數所需的核心頭文件
#include"cppoptlib/solver/augmented_lagrangian.h"// 引入增廣拉格朗日求解器
#include"cppoptlib/solver/lbfgs.h"// 引入 L-BFGS 求解器,用作增廣拉格朗日法的內層求解器// Define a 2D function type with first-order differentiability.
// 定義一個二維、一階可微的函數類型別名。
// 這簡化了后面定義函數類的代碼。
template<classF>
using?Function2d?=?cppoptlib::function::FunctionCRTP<F,double,?cppoptlib::function::DifferentiabilityMode::First,2>;// Alias for an "any function" with the above properties.
// 為具有上述屬性的“任意函數”(通過類型擦除)創建別名。
// 這在將具體函數存入通用容器或作為參數傳遞時很有用。
using?FunctionExpr2d?=?cppoptlib::function::FunctionExpr<
double,?cppoptlib::function::DifferentiabilityMode::First,2>;//
// QuadraticObjective2: f(x) = (x[0]-1)^2 + (x[1]-2)^2
// 二次目標函數2:f(x) = (x[0]-1)^2 + (x[1]-2)^2
//
// The unconstrained optimum is (1,2) with f(x)=0.
// 無約束最優解是 (1,2),此時 f(x)=0。
// However, the constraints (below) force a different solution.
// 然而,下面的約束會強制得到一個不同的解。
//
classQuadraticObjective2:publicFunction2d<QuadraticObjective2>{
public:EIGEN_MAKE_ALIGNED_OPERATOR_NEW?// Eigen 內存對齊宏// 重載 operator(),實現函數求值和梯度計算ScalarType?operator()(const?VectorType?&x,VectorType?*gradient?=nullptr)const{
if(gradient){// 如果 gradient 指針非空,則計算梯度
// Gradient: 2*(x - [1,2])
// 梯度公式:2*(x - [1,2])VectorType?ref(2);ref?<<1,2;
*gradient?=2*(x?-?ref);
}
// 返回函數值
return(x(0)-1)*(x(0)-1)+(x(1)-2)*(x(1)-2);
}
};//
// EqualityConstraint2: g(x) = x[0] - 0.5 = 0
// 等式約束2:g(x) = x[0] - 0.5 = 0
//
// This forces x[0] to be exactly 0.5.
// 這強制 x[0] 必須等于 0.5。
// Thus, the optimal solution must have x[0] = 0.5.
// 因此,最優解必須滿足 x[0] = 0.5。
//
classEqualityConstraint2:publicFunction2d<EqualityConstraint2>{
public:EIGEN_MAKE_ALIGNED_OPERATOR_NEW// 實現約束函數的求值和梯度計算ScalarType?operator()(const?VectorType?&x,VectorType?*gradient?=nullptr)const{
if(gradient){// 如果請求梯度
// Gradient is [1, 0].
// 梯度是 [1, 0]。
*gradient?=(VectorType(2)<<1,0).finished();
}
// 返回約束函數的值
returnx(0)-0.5;
}
};//
// InequalityConstraint3: h(x) = 2 - (x[0] + x[1]) >= 0
// 不等式約束3:h(x) = 2 - (x[0] + x[1]) >= 0
//
// This requires x[0] + x[1] <= 2. With x[0]=0.5 from the equality constraint,
// we have x[1] <= 1.5.
// 這要求 x[0] + x[1] <= 2。結合等式約束 x[0]=0.5,我們得到 x[1] <= 1.5。
//
classInequalityConstraint3:publicFunction2d<InequalityConstraint3>{
public:EIGEN_MAKE_ALIGNED_OPERATOR_NEW// 實現約束函數的求值和梯度計算ScalarType?operator()(const?VectorType?&x,VectorType?*gradient?=nullptr)const{
if(gradient){// 如果請求梯度
// Gradient is [-1, -1].
// 梯度是 [-1, -1]。
*gradient?=(VectorType(2)<<-1,-1).finished();
}
// 返回約束函數的值
return2-(x(0)+x(1));
}
};//
// Main demo: Solve the constrained problem with multiple constraints
// 主演示程序:解決帶有多個約束的約束問題
//
intmain(){
// Initial guess for x. We choose a starting point that is not optimal.
// x 的初始猜測值。我們選擇一個非最優的起始點。QuadraticObjective2::VectorType?x(2);x?<<1,1;// Define the objective function.
// 定義目標函數。
// 將具體的目標函數類包裝成通用的 FunctionExpr 類型。cppoptlib::function::FunctionExpr objective?=QuadraticObjective2();// Define the constraint functions.
// 定義約束函數。
// 同樣將具體的約束函數類包裝成通用的 FunctionExpr 類型。cppoptlib::function::FunctionExpr eq?=EqualityConstraint2();cppoptlib::function::FunctionExpr ineq?=InequalityConstraint3();// Construct the constrained optimization problem.
// 構造約束優化問題。
//
// Problem Statement:
// 問題描述:
// ? minimize f(x) = (x[0]-1)^2 + (x[1]-2)^2
// ? 最小化 f(x) = (x[0]-1)^2 + (x[1]-2)^2
// ? subject to:
// ? 約束條件:
// ? ? equality constraint: ?x[0] - 0.5 == 0 ? (forces x[0]=0.5)
// ? ? 等式約束:x[0] - 0.5 == 0 ? (強制 x[0]=0.5)
// ? ? inequality constraint: 2 - (x[0]+x[1]) >= 0 ?(forces x[0]+x[1] <= 2)
// ? ? 不等式約束:2 - (x[0]+x[1]) >= 0 ?(強制 x[0]+x[1] <= 2)
//
// Expected Outcome:
// 預期結果:
// ? The unconstrained optimum is (1,2) with f(x)=0, but it is infeasible
// ? because 1+2=3 > 2. With x[0] forced to 0.5, the inequality requires x[1]
// ? <= 1.5. The best feasible choice is x = (0.5, 1.5), giving:
// ? 無約束最優解是 (1,2),f(x)=0,但這個解是不可行的,
// ? 因為 1+2=3 > 2。在 x[0] 被強制為 0.5 的情況下,不等式要求 x[1] <= 1.5。
// ? 最佳的可行選擇是 x = (0.5, 1.5),此時:
// ? ? ? f(x) = (0.5-1)^2 + (1.5-2)^2 = 0.25 + 0.25 = 0.5.
//
// 將目標函數、等式約束列表和不等式約束列表打包成一個 ConstrainedOptimizationProblem 對象。cppoptlib::function::ConstrainedOptimizationProblem?prob(objective,
/* equality constraints */{eq},// 等式約束列表
/* inequality constraints */{ineq});// 不等式約束列表// Set up an inner LBFGS solver for unconstrained subproblems.
// 設置一個內層的 L-BFGS 求解器,用于解決無約束子問題。cppoptlib::solver::Lbfgs<FunctionExpr2d>?unconstrained_solver;// Create the augmented Lagrangian solver that handles the constraints.
// 創建處理約束的增廣拉格朗日求解器。
// 它接收約束問題 `prob` 和一個無約束求解器 `unconstrained_solver`作為參數。cppoptlib::solver::AugmentedLagrangian?solver(prob,?unconstrained_solver);// Initialize the augmented Lagrange state.
// 初始化增廣拉格朗日的狀態。
// 參數:初始點x,等式約束數量(1),不等式約束數量(1),初始懲罰因子(1.0)cppoptlib::solver::AugmentedLagrangeState<double,2>l_state(x,1,1,1.0);// Run the solver.
// 運行求解器。
// `Minimize` 函數返回一個包含最終解和求解器狀態的元組。
auto[solution,?solver_state]=?solver.Minimize(prob,?l_state);// Output the results.
// 輸出結果。std::cout?<<"Optimal f(x): "<<objective(solution.x)<<?std::endl;std::cout?<<"Optimal x: "<<?solution.x.transpose()<<?std::endl;std::cout?<<"Iterations: "<<?solver_state.num_iterations?<<?std::endl;std::cout?<<"Solver status: "<<?solver_state.status?<<?std::endl;// Expected Output:
// 預期輸出:
// ? Optimal x should be close to (0.5, 1.5).
// ? 最優解 x 應該接近 (0.5, 1.5)。
// ? The optimal function value f(x) should be approximately 0.5.
// ? 最優函數值 f(x) 應該約等于 0.5。
// ? Both constraints are active: x[0]=0.5 (equality) and x[0]+x[1]=2
// ? (inequality).
// ? 兩個約束都是激活的:x[0]=0.5 (等式) 和 x[0]+x[1]=2 (不等式)。
return0;
}
2. ?constrained_simple2.cc
這份代碼是使用?CppNumericalSolvers
?庫解決另一個約束優化問題的示例。這個例子非常經典:在一個圓形區域上,尋找一個點,使得該點坐標的和最小。它巧妙地展示了如何使用同一個基礎函數(Circle
)來構造等式和不等式約束。
// Copyright 2025, https://github.com/PatWie/CppNumericalSolvers
// 版權所有 2025, https://github.com/PatWie/CppNumericalSolvers
#include<iostream>// 引入標準輸入輸出流庫,用于打印結果#include"cppoptlib/function.h"// 引入庫中定義函數所需的核心頭文件
#include"cppoptlib/solver/augmented_lagrangian.h"// 引入增廣拉格朗日求解器
#include"cppoptlib/solver/lbfgs.h"// 引入 L-BFGS 求解器//
// SumObjective: f(x) = x[0] + x[1] (to be minimized)
// 求和目標函數:f(x) = x[0] + x[1] (待最小化)
//
classSumObjective:public?cppoptlib::function::FunctionXd<SumObjective>{
public:EIGEN_MAKE_ALIGNED_OPERATOR_NEW?// Eigen 內存對齊宏// Return the sum of the components.
// 返回各分量之和。ScalarType?operator()(const?VectorType?&x,VectorType?*gradient?=nullptr)const{
if(gradient){// 如果 gradient 指針非空,則計算梯度
// The gradient is a vector of ones.
// 梯度是一個全為 1 的向量。
*gradient?=VectorType::Ones(2);
}
// 返回 x 各分量之和
return?x.sum();
}
};//
// Circle: c(x) = x[0]^2 + x[1]^2
// 圓函數:c(x) = x[0]^2 + x[1]^2
// This function will be used to form both an equality constraint (forcing
// the solution to lie on the circle) and an inequality constraint (ensuring
// the solution remains within the circle).
// 這個函數將被用來構造一個等式約束(強制解落在圓上)和一個不等式約束(確保解保持在圓內)。
//
classCircle:public?cppoptlib::function::FunctionXd<Circle>{
public:EIGEN_MAKE_ALIGNED_OPERATOR_NEW// Compute the squared norm.
// 計算平方范數。ScalarType?operator()(const?VectorType?&x,VectorType?*gradient?=nullptr)const{
if(gradient){// 如果請求梯度
// 梯度是 2*x
*gradient?=2*?x;
}
// 返回 x 的平方范數 (x[0]^2 + x[1]^2)
return?x.squaredNorm();
}
};//
// Main demo: solve the constrained problem
// 主演示程序:解決約束問題
//
intmain(){
// Initial guess for x.
// x 的初始猜測值。SumObjective::VectorType?x(2);x?<<2,10;// Define the objective: f(x) = x[0] + x[1].
// 定義目標函數:f(x) = x[0] + x[1]。
// 將具體的 SumObjective 類包裝成通用的 FunctionExpr 類型。cppoptlib::function::FunctionExpr objective?=SumObjective();// Define the circle function: c(x) = x[0]^2 + x[1]^2.
// 定義圓函數:c(x) = x[0]^2 + x[1]^2。
// 將具體的 Circle 類包裝成通用的 FunctionExpr 類型。cppoptlib::function::FunctionExpr circle?=Circle();// Build the constrained optimization problem.
// 構造約束優化問題。
// We impose two constraints:
// 我們施加兩個約束:
// 1. Equality constraint: circle(x) - 2 == 0, forcing the solution onto the
// circle's boundary.
// 1. 等式約束: circle(x) - 2 == 0,強制解落在圓的邊界上。
// 2. Inequality constraint: 2 - circle(x) >= 0, ensuring the solution remains
// inside the circle.
// 2. 不等式約束: 2 - circle(x) >= 0,確保解保持在圓的內部。
//
// 這里使用了表達式模板來動態地創建約束函數。
// `circle - 2` 創建了一個新的函數表達式,其值為 circle(x) - 2。
// `2 - circle` 創建了另一個新的函數表達式,其值為 2 - circle(x)。cppoptlib::function::ConstrainedOptimizationProblem?prob(objective,
/* equality constraints */
{cppoptlib::function::FunctionExpr(circle?-2)},// 等式約束列表
/* inequality constraints */
{cppoptlib::function::FunctionExpr(2-?circle)});// 不等式約束列表
// 注意:這個例子中的兩個約束實際上是冗余的。等式約束已經包含了不等式約束。
// 這里主要是為了演示如何同時處理兩種類型的約束。// Set up an inner solver (LBFGS) for the unconstrained subproblems.
// 設置一個內層的 L-BFGS 求解器,用于解決無約束子問題。cppoptlib::solver::Lbfgs<cppoptlib::function::FunctionExprXd>?inner_solver;// Create the augmented Lagrangian solver using the inner solver.
// 使用內層求解器創建增廣拉格朗日求解器。cppoptlib::solver::AugmentedLagrangian?solver(prob,?inner_solver);// Initialize the augmented Lagrange state.
// 初始化增廣拉格朗日的狀態。
// 參數:初始點x,等式約束數量(1),不等式約束數量(1),初始懲罰因子(1.0)cppoptlib::solver::AugmentedLagrangeState<double>l_state(x,1,1,1.0);// Run the solver.
// 運行求解器。
auto[solution,?solver_state]=?solver.Minimize(prob,?l_state);// Output the results.
// 輸出結果。std::cout?<<"Optimal f(x): "<<objective(solution.x)<<?std::endl;std::cout?<<"Optimal x: "<<?solution.x.transpose()<<?std::endl;std::cout?<<"Iterations: "<<?solver_state.num_iterations?<<?std::endl;std::cout?<<"Solver status: "<<?solver_state.status?<<?std::endl;// 預期結果:
// 為了最小化 x1+x2,同時滿足 x1^2+x2^2=2,
// 幾何上是在圓上找一條 y=-x+c 的直線,使其與圓相切且截距 c 最小。
// 這發生在圓的第三象限,即 x1=x2 的點。
// x1^2 + x1^2 = 2 ?=> 2*x1^2 = 2 => x1 = -1。
// 所以最優解是 (-1, -1),最優值是 -2。return0;
}
3.?debug.cc
這份代碼是一份全面的單元測試和功能演示,旨在展示?CppNumericalSolvers
?庫中與函數定義、函數表達式和約束問題構建相關的核心功能。它不進行任何實際的優化求解,而是側重于驗證以下幾點:
如何定義不同可微性等級(零階、一階、二階)的函數。
如何使用?
FunctionExpr
?類型擦除包裝器來統一處理這些不同類型的函數。如何使用重載的運算符(
+
,?-
,?*
)通過表達式模板來動態構建復雜的函數。如何使用庫提供的工具來構建約束問題的懲罰項和增廣拉格朗日函數。
#include<Eigen/Dense>// 引入 Eigen 密集矩陣和向量庫
#include<cmath>// 引入 C++ 標準數學庫
#include<iostream>// 引入標準輸入輸出流庫
#include<memory>// 引入智能指針
#include<stdexcept>// 引入標準異常
#include<type_traits>// 引入類型萃取工具#include"cppoptlib/function.h"// 引入庫中定義函數所需的核心頭文件usingnamespace?cppoptlib::function;// 使用 cppoptlib::function 命名空間// LinearFunction: supports first-order information.
// 線性函數:支持一階信息(梯度)。
// 繼承自 FunctionXd,這是一個預定義的別名,代表一階可微、double類型的函數。
structLinearFunction:publicFunctionXd<LinearFunction>{Eigen::VectorXd m;// 斜率向量
double?b;// 截距LinearFunction(const?Eigen::VectorXd?&m_,double?b_):m(m_),b(b_){}// Simplified operator(): only function value and gradient.
// 簡化的 operator():只提供函數值和梯度。
doubleoperator()(const?VectorType?&x,?VectorType?*grad?=nullptr)const{
double?fx?=?m.dot(x)+?b;// f(x) = m^T * x + b
if(grad){// 如果請求梯度
*grad?=?m;// 梯度就是 m
}
return?fx;
}
};// ConstantFunction: supports no derivative information.
// 常數函數:不支持導數信息。
// 直接繼承自 FunctionCRTP,并指定可微性模式為 None。
structConstantFunction
:public?cppoptlib::function::FunctionCRTP<ConstantFunction,double,DifferentiabilityMode::None>{
double?c;// 常數值
ConstantFunction(double?c_):c(c_){}// 最簡化的 operator():只接受 x,返回函數值。
doubleoperator()(const?VectorType?&x)const{
(void)x;// 明確表示 x 未被使用,避免編譯器警告。
return?c;
}
};// QuadraticFunction: supports second-order information.
// 二次函數:支持二階信息(梯度和海森矩陣)。
// 繼承自 FunctionCRTP,并指定可微性模式為 Second。
structQuadraticFunction
:public?cppoptlib::function::FunctionCRTP<QuadraticFunction,double,DifferentiabilityMode::Second>{Eigen::MatrixXd A;// 二次項矩陣Eigen::VectorXd b;// 線性項向量
double?c;// 常數項QuadraticFunction(const?Eigen::MatrixXd?&A_,const?Eigen::VectorXd?&b_,
double?c_)
:A(A_),b(b_),c(c_){}// 完整的 operator():提供函數值、梯度和海森矩陣。
doubleoperator()(const?VectorType?&x,?VectorType?*grad?=nullptr,MatrixType?*hess?=nullptr)const{
double?fx?=0.5*?x.transpose()*?A?*?x?+?b.dot(x)+?c;// f(x) = 0.5*x^T*A*x + b^T*x + c
if(grad){// 如果請求梯度
*grad?=?A?*?x?+?b;// 梯度是 A*x + b
}
if(hess){// 如果請求海森矩陣
*hess?=?A;// 海森矩陣就是 A
}
return?fx;
}
};
// 二次函數2,與上面類似,但使用靜態維度 (2D)
structQuadraticFunction2
:public?cppoptlib::function::FunctionCRTP<
QuadraticFunction2,double,?DifferentiabilityMode::Second,2>{Eigen::Matrix2d A;// 使用固定大小的 Eigen 類型Eigen::Vector2d b;
double?c;QuadraticFunction2(const?Eigen::Matrix2d?&A_,const?Eigen::Vector2d?&b_,
double?c_)
:A(A_),b(b_),c(c_){}doubleoperator()(const?VectorType?&x,?VectorType?*grad?=nullptr,MatrixType?*hess?=nullptr)const{
double?fx?=0.5*?x.transpose()*?A?*?x?+?b.dot(x)+?c;
if(grad){
*grad?=?A?*?x?+?b;
}
if(hess){
*hess?=?A;
}
return?fx;
}
};//-----------------------------------------------------------------
// Example Usage
// 示例用法
intmain(){
using?VectorType?=?Eigen::VectorXd;// 使用動態大小的向量類型
using?MatrixType?=?Eigen::MatrixXd;// 使用動態大小的矩陣類型// Test LinearFunction via FunctionExpr (First-order).
// 通過 FunctionExpr 測試線性函數 (一階)。
{VectorType?m(2);m?<<2,-1;
double?b_val?=0.5;LinearFunction?lin(m,?b_val);// 創建一個具體的線性函數對象FunctionExpr?anyLin(lin);// 將其包裝進類型擦除的 FunctionExpr 中VectorType?x(2);x?<<1,2;VectorType?grad(2);
double?f_lin?=anyLin(x,&grad);// 調用包裝器,就像調用原始函數一樣std::cout?<<"Linear function value: "<<?f_lin?<<"\n";std::cout?<<"Gradient: "<<?grad.transpose()<<"\n\n";
}// Test ConstantFunction via FunctionExpr (None).
// 通過 FunctionExpr 測試常數函數 (零階)。
{ConstantFunction?cf(3.14);// 創建一個常數函數對象FunctionExpr?anyConst(cf);// 包裝它VectorType?x(2);x?<<1,2;
double?f_const?=anyConst(x);// 調用std::cout?<<"Constant function value: "<<?f_const?<<"\n\n";// 測試表達式模板:對一個 FunctionExpr 對象取負FunctionExpr negAnyConst?=-anyConst;f_const?=negAnyConst(x);std::cout?<<"Constant function value: "<<?f_const?<<"\n\n";
}// Test QuadraticFunction via FunctionExpr (Second-order).
// 通過 FunctionExpr 測試二次函數 (二階)。
{MatrixType?A(2,2);A?<<3,1,1,2;VectorType?b(2);b?<<1,-1;
double?c_val?=0.5;QuadraticFunction?quad(A,?b,?c_val);// 創建二次函數對象FunctionExpr?anyQuad(quad);// 包裝VectorType?x(2);x?<<1,2;VectorType?grad(2);MatrixType?hess(2,2);
double?f_quad?=anyQuad(x,&grad,&hess);// 調用,獲取值、梯度和海森矩陣std::cout?<<"Quadratic function value: "<<?f_quad?<<"\n";std::cout?<<"Gradient: "<<?grad.transpose()<<"\n";std::cout?<<"Hessian:\n"<<?hess?<<"\n\n";
}
// Test QuadraticFunction2 via FunctionExpr (Second-order).
// 測試靜態維度的二次函數。
{
using?VectorType?=?Eigen::Vector2d;
using?MatrixType?=?Eigen::Matrix2d;MatrixType A;A?<<3,1,1,2;VectorType b;b?<<1,-1;
double?c_val?=0.5;QuadraticFunction2?quad(A,?b,?c_val);FunctionExpr?anyQuad(quad);VectorType?x(2);x?<<1,2;VectorType?grad(2);MatrixType?hess(2,2);
double?f_quad?=anyQuad(x,&grad,&hess);std::cout?<<"Quadratic function value: "<<?f_quad?<<"\n";std::cout?<<"Gradient: "<<?grad.transpose()<<"\n";std::cout?<<"Hessian:\n"<<?hess?<<"\n\n";
}// Test expression templates:
// 測試表達式模板:
// Expression: (quad + lin - lin * 2.0)
// 表達式: (quad + lin - lin * 2.0)
{MatrixType?A(2,2);A?<<3,1,1,2;VectorType?b(2);b?<<1,-1;
double?c_val?=0.5;QuadraticFunction?quad(A,?b,?c_val);VectorType?m(2);m?<<2,-1;
double?b_val?=0.5;LinearFunction?lin(m,?b_val);// Expression: quad + lin ?(Min(Differentiability(quad),
// Differentiability(lin)) = First)
// 表達式: quad + lin (最終可微性取兩者中的較低者,即一階)
auto?expr1?=?quad?+?lin;
// Expression: lin * 2.0
// 表達式: lin * 2.0
auto?expr2?=?lin?*2.0;
// Expression: (quad + lin) - (lin * 2.0)
// 表達式: (quad + lin) - (lin * 2.0)
auto?expr?=?expr1?-?expr2;FunctionExpr?anyExpr(expr);// 將最終的復雜表達式包裝起來VectorType?x(2);x?<<1,2;VectorType?grad(2);
double?f_expr?=anyExpr(x,&grad);// 調用復雜表達式std::cout?<<"Expression (quad + lin - lin*2.0) value: "<<?f_expr?<<"\n";std::cout?<<"Expression gradient: "<<?grad.transpose()<<"\n";// 測試兩個函數相乘的表達式FunctionExpr?prodFunc(quad?*?quad);VectorType?grad2(2);
quad(x,&grad2);std::cout?<<"Expression gradient: "<<?grad2.transpose()<<"\n";// 這行打印的是 quad 的梯度
double?f2_expr?=prodFunc(x,&grad2);std::cout?<<"Expression (quad * quad) value: "<<?f2_expr?<<"\n";std::cout?<<"Expression gradient: "<<?grad2.transpose()<<"\n";// 這行打印的是 (quad*quad) 的梯度
}// --- Equality Constraint Example ---
// --- 等式約束示例 ---
// Constraint: 2*x0 + x1 - 3 == 0
// 約束: 2*x0 + x1 - 3 == 0
// Penalty function: 0.5 * [2*x0 + x1 - 3]^2
// 懲罰函數: 0.5 * [2*x0 + x1 - 3]^2
{
// Define a linear function: f(x) = 2*x0 + 1*x1 - 3.
// 定義一個線性函數: f(x) = 2*x0 + 1*x1 - 3。VectorType?m(2);m?<<2,1;
double?b_val?=-3;
// 使用庫函數將線性約束 c(x)=0 轉化為二次懲罰函數 P(x)=0.5*c(x)^2FunctionExpr anyEqPenalty?=
quadraticEqualityPenalty(LinearFunction(m,?b_val));// Evaluate at x = (1, 2)
// 在點 x = (1, 2) 處求值VectorType?x(2);x?<<1,2;VectorType?grad(2);
double?eqPenaltyValue?=anyEqPenalty(x,&grad);// 調用懲罰函數std::cout?<<"Equality constraint penalty (2*x0 + x1 - 3 == 0) at x=(1,2): "
<<?eqPenaltyValue?<<"\n";std::cout?<<"Gradient: "<<?grad.transpose()<<"\n\n";
}// --- 構造增廣拉格朗日函數示例 ---
{
using?TScalar?=double;
using?VectorType?=?Eigen::VectorXd;
using?MatrixType?=?Eigen::MatrixXd;// Define the objective function as a quadratic:
// 將目標函數定義為二次函數:MatrixType?A(2,2);A?<<3,1,1,2;VectorType?b(2);b?<<1,-1;TScalar c_val?=0.5;FunctionExpr objective?=QuadraticFunction(A,?b,?c_val);// Define two constraints.
// 定義兩個約束。
// Constraint 0: Equality constraint: 2*x0 + x1 - 3 == 0.
// 約束 0: 等式約束: 2*x0 + x1 - 3 == 0。VectorType?m1(2);m1?<<2,1;TScalar b1?=-3;FunctionExpr eqConstraint?=LinearFunction(m1,?b1);// Constraint 1: Inequality constraint: x0 - 1 <= 0.
// 約束 1: 不等式約束: x0 - 1 <= 0。
// (We interpret "≤" as an InequalityLe constraint.)
// (我們將其解釋為“小于等于”的不等式約束。)VectorType?m2(2);m2?<<1,0;TScalar b2?=-1;FunctionExpr ineqConstraint?=LinearFunction(m2,?b2);// Build the optimization problem.
// 構造優化問題。
// 注意:這里的約束格式與庫的標準 c(x)>=0 不完全一致,但主要用于演示構建過程。
// eqConstraint 表示 c(x)=0,-eqConstraint 也表示 c(x)=0。ConstrainedOptimizationProblem?optimization_problem(objective,{FunctionExpr(-eqConstraint)},{ineqConstraint});// 創建拉格朗日乘子和懲罰因子的狀態LagrangeMultiplierState<double>l_state(1,1);l_state.equality_multipliers[0]=1;l_state.inequality_multipliers[0]=1;l_state?=LagrangeMultiplierState<double>({1},{1});// 另一種初始化方式PenaltyState<double>p_state(1);// 使用庫函數構建完整的懲罰函數和增廣拉格朗日函數FunctionExpr unconstrainedPenalty?=
ToPenalty(optimization_problem,?p_state);FunctionExpr unconstrainedAugmentedLagrangian?=
ToAugmentedLagrangian(optimization_problem,?l_state,?p_state);VectorType?x(2);x?<<1,3;// 分別構建拉格朗日部分和懲罰部分FunctionExpr penalty_part?=FormPenaltyPart(optimization_problem,?p_state);FunctionExpr l_part?=FormLagrangianPart(optimization_problem,?l_state);// Evaluate at the initial state.
// 在初始點處求值,以驗證構建是否正確。VectorType?grad(2);TScalar objValue;objValue?=eqConstraint(x,&grad);std::cout?<<"eqConstraint at x0: "<<?objValue?<<"\n";objValue?=ineqConstraint(x,&grad);std::cout?<<"ineqConstraint at x0: "<<?objValue?<<"\n";objValue?=unconstrainedAugmentedLagrangian(x,&grad);std::cout?<<"Augmented Lagrangian value at x0: "<<?objValue?<<"\n";objValue?=l_part(x,&grad);std::cout?<<"Lpart value at x0: "<<?objValue?<<"\n";objValue?=penalty_part(x,&grad);std::cout?<<"Penalty value at x0: "<<?objValue?<<"\n";objValue?=unconstrainedPenalty(x,&grad);std::cout?<<"obj + penalty value at x0: "<<?objValue?<<"\n";std::cout?<<"Gradient: "<<?grad.transpose()<<"\n";
}return0;
}
4.?linear_regression.cc
這份代碼非常巧妙,它用兩種完全不同的方法來解決同一個帶箱形約束的線性回歸問題,并對比它們的結果,從而展示了?CppNumericalSolvers
?庫的靈活性。
- 方法一 (L-BFGS-B)
: 直接使用專門為箱形約束設計的?
Lbfgsb
?求解器。 - 方法二 (Augmented Lagrangian)
: 將箱形約束顯式地表達為四個獨立的不等式約束,然后使用通用的?
AugmentedLagrangian
?求解器來解決。
這個問題本身是一個簡單的最小二乘問題,目標是找到參數?beta1
?和?beta2
?(在代碼中是?x[0]
?和?x[1]
),使得模型能最好地擬合兩個數據點。
// Copyright 2025, https://github.com/PatWie/CppNumericalSolvers
// 版權所有 2025, https://github.com/PatWie/CppNumericalSolvers#include<iostream>// 引入標準輸入輸出流庫
#include<limits>// 引入數值極限#include"Eigen/Core"// 引入 Eigen 核心庫
#include"cppoptlib/function.h"// 引入庫中定義函數所需的核心頭文件
#include"cppoptlib/solver/augmented_lagrangian.h"// 引入增廣拉格朗日求解器
#include"cppoptlib/solver/lbfgs.h"// 引入 L-BFGS 求解器
#include"cppoptlib/solver/lbfgsb.h"// 引入 L-BFGS-B 求解器usingnamespace?cppoptlib::function;// 使用 cppoptlib::function 命名空間// 定義一個線性回歸的目標函數(最小二乘法)
// 繼承自 FunctionXf,這是一個預定義的別名,代表一階可微、float類型的函數。
classLinearRegression:publicFunctionXf<LinearRegression>{
public:EIGEN_MAKE_ALIGNED_OPERATOR_NEW?// Eigen 內存對齊宏// 重載 operator(),實現函數求值和梯度計算ScalarType?operator()(const?VectorType?&x,VectorType?*gradient?=nullptr)const{
// Compute residuals for the two observations:
// 計算兩個觀測值的殘差:
// Observation 1: beta1 + 2*beta2 - 4
// 觀測點 1: x[0] + 2*x[1] - 4
// Observation 2: 3*beta1 + beta2 - 5
// 觀測點 2: 3*x[0] + x[1] - 5ScalarType r1?=?x[0]+2*?x[1]-4;ScalarType r2?=3*?x[0]+?x[1]-5;// Compute the objective function: sum of squared errors
// 計算目標函數:誤差平方和ScalarType f?=?r1?*?r1?+?r2?*?r2;// Compute the gradient if requested.
// 如果請求梯度,則進行計算。
// The gradient is: grad f = 2 * [ r1 + 3*r2, 2*r1 + r2 ]
// 梯度公式:grad f = 2 * [ r1 + 3*r2, 2*r1 + r2 ]
if(gradient){gradient->resize(x.size());
(*gradient)[0]=2*(r1?+3*?r2);
(*gradient)[1]=2*(2*?r1?+?r2);
}return?f;
}
};// 定義一個通用的邊界約束函數
// 它可以表示 x[i] - lower_bound >= 0 這種形式的約束
classBoundConstraint:publicFunctionXf<BoundConstraint>{
public:
int?index;// 0 or 1 // 變量的索引 (0 或 1)ScalarType lower_bound;// 邊界值BoundConstraint(int?i,?ScalarType bound):index(i),lower_bound(bound){}// 實現約束函數的求值和梯度計算ScalarType?operator()(const?VectorType?&x,?VectorType?*grad?=nullptr)const{
if(grad){// 如果請求梯度grad->setZero();// 先將梯度向量置零
(*grad)[index]=1.0;// ?(x[i] - lower_bound)
// 對應 x[i] 的偏導數是 1,其他是 0
}
// 返回約束函數的值
return?x[index]-?lower_bound;
}
};intmain(){
// 創建一個 L-BFGS-B 求解器實例cppoptlib::solver::Lbfgsb<FunctionExprXf>?solver;// optimal solution is suppose to be [1, 1.6] under the box constraints
// 在箱形約束下,最優解應該是 [1, 1.6]
// 將具體的線性回歸類包裝成通用的 FunctionExpr 類型FunctionExpr f?=LinearRegression();// Either use L-BFG-B
// ---------------------------
// 方法一:直接使用 L-BFGS-BEigen::VectorXf?x(2);x?<<-1,2;// 設置初始猜測值Eigen::VectorXf?lb(2);lb?<<0,1;// 設置下界 [0, 1]Eigen::VectorXf?ub(2);ub?<<1,2;// 設置上界 [1, 2]solver.SetBounds(lb,?ub);// 將邊界信息傳遞給 L-BFGS-B 求解器constauto?initial_state?=?cppoptlib::function::FunctionState(x);// 創建初始狀態
auto[solution,?solver_state]=?solver.Minimize(f,?initial_state);// 運行求解器std::cout?<<"argmin "<<?solution.x.transpose()<<?std::endl;// 打印 L-BFGS-B 找到的解// Or model it as a augmented Lagrangian
// ------------------------------------------
// 方法二:將其建模為增廣拉格朗日問題// 將箱形約束 0 <= x[0] <= 1 和 1 <= x[1] <= 2 拆分為四個不等式約束 c(x) >= 0
// 1. x[0] >= 0 ?=> x[0] - 0 >= 0FunctionExpr lb0?=BoundConstraint(0,0.0f);
// 2. x[1] >= 1 ?=> x[1] - 1 >= 0FunctionExpr lb1?=BoundConstraint(1,1.0f);
// 3. x[0] <= 1 ?=> 1 - x[0] >= 0
// 這里通過 -1 * (x[0] - 1) 來實現FunctionExpr ub0?=-1*BoundConstraint(0,1.0f);
// 4. x[1] <= 2 ?=> 2 - x[1] >= 0
// 這里通過 -1 * (x[1] - 2) 來實現FunctionExpr ub1?=-1*BoundConstraint(1,2.0f);// 構造約束優化問題ConstrainedOptimizationProblem?prob(f,
/* equality constraints */{},// 沒有等式約束
/* inequality constraints */
{lb0,/* x[0] - 0 >= 0 */lb1,/* x[1] - 1 >= 0 */ub0,/* 1 - x[0] >= 0 */ub1,/* 2 - x[1] >= 0 */
});
// 創建一個 L-BFGS 求解器,用作增廣拉格朗日法的內層求解器cppoptlib::solver::Lbfgs<FunctionExprXf>?unconstrained_solver;
// 創建增廣拉格朗日求解器cppoptlib::solver::AugmentedLagrangian?aug_solver(prob,?unconstrained_solver);
// 初始化增廣拉格朗日的狀態
// 參數:初始點x,等式約束數量(0),不等式約束數量(4),初始懲罰因子(1.0)cppoptlib::solver::AugmentedLagrangeState?l_state(x,0,4,1.0f);// Run the agumented solver.
// 運行增廣拉格朗日求解器。
auto[aug_solution,?aug_solver_state]=?aug_solver.Minimize(prob,?l_state);
// 打印增廣拉格朗日法找到的解std::cout?<<"argmin "<<?aug_solution.x.transpose()<<?std::endl;return0;
}
5.?simple.cc
這份代碼是一個全面的求解器測試平臺。它的目的是在一個簡單、明確的二次函數上,測試和演示?CppNumericalSolvers
?庫中提供的幾乎所有無約束優化求解器。
通過注釋和取消注釋不同的?using Solver = ...
?行,用戶可以輕松地切換不同的求解器(如梯度下降、共軛梯度、牛頓法、BFGS、L-BFGS等),并觀察它們的性能、收斂路徑和最終結果。代碼還演示了如何使用庫中的工具來驗證用戶自定義的梯度和海森矩陣是否正確。
// Copyright 2020, https://github.com/PatWie/CppNumericalSolvers
// 版權所有 2020, https://github.com/PatWie/CppNumericalSolvers#include<iostream>// 引入標準輸入輸出流庫
#include<limits>// 引入數值極限#include"Eigen/Core"// 引入 Eigen 核心庫
#include"cppoptlib/function.h"// 引入庫中定義函數所需的核心頭文件
#include"cppoptlib/solver/bfgs.h"// 引入 BFGS 求解器
#include"cppoptlib/solver/conjugated_gradient_descent.h"// 引入共軛梯度下降求解器
#include"cppoptlib/solver/gradient_descent.h"// 引入梯度下降求解器
#include"cppoptlib/solver/lbfgs.h"// 引入 L-BFGS 求解器
#ifEIGEN_VERSION_AT_LEAST(3,4,0)// 條件編譯:只有當 Eigen 版本大于等于 3.4.0 時才包含 L-BFGS-B
#include"cppoptlib/solver/lbfgsb.h"// 引入 L-BFGS-B 求解器
#endif
#include"cppoptlib/solver/nelder_mead.h"// 引入 Nelder-Mead 求解器
#include"cppoptlib/solver/newton_descent.h"// 引入牛頓下降求解器
#include"cppoptlib/utils/derivatives.h"// 引入用于驗證導數的工具// 定義一個類型別名,代表一個二階可微、double類型的函數基類。
template<classF>
using?FunctionXd?=?cppoptlib::function::FunctionCRTP<F,double,?cppoptlib::function::DifferentiabilityMode::Second>;
// 定義一個類型別名,代表一個通用的二階可微、double類型的函數表達式。
using?FunctionExprXd2?=?cppoptlib::function::FunctionExpr<
double,?cppoptlib::function::DifferentiabilityMode::Second>;// 定義一個具體的測試函數:f(x) = 5*x[0]^2 + 100*x[1]^2 + 5
classFunction:publicFunctionXd<Function>{
public:EIGEN_MAKE_ALIGNED_OPERATOR_NEW?// Eigen 內存對齊宏
// 以下幾行是被注釋掉的,但它們明確地定義了函數的屬性,
// 實際上這些屬性已經通過繼承 FunctionXd<Function> 隱式地定義了。
// static constexpr int Dimension = Eigen::Dynamic;
// static constexpr cppoptlib::function::DifferentiabilityMode
// ? ? Differentiability = cppoptlib::function::DifferentiabilityMode::Second;
// using ScalarType = double;// 重載 operator(),實現函數求值、梯度和海森矩陣的計算。ScalarType?operator()(const?VectorType?&x,?VectorType?*gradient?=nullptr,MatrixType?*hessian?=nullptr)const{
if(gradient){// 如果請求梯度gradient->resize(x.size());
(*gradient)[0]=2*5*?x[0];// 梯度分量1: ?f/?x0 = 10*x0
(*gradient)[1]=2*100*?x[1];// 梯度分量2: ?f/?x1 = 200*x1
}if(hessian){// 如果請求海森矩陣hessian->resize(x.size(),?x.size());
(*hessian)(0,0)=10;
(*hessian)(0,1)=0;
(*hessian)(1,0)=0;
(*hessian)(1,1)=200;
}// 返回函數值
return5*?x[0]*?x[0]+100*?x[1]*?x[1]+5;
}
};intmain(){
// 通過取消注釋不同的行來選擇要使用的求解器。
// using Solver = cppoptlib::solver::GradientDescent<FunctionExprXd2>;
// using Solver = cppoptlib::solver::ConjugatedGradientDescent<FunctionExprXd2>;
// using Solver = cppoptlib::solver::NewtonDescent<FunctionExprXd2>;
// using Solver = cppoptlib::solver::Bfgs<FunctionExprXd2>;
using?Solver?=?cppoptlib::solver::Lbfgs<FunctionExprXd2>;// 當前選擇的是 L-BFGS
// using Solver = cppoptlib::solver::Lbfgsb<FunctionExprXd2>;
// using Solver = cppoptlib::solver::NelderMead<FunctionExprXd2>;constexprauto?dim?=2;// 定義問題維度
// 將具體的 Function 類包裝成通用的 FunctionExpr 類型FunctionExprXd2 f?=Function();Function::VectorType?x(dim);// 創建一個二維向量x?<<-10,2;// 設置初始猜測值// ---- 初步測試和驗證 ----Function::VectorType gradient?=?Function::VectorType::Zero(2);
constdouble?value?=f(x,&gradient);// 計算初始點的函數值和梯度std::cout?<<?value?<<?std::endl;// 打印初始函數值std::cout?<<?gradient?<<?std::endl;// 打印初始梯度// 使用庫工具驗證解析梯度和海森矩陣是否正確
// IsGradientCorrect 會比較解析梯度和數值梯度std::cout?<<?cppoptlib::utils::IsGradientCorrect(f,?x)<<?std::endl;// 應該輸出 1 (true)
// IsHessianCorrect 會比較解析海森矩陣和數值海森矩陣std::cout?<<?cppoptlib::utils::IsHessianCorrect(f,?x)<<?std::endl;// 應該輸出 1 (true)// ---- 運行優化求解器 ----
// 創建初始狀態
constauto?initial_state?=?cppoptlib::function::FunctionState(x);std::cout?<<"init "<<?initial_state.x.transpose()<<?std::endl;// 打印初始點Solver solver;// 創建所選求解器的實例// 設置一個回調函數,用于在每次迭代時打印詳細進度solver.SetCallback(cppoptlib::solver::PrintProgressCallback<Solver::FunctionType,Solver::StateType>(std::cout));
// 運行最小化過程
auto[solution,?solver_state]=?solver.Minimize(f,?initial_state);// ---- 打印最終結果 ----std::cout?<<"argmin "<<?solution.x.transpose()<<?std::endl;// 打印找到的最優解 xstd::cout?<<"f in argmin "<<f(solution.x)<<?std::endl;// 打印最優解處的函數值std::cout?<<"iterations "<<?solver_state.num_iterations?<<?std::endl;// 打印總迭代次數std::cout?<<"status "<<?solver_state.status?<<?std::endl;// 打印求解器的最終狀態(終止原因)return0;
}
6.?CMakeLists.txt
這份?CMakeLists.txt
?文件是一個用于自動化編譯 C++ 項目的腳本。它的主要作用是定義如何編譯幾個示例程序,并確保它們能找到所需的依賴庫(如 Eigen3)。它通過定義一個可復用的?function
?來簡化多個相似示例的構建過程。
# Function to build examples
# 定義一個名為 build_example 的函數,用于構建示例程序
# 這個函數接受一個參數 `name`,代表示例程序的名稱 (例如 "simple")
function(build_example name)
# add_executable 指令用于告訴 CMake 創建一個可執行文件。
# 第一個參數 ${name} 是可執行文件的目標名稱 (target name)。
# 第二個參數 ${name}.cc 是用于編譯該可執行文件的源文件名。
# 例如,如果 name 是 "simple",這條命令就是 add_executable(simple simple.cc)。
add_executable(${name}${name}.cc)# target_compile_options 指令用于為指定的目標設置編譯選項。
# ${name} 是目標名稱。
# PRIVATE 表示這些選項只對這個目標本身生效,不會傳遞給鏈接到它的其他目標。
# -std=c++17: 要求編譯器使用 C++17 標準。
# -Wall: 開啟所有常用的編譯器警告 (Warnings all)。
# -Wextra: 開啟一些額外的、不被 -Wall 包含的警告。
target_compile_options(${name}PRIVATE?-std=c++17?-Wall -Wextra)# target_link_libraries 指令用于為指定的目標鏈接所需的庫。
# ${name} 是目標名稱。
# PRIVATE 表示鏈接關系只對這個目標本身生效。
# CppNumericalSolvers: 鏈接到本 CppNumericalSolvers 項目本身(假設在主 CMakeLists.txt 中已定義)。
# Eigen3::Eigen: 鏈接到通過 find_package 找到的 Eigen3 庫。
target_link_libraries(${name}PRIVATE?CppNumericalSolvers?Eigen3::Eigen)
endfunction()# Find Eigen and GoogleTest
# 尋找 Eigen 和 GoogleTest 庫
# find_package 指令用于在系統中查找并加載一個外部庫的配置。
# Eigen3 是要查找的庫的名稱。
# REQUIRED 表示如果找不到這個庫,CMake 將會報錯并停止構建過程。
find_package(Eigen3 REQUIRED)
# 查找 GoogleTest 庫(用于單元測試),如果找不到則報錯。
# 注意:盡管這里找到了 GTest,但在下面的示例構建中并沒有使用它。
# 這可能意味著 GTest 是用于其他部分(如測試目標)的,或者這是一個不完整的腳本片段。
find_package(GTest REQUIRED)# Build examples
# 構建示例程序
# 調用之前定義的 build_example 函數來構建名為 "simple" 的示例。
# 這會執行:
# ? add_executable(simple simple.cc)
# ? target_compile_options(simple PRIVATE ...)
# ? target_link_libraries(simple PRIVATE ...)
build_example(simple)# 調用 build_example 函數來構建名為 "constrained_simple" 的示例。
# 這會執行:
# ? add_executable(constrained_simple constrained_simple.cc)
# ? target_compile_options(constrained_simple PRIVATE ...)
# ? target_link_libraries(constrained_simple PRIVATE ...)
build_example(constrained_simple)
這份?CMakeLists.txt
?腳本定義了一個自動化的構建流程,其核心邏輯可以概括如下:
1. 依賴聲明 (Dependency Declaration)
腳本首先聲明了項目的硬性依賴:
- Eigen3
: 一個用于線性代數(矩陣、向量)的 C++ 模板庫。這是必需的 (REQUIRED)。
- GTest
: Google 的 C++ 測試框架。這也是必需的 (REQUIRED),但在此腳本片段中未被示例目標直接使用。
2. 構建藍圖定義 (Build Blueprint Definition)
通過?function(build_example name)
,腳本定義了一個名為?build_example
?的構建模板或藍圖。這個藍圖規定了任何一個“示例程序”的構建規則:
- 輸入 (Input)
:
一個源文件,其名稱為?
name.cc
。
- 輸出 (Output)
:
一個可執行文件,其名稱為?
name
。
- 編譯規則 (Compilation Rules)
:
必須使用?C++17?標準進行編譯。
必須開啟?
-Wall
?和?-Wextra
?警告選項,以確保代碼質量。
- 鏈接規則 (Linking Rules)
:
必須鏈接到?
CppNumericalSolvers
?庫本身(提供優化算法)。必須鏈接到?
Eigen3
?庫(提供數學計算支持)。
3. 構建目標實例化 (Build Target Instantiation)
最后,腳本通過調用這個藍圖兩次,實例化了兩個具體的構建目標:
- 目標?
simple
:
基于源文件?
simple.cc
?構建。遵循?
build_example
?藍圖的所有編譯和鏈接規則。
- 目標?
constrained_simple
:
基于源文件?
constrained_simple.cc
?構建。同樣遵循?
build_example
?藍圖的所有規則。
總結:
該 CMake 腳本通過一個可復用的函數,高效地定義了兩個示例程序(simple
?和?constrained_simple
)的構建過程。每個示例都會被編譯成一個獨立的可執行文件,使用 C++17 標準,并鏈接到?CppNumericalSolvers
?和?Eigen3
?庫。這體現了良好的 CMake 實踐,即通過函數來避免重復代碼,使構建腳本更簡潔、更易于維護。