二次规划(QP)问题根据其约束条件的性质可以分为线性二次规划和非线性二次规划两大类,它们在数学形式、求解难度和应用场景等方面存在显著差异。
比较维度 | 线性二次规划 | 非线性二次规划 |
---|---|---|
约束条件 | 全部线性 | 至少一个非线性约束 |
目标函数 | 必须凸(Q半正定) | 可凸可非凸(Q不定) |
可行域 | 凸多面体 | 可能非凸 |
最优解性质 | 全局最优解唯一(严格凸时) | 可能有多个局部最优解 |
求解难度 | 多项式时间可解 | 通常NP难 |
求解方法 | 内点法、活动集法等专用算法 | 需要通用非线性优化方法 |
应用领域 | 投资组合优化、控制理论等 | 化学工程、复杂物理系统建模等 |
线性二次规划(Linear Quadratic Programming)是一类特殊的非线性规划问题,其目标函数是二次函数,约束条件是线性函数。标准形式的QP问题可以表示为:
m i n 1 2 x T Q x + x T C 约束条件 : A x ≤ b E x = d x ≥ 0 ( 可选 ) min \space \space \frac{1}{2} xᵀQx + xᵀ C \\ \begin{aligned} 约束条件:Ax &≤ b \\ Ex &= d \\ x &≥ 0 (可选) \\ \end{aligned} min 21xTQx+xTC约束条件:AxExx≤b=d≥0(可选)
其中:
x ∈ R n x ∈ ℝⁿ x∈Rn是决策变量
Q ∈ R n x n Q ∈ ℝⁿˣⁿ Q∈Rnxn是对称矩阵(通常要求半正定)
C ∈ R n C ∈ ℝⁿ C∈Rn是线性项系数向量
A ∈ R m x n 和 b ∈ R m A ∈ ℝᵐˣⁿ 和 b ∈ ℝᵐ A∈Rmxn和b∈Rm 定义线性不等式约束
E ∈ R p x n 和 d ∈ R p E ∈ ℝᵖˣⁿ 和 d ∈ ℝᵖ E∈Rpxn和d∈Rp 定义线性等式约束
根据矩阵Q的性质,QP问题可分为:
凸问题是数学优化中一类特殊且重要的问题,因其优良的数学性质在实际应用中备受青睐。下面我将从多个角度全面解释凸问题的概念。
凸问题是指满足以下两个条件的优化问题:
标准形式的凸优化问题可表示为:
最小化 f ( x ) 约束条件 : g i ( x ) ≤ 0 , i = 1 , . . . , m h j ( x ) = 0 , j = 1 , . . . , p 最小化 f(x) \\ \begin{aligned} 约束条件:gᵢ(x) ≤ 0, i = 1,...,m \\ hⱼ(x) = 0, j = 1,...,p \\ \end{aligned} 最小化f(x)约束条件:gi(x)≤0,i=1,...,mhj(x)=0,j=1,...,p
其中:
f ( x ) f(x) f(x) 是凸函数
g i ( x ) gᵢ(x) gi(x) 是凸函数(不等式约束)
h j ( x ) hⱼ(x) hj(x) 是仿射函数(等式约束)
集合 C ⊆ R n 称为凸集,如果对于任意 x , y ∈ C 和任意 θ ∈ [ 0 , 1 ] ,有: θ x + ( 1 − θ ) y ∈ C 即集合中任意两点的连线仍完全包含在集合内。 集合C ⊆ ℝⁿ称为凸集,如果对于任意x,y ∈ C和任意θ ∈ [0,1],有:\\ θx + (1-θ)y ∈ C \\ 即集合中任意两点的连线仍完全包含在集合内。 集合C⊆Rn称为凸集,如果对于任意x,y∈C和任意θ∈[0,1],有:θx+(1−θ)y∈C即集合中任意两点的连线仍完全包含在集合内。
函数 f : R n → R 称为凸函数,如果其定义域是凸集,且对于任意 x , y ∈ d o m ( f ) 和任意 θ ∈ [ 0 , 1 ] ,有 : f ( θ x + ( 1 − θ ) y ) ≤ θ f ( x ) + ( 1 − θ ) f ( y ) 几何解释:函数图像上任意两点间的线段位于函数图像上方。 函数f: ℝⁿ → ℝ称为凸函数,如果其定义域是凸集,且对于任意x,y ∈ dom(f)和任意θ ∈ [0,1],有:\\ f(θx + (1-θ)y) ≤ θf(x) + (1-θ)f(y) \\ 几何解释:函数图像上任意两点间的线段位于函数图像上方。 函数f:Rn→R称为凸函数,如果其定义域是凸集,且对于任意x,y∈dom(f)和任意θ∈[0,1],有:f(θx+(1−θ)y)≤θf(x)+(1−θ)f(y)几何解释:函数图像上任意两点间的线段位于函数图像上方。
二次函数: f ( x ) = x T Q x + c T x 是凸的 ⇔ Q 是半正定矩阵 二次函数:f(x) = xᵀQx + cᵀx是凸的 ⇔ Q是半正定矩阵 二次函数:f(x)=xTQx+cTx是凸的⇔Q是半正定矩阵
一般函数:可通过 H e s s i a n 矩阵判断若 ∇ 2 f ( x ) ≽ 0 (半正定)对所有 x ∈ d o m ( f ) 成立,则 f 是凸函数 一般函数:可通过Hessian矩阵判断 若∇²f(x) ≽ 0(半正定)对所有x ∈ dom(f)成立,则f是凸函数 一般函数:可通过Hessian矩阵判断若∇2f(x)≽0(半正定)对所有x∈dom(f)成立,则f是凸函数
不等式约束 g i ( x ) ≤ 0 :要求 g i ( x ) 是凸函数 不等式约束gᵢ(x) ≤ 0:要求gᵢ(x)是凸函数 不等式约束gi(x)≤0:要求gi(x)是凸函数
等式约束 h j ( x ) = 0 :必须是仿射函数(形如 A x = b ) 等式约束hⱼ(x) = 0:必须是仿射函数(形如Ax = b) 等式约束hj(x)=0:必须是仿射函数(形如Ax=b)
线性规划(LP):
$
最小化 cᵀx \
\begin{aligned}
约束条件 Ax &≤ b \
Cx &= d \
\end{aligned}
$
线性二次规划(QP)(当Q半正定时):
$
最小化 \frac{1}{2}xᵀQx + cᵀx \
\begin{aligned} \
约束条件 Ax ≤ b
\end{aligned}
$
无约束凸优化:梯度下降法、牛顿法、共轭梯度法
约束凸优化:内点法(特别是路径跟踪法)、有效集法(适用于QP)、交替方向乘子法(ADMM)
特性 | 凸问题 | 非凸问题 |
---|---|---|
最优解 | 局部最优=全局最优 | 可能有多个局部最优 |
求解难度 | 通常可高效求解 | 通常NP难 |
算法保证 | 有收敛性和最优性保证 | 通常只能得到局部最优 |
对偶间隙 | 通常为零 | 通常存在 |
应用范围 | 受限于凸性要求 | 建模更灵活 |
设投资比例向量为:
X = [ w 1 , w 2 , w 3 ] T X = [w₁, w₂, w₃]ᵀ X=[w1,w2,w3]T
目标函数(组合方差):
m i n J = 1 2 ∗ ( 0.04 w 1 2 + 0.0225 w 2 2 + 0.0025 w 3 2 + 2 × 0.018 w 1 w 2 + 2 × ( − 0.002 ) w 1 w 3 + 2 × 0.00075 w 2 w 3 ) min \space \space J= \frac{1}{2}*(0.04w₁² + 0.0225w₂² + 0.0025w₃² + 2×0.018w₁w₂ + 2×(-0.002)w₁w₃ + 2×0.00075w₂w₃) min J=21∗(0.04w12+0.0225w22+0.0025w32+2×0.018w1w2+2×(−0.002)w1w3+2×0.00075w2w3)
约束条件:
预期收益约束: 0.12 w 1 + 0.08 w 2 + 0.04 w 3 ≥ 0.07 预期收益约束:0.12w₁ + 0.08w₂ + 0.04w₃ ≥ 0.07 预期收益约束:0.12w1+0.08w2+0.04w3≥0.07
预算约束: w 1 + w 2 + w 3 = 1 预算约束:w₁ + w₂ + w₃ = 1 预算约束:w1+w2+w3=1
单一资产上限: w 1 ≤ 0.5 , w 2 ≤ 0.5 , w 3 ≤ 0.5 单一资产上限:w₁ ≤ 0.5, w₂ ≤ 0.5, w₃ ≤ 0.5 单一资产上限:w1≤0.5,w2≤0.5,w3≤0.5
非负约束: w 1 ≥ 0 , w 2 ≥ 0 , w 3 ≥ 0 非负约束:w₁ ≥ 0, w₂ ≥ 0, w₃ ≥ 0 非负约束:w1≥0,w2≥0,w3≥0
m i n J = 1 2 X T P X + X T q s . t . l o w e r b o u n d ≤ A X ≤ u p p e r b o u n d min J= \frac{1}{2} X^T P X + X^T q \\ s.t. \space \space lowerbound\leq AX \leq upperbound minJ=21XTPX+XTqs.t. lowerbound≤AX≤upperbound
其中:
P = [ 0.04 0.018 − 0.002 0.018 0.0225 0.00075 − 0.002 0.00075 0.0025 ] P= \left[ \begin{matrix} 0.04& 0.018& -0.002 \\ 0.018&0.0225&0.00075 \\ -0.002& 0.00075& 0.0025 \end{matrix} \right] P= 0.040.018−0.0020.0180.02250.00075−0.0020.000750.0025
q = [ 0 0 0 ] q= \left[ \begin{matrix} 0 \\ 0 \\ 0 \end{matrix} \right] q= 000
A = [ 0.12 0.08 0.04 1 1 1 1 0 0 0 1 0 0 0 1 ] A= \left[ \begin{matrix} 0.12 & 0.08 & 0.04 \\ 1& 1& 1 \\ 1& 0& 0 \\ 0& 1& 0 \\ 0& 0& 1 \end{matrix} \right] A= 0.1211000.0810100.041001
l o w e r b o u n d = [ 0.07 1 0 0 0 ] lowerbound= \left[ \begin{matrix} 0.07 \\ 1 \\ 0 \\ 0 \\ 0 \end{matrix} \right] lowerbound= 0.071000
u p p e r b o u n d = [ i n f 1 i n f i n f i n f ] upperbound= \left[ \begin{matrix} inf \\ 1 \\ inf \\ inf \\ inf \end{matrix} \right] upperbound= inf1infinfinf
OSQP 是一个用于求解 凸二次规划(QP) 问题的高效数值优化库,基于 ADMM(交替方向乘子法) 算法。
特点:
Data structure
typedef struct {
c_int n; ///< number of variables n
c_int m; ///< number of constraints m
csc *P; ///< the upper triangular part of the quadratic cost matrix P in csc format (size n x n).必须上三角矩阵,且为OSQP使用的CSC格式
csc *A; ///< linear constraints matrix A in csc format (size m x n)OSQP使用的CSC格式
c_float *q; ///< dense array for linear part of cost function (size n)
c_float *l; ///< dense array for lower bound (size m)
c_float *u; ///< dense array for upper bound (size m)
} OSQPData;
Settings struct
typedef struct {
c_float rho; ///< ADMM step rho
c_float sigma; ///< ADMM step sigma
c_int scaling; ///< heuristic data scaling iterations; if 0, then disabled.
# if EMBEDDED != 1
c_int adaptive_rho; ///< boolean, is rho step size adaptive?
c_int adaptive_rho_interval; ///< number of iterations between rho adaptations; if 0, then it is automatic
c_float adaptive_rho_tolerance; ///< tolerance X for adapting rho. The new rho has to be X times larger or 1/X times smaller than the current one to trigger a new factorization.
# ifdef PROFILING
c_float adaptive_rho_fraction; ///< interval for adapting rho (fraction of the setup time)
# endif // Profiling
# endif // EMBEDDED != 1
c_int max_iter; ///< maximum number of iterations
c_float eps_abs; ///< absolute convergence tolerance
c_float eps_rel; ///< relative convergence tolerance
c_float eps_prim_inf; ///< primal infeasibility tolerance
c_float eps_dual_inf; ///< dual infeasibility tolerance
c_float alpha; ///< relaxation parameter
enum linsys_solver_type linsys_solver; ///< linear system solver to use
# ifndef EMBEDDED
c_float delta; ///< regularization parameter for polishing
c_int polish; ///< boolean, polish ADMM solution
c_int polish_refine_iter; ///< number of iterative refinement steps in polishing
c_int verbose; ///< boolean, write out progress
# endif // ifndef EMBEDDED
c_int scaled_termination; ///< boolean, use scaled termination criteria
c_int check_termination; ///< integer, check termination interval; if 0, then termination checking is disabled
c_int warm_start; ///< boolean, warm start
# ifdef PROFILING
c_float time_limit; ///< maximum number of seconds allowed to solve the problem; if 0, then disabled
# endif // ifdef PROFILING
} OSQPSettings;
typedef struct {
/// Problem data to work on (possibly scaled)
OSQPData *data;
OSQPSettings *settings; ///< problem settings
OSQPScaling *scaling; ///< scaling vectors
OSQPSolution *solution; ///< problem solution
OSQPInfo *info; ///< solver information
...
} OSQPWorkspace;
代码如下:
#include
#include
#include
// 类型转换辅助函数 - 创建独立的内存
csc eigen_to_csc(const Eigen::SparseMatrix<double> &mat)
{
csc result;
result.nzmax = mat.nonZeros();
result.m = mat.rows();
result.n = mat.cols();
// Allocate new memory and copy data
result.p = (c_int *)malloc((mat.outerSize() + 1) * sizeof(c_int));
std::copy(mat.outerIndexPtr(), mat.outerIndexPtr() + mat.outerSize() + 1, result.p);
result.i = (c_int *)malloc(mat.nonZeros() * sizeof(c_int));
std::copy(mat.innerIndexPtr(), mat.innerIndexPtr() + mat.nonZeros(), result.i);
result.x = (c_float *)malloc(mat.nonZeros() * sizeof(c_float));
std::copy(mat.valuePtr(), mat.valuePtr() + mat.nonZeros(), result.x);
result.nz = -1; // 使用压缩格式
return result;
}
void free_csc(csc *matrix)
{
if (matrix)
{
if (matrix->p)
free(matrix->p);
if (matrix->i)
free(matrix->i);
if (matrix->x)
free(matrix->x);
}
}
int main()
{
// 1. 构造标准QP问题
Eigen::Matrix3d Sigma;
Sigma << 0.04, 0.018, -0.002,
0.018, 0.0225, 0.00075,
-0.002, 0.00075, 0.0025;
// 2. 提取上三角部分并转为稀疏矩阵,并压缩
Eigen::Matrix3d P_upper = Sigma.triangularView<Eigen::Upper>();
Eigen::SparseMatrix<double> P = P_upper.sparseView();//将稠密矩阵转换为稀疏矩阵
P.makeCompressed();//压缩稀疏矩阵存储,将稀疏矩阵转换为 压缩列存储(CSC)格式,这是 OSQP 所需的格式
// 3. 梯度矩阵无要求
Eigen::VectorXd q = Eigen::VectorXd::Zero(3);
// 4. 约束矩阵转为稀疏矩阵,并压缩
Eigen::MatrixXd A_dense(5, 3);
A_dense << 0.12, 0.08, 0.04,
1, 1, 1,
1, 0, 0,
0, 1, 0,
0, 0, 1;
Eigen::SparseMatrix<double> A = A_dense.sparseView();//将稠密矩阵转换为稀疏矩阵
A.makeCompressed();//压缩稀疏矩阵存储,将稀疏矩阵转换为 压缩列存储(CSC)格式,这是 OSQP 所需的格式
// 5.设置约束上下限(等式约束和不等式约束)
Eigen::VectorXd lowerbound(5), upperbound(5);
lowerbound << 0.07, 1, 0, 0, 0;
upperbound << OSQP_INFTY, 1, OSQP_INFTY, OSQP_INFTY, OSQP_INFTY;
// 6.将压缩的稀疏矩阵转换为 OSQP 的 CSC 格式
// 第二种
csc P_csc = eigen_to_csc(P);
csc A_csc = eigen_to_csc(A);
// // 第一种
// csc P_csc;
// P_csc.nzmax = P.nonZeros();
// P_csc.m = P.rows();
// P_csc.n = P.cols();
// P_csc.p = (c_int *)P.outerIndexPtr();
// P_csc.i = (c_int *)P.innerIndexPtr();
// P_csc.x = (c_float *)P.valuePtr();
// P_csc.nz = -1; // 标记为压缩格式
// csc A_csc;
// A_csc.nzmax = A.nonZeros();
// A_csc.m = A.rows();
// A_csc.n = A.cols();
// A_csc.p = (c_int *)A.outerIndexPtr();
// A_csc.i = (c_int *)A.innerIndexPtr();
// A_csc.x = (c_float *)A.valuePtr();
// A_csc.nz = -1; // 标记为压缩格式
// 7.填充OSQPData
OSQPData data;
data.n = P.rows();
data.m = A.rows();
data.P = &P_csc; // 必须是上三角矩阵
data.q = q.data();
data.A = &A_csc;
data.l = lowerbound.data();
data.u = upperbound.data();
// 8. 设置参数
OSQPSettings settings;
osqp_set_default_settings(&settings);
settings.verbose = true;//输出求解日志
// 9. 创建工作空间
OSQPWorkspace *work = nullptr;
osqp_setup(&work, &data, &settings);
if (!work)
{
std::cerr << "Failed to setup OSQP workspace" << std::endl;
free_csc(&P_csc);
free_csc(&A_csc);
return 1;
}
// 10. 求解QP问题
osqp_solve(work);
// 11. 输出结果
if (work->info->status_val == OSQP_SOLVED)
{
std::cout << "A: " << work->solution->x[0] * 100 << "%\n";
std::cout << "B: " << work->solution->x[1] * 100 << "%\n";
std::cout << "C: " << work->solution->x[2] * 100 << "%\n";
}
else
{
std::cerr << "error: " << work->info->status << std::endl;
}
// 12. 清理资源
osqp_cleanup(work);
// free(P_csc.p);
// free(P_csc.i);
// free(P_csc.x);
free_csc(&P_csc);
free_csc(&A_csc);
free(&settings);
return 0;
}
-----------------------------------------------------------------
OSQP v0.6.3 - Operator Splitting QP Solver
(c) Bartolomeo Stellato, Goran Banjac
University of Oxford - Stanford University 2021
-----------------------------------------------------------------
problem: variables n = 3, constraints m = 5
nnz(P) + nnz(A) = 15
settings: linear system solver = qdldl,
eps_abs = 1.0e-003, eps_rel = 1.0e-003,
eps_prim_inf = 1.0e-004, eps_dual_inf = 1.0e-004,
rho = 1.00e-001 (adaptive),
sigma = 1.00e-006, alpha = 1.60, max_iter = 4000
check_termination: on (interval 25),
scaling: on, scaled_termination: off
warm start: on, polish: off, time_limit: off
iter objective pri res dua res rho time
1 0.0000e+000 1.00e+000 1.00e+002 1.00e-001 2.75e-004s
25 2.8538e-003 2.64e-004 4.26e-004 1.00e-001 5.50e-004s
status: solved
number of iterations: 25
optimal objective: 0.0029
run time: 1.01e-003s
optimal rho estimate: 1.77e-002
problem:
variables n = 3
优化问题的变量数(即投资组合中的资产数量,此处为3:股票A、股票B、债券C)。
constraints m = 5
约束条件的数量(包括收益率约束、权重和为1、单个资产权重上限等)。
nnz(P) + nnz(A) = 15
矩阵 P(二次项)和 A(约束矩阵)中非零元素的总数。nnz 表示 "number of non-zeros"。
settings
linear system solver = qdldl
使用的线性系统求解器(QDLLDL,一种针对稀疏对称正定矩阵的高效求解器)。
收敛容忍度:
eps_abs/eps_rel:绝对/相对残差容忍度(1e-3)。
eps_prim_inf/eps_dual_inf:原始/对偶不可行性容忍度(1e-4)。
自适应参数:
rho:惩罚参数(初始值0.1,启用自适应调整)。
sigma:正则化参数(1e-6)。
alpha:松弛参数(1.6)。
终止条件:
max_iter = 4000:最大迭代次数。
check_termination: on (interval 25):每25次迭代检查终止条件。
其他选项:
scaling: on:启用问题数据缩放(提高数值稳定性)。
warm start: on:启用热启动(加速求解)。
iter
iter:迭代次数。
objective:当前目标函数值(投资组合方差)。
pri res:原始残差(约束违反程度)。
dua res:对偶残差(最优性条件违反程度)。
rho:当前惩罚参数。
time:累计求解时间。
关键观察:
第1次迭代:目标值为0(初始猜测),原始残差和对偶残差较大(未收敛)。
第25次迭代:目标值收敛到 0.0032,原始和对偶残差均降至容忍度以下(<1e-3),求解成功。
result
status: solved
问题成功求解(其他可能状态:max_iter_reached、primal_infeasible等)。
optimal objective: 0.0032
最优目标函数值(投资组合的最小方差)。
run time: 2ms
总求解时间(性能极佳)。
optimal rho estimate: 1.26e-002
自适应调整后的最终惩罚参数值。
在OSQP的求解过程中,原始残差(primal residual)和对偶残差(dual residual)是衡量解的质量和可行性的关键指标。当两者均低于设定的容忍度(如 eps_abs=1e-3 或 eps_rel=1e-3)时,表明求解器找到了一个满足所有约束且接近最优的解。
(1)原始残差(Primal Residual):衡量当前解是否满足原始问题的约束条件,物理意义:
(2)对偶残差(Dual Residual):衡量当前解 是否满足最优性条件(即梯度在约束边界上的平衡)。物理意义:
原始残差低 → 约束满足
对偶残差低 → 最优性保证
两者结合 → 可行且最优
(1)检查收敛状态
if (work->info->status_val == OSQP_SOLVED) {
std::cout << "残差: " << work->info->pri_res << " (原始), "
<< work->info->dua_res << " (对偶)" << std::endl;
}
(2)调整容忍度
若需要更高精度,可降低 eps_abs(如 1e-5):
settings.eps_abs = 1e-5;
(3)调试不可行问题
若 pri_res 很高,检查约束是否矛盾(如 l > u)。
若 dua_res 很高,验证 P 是否正定或 q 是否正确。