在优化问题建模中,手动编写每个约束条件就像用砖块一块一块地盖房子。当问题规模较小时(比如只有三五个变量),这种方法是可行的。但遇到多变量、多约束的复杂问题时(比如电力系统调度中的机组组合问题,可能涉及上百个0-1变量和数千条约束),手动编写不仅效率低下,还容易出错。
我曾在风电功率预测项目中遇到过这样的场景:需要为20台风电机组建立包含启停成本、爬坡率约束的混合整数规划模型。最初手动编写约束时,光是检查下标对齐就花了整整两天,后来改用矩阵形式构建,代码量减少了70%,调试时间缩短了90%。
矩阵形式的本质是将优化问题的结构化特征显式表达。具体来说:
x = [x1; x2; ... xn])c与变量的内积(如c'*x)Ax ≤ b或Ax = b的规范形式这种表达方式有三大优势:
这两个函数虽然都能获取变量索引,但在非线性表达式处理上存在关键区别。通过一个光伏逆变器调度案例来说明:
matlab复制yalmip('clear')
P = sdpvar(5,1); % 5个逆变器的输出功率
Q = P.^2; % 无功功率与有功的平方关系
P_indices = depends(P) % 返回 [1 2 3 4 5]
Q_indices = getvariables(Q) % 返回 [6 7 8 9 10]
Q_linear = depends(Q) % 仍然返回 [1 2 3 4 5]
这里揭示了一个重要规律:
getvariables将非线性项视为新变量,因此Q会获得独立索引depends始终追踪底层线性变量,忽略非线性运算实际应用时建议:
depends获取原始变量索引getbase函数是构建系数矩阵的核心工具。以一个微电网经济调度为例:
matlab复制% 定义变量
Pg = sdpvar(3,1); % 3台发电机出力
Cost = [100, 150, 200] * Pg + 50*sum(Pg);
% 提取系数矩阵
B = full(getbase(Cost));
% 结果:
% [ 50 100 150 200]
% \ \ \ \
% 常数额外成本 发电机1系数 发电机2系数 发电机3系数
更复杂的场景是处理带条件约束。比如电池储能系统的充放电互斥约束:
matlab复制Pch = sdpvar(24,1); % 充电功率
Pdis = sdpvar(24,1); % 放电功率
alpha = binvar(24,1); % 二元标志变量
cons = [Pch <= 100*alpha, Pdis <= 100*(1-alpha)];
B_cons = cellfun(@(x) full(getbase(x)), cons, 'UniformOutput', false);
此时B_cons将包含两个块的系数矩阵,分别对应充电和放电约束。
当需要选择性提取特定变量系数时,getbasematrix比getbase更高效。例如在含可再生能源的机组组合问题中:
matlab复制% 变量定义
x = binvar(10,1); % 机组启停状态
P = sdpvar(10,1); % 机组出力
Wind = sdpvar(1); % 风电预测值
% 只提取机组出力的系数
P_coeff = getbasematrix(P(1), getvariables(P));
% 结果将是一个单位矩阵,因为P各元素是独立变量
recover函数在动态建模中特别有用。比如在模型预测控制(MPC)中:
matlab复制% 初始变量
u = sdpvar(5,1);
idx = getvariables(u);
% 在后续迭代中重建变量
for k = 1:5
u_recovered(k) = recover(idx(k));
end
这种方法可以保持变量索引的一致性,特别适合分步求解的场景。
考虑一个包含24时段的储能调度问题:
matlab复制% 变量定义
Pgrid = sdpvar(24,1); % 电网购售电
Pbat = sdpvar(24,1); % 电池充放电
E = sdpvar(24,1); % 电池电量
% 约束条件
constraints = [];
for t = 1:24
% 电量平衡
if t == 1
constraints = [constraints, E(t) == 0.9*E_init + 0.95*Pbat(t)];
else
constraints = [constraints, E(t) == 0.9*E(t-1) + 0.95*Pbat(t)];
end
% 功率限制
constraints = [constraints, -50 <= Pbat(t) <= 50];
end
% 转换为矩阵形式
all_vars = [Pgrid; Pbat; E];
idx = depends(all_vars);
B = full(getbase(constraints));
A = B(:, idx+1); % +1跳过常数列
b = -B(:,1); % 提取右侧常数
对于含0-1变量的工厂选址问题:
matlab复制% 变量
x = binvar(5,1); % 是否在i地建厂
y = sdpvar(5,5); % 从i地到j地的运输量
% 逻辑约束:只有建厂才能运输
cons = [];
for i = 1:5
for j = 1:5
cons = [cons, y(i,j) <= 1000*x(i)];
end
end
% 矩阵化关键步骤
base = full(getbase(cons));
var_idx = depends([x; y(:)]);
A = base(:, var_idx + 1);
这里需要注意:
y展开为一维y(:)在电力系统潮流计算中,节点关联矩阵通常是稀疏的。利用Yalmip的矩阵操作可以高效构建:
matlab复制% 定义30节点系统的支路功率
n = 30;
P = sdpvar(n,n,'full');
P(logical(eye(n))) = 0; % 对角线清零
% 构建节点平衡约束
Load = rand(n,1); % 各节点负荷
Gen = sdpvar(n,1); % 各节点发电
cons = [];
for i = 1:n
inflow = sum(P(:,i)); % 流入功率
outflow = sum(P(i,:)); % 流出功率
cons = [cons, Gen(i) - Load(i) == inflow - outflow];
end
% 转换为矩阵形式时,注意保留稀疏性
full_cons = full(getbase(cons));
当处理超大规模问题(如超过1万个变量)时,建议采用分块构建策略。以区域电网互联为例:
matlab复制% 分区定义
areas = {1:10, 11:20, 21:30}; % 3个区域
% 分块构建约束
A_blocks = cell(3,3);
b_blocks = cell(3,1);
for a = 1:3
% 区内约束
P_local = sdpvar(length(areas{a}),1);
cons_local = [0 <= P_local <= 100];
% 区间联络线约束
if a < 3
P_tie = sdpvar(length(areas{a}), length(areas{a+1}));
cons_tie = [ -50 <= P_tie(:) <= 50 ];
cons_all = [cons_local; cons_tie(:)];
end
% 转换为分块矩阵
base = full(getbase(cons_all));
A_blocks{a,a} = base(:, 2:end); % 假设变量连续
b_blocks{a} = -base(:,1);
end
% 最终组装
A = blkdiag(A_blocks{:});
b = vertcat(b_blocks{:});
在长期使用中,我总结出几个关键注意事项:
zeros预分配矩阵空间matlab复制n = 5000;
A = zeros(n, 1000); % 预分配5000×1000矩阵
sparse存储matlab复制A_sparse = sparse(A); % 转换稀疏格式
dependsmatlab复制% 不推荐
for i = 1:n
idx(i) = depends(x(i));
end
% 推荐
all_idx = depends(x);
矩阵形式构建完成后,可通过以下方式提升求解效率:
matlab复制options = sdpsettings('solver','gurobi');
options.gurobi.Method = 2; % 使用内点法
options.gurobi.Presolve = 2; % 激进预处理
% 特别针对大规模MIP问题
options.gurobi.MIPGap = 0.01; % 设置容差
options.gurobi.Threads = 8; % 多线程并行
在实际项目中,这种矩阵化方法曾将某物流配送问题的求解时间从47分钟缩短到2.3分钟。关键在于充分挖掘问题结构特征,将逻辑约束转化为高效的矩阵运算。