0.1. 第一阶段:环境搭建与认知重构(第1-3天)

0.1.1. 第一天:理解”优化”本质与工具链定位

0.1.1.1. 上午(2小时):概念认知重塑

1.1 什么是优化问题?(从生活实例讲起)

想象你在超市购物,手里有200元预算,要买10种商品。每种商品有:

  • 价格(成本)
  • 营养价值(收益)
  • 保质期(约束条件)

目标:在预算内买到营养最高的组合,且每种商品至少买1件。

这就是优化问题的三要素:

  • 决策变量:每种商品买多少(x₁, x₂, …, x₁₀)
  • 目标函数:最大化营养价值(或最小化成本)
  • 约束条件:总预算≤200元,每种商品≥1件
数学表达:
maximize: Σ(营养值ᵢ × xᵢ)
subject to: Σ(价格ᵢ × xᵢ) ≤ 200
            xᵢ ≥ 1, ∀i ∈ {1,2,...,10}

优化求解器(Gurobi)的作用就是:自动帮你找到满足所有条件的最优解,而不是让你穷举所有可能组合

1.2 工具链角色分工(保姆级比喻)

┌─────────────────────────────────────────────────────────┐
│                    你的办公桌 (MATLAB)                  │
│                                                          │
│  ┌──────────────┐       ┌──────────────┐               │
│  │   秘书小姐   │◄─────►│   数学苦力   │               │
│  │   (YALMIP)   │ 翻译  │   (Gurobi)   │               │
│  └──────────────┘       └──────────────┘               │
└─────────────────────────────────────────────────────────┘
  • MATLAB:你的”操作系统”。所有操作都在这里完成,负责数据预处理、结果可视化、脚本管理
  • YALMIP:你的”智能翻译秘书”。她懂数学语言(≤, ≥, Σ),会把你的数学公式翻译成Gurobi能听懂的机器语言
  • Gurobi:你的”超级计算苦力”。他只做一件事:快速解方程,但需要你告诉他方程长什么样

关键认知千万不要跳过YALMIP直接学Gurobi! 就像你不会为了学英语直接去读莎士比亚原著,而是应该先学语法。YALMIP就是那个”语法老师”。

0.1.1.2. 下午(3小时):软件安装实战

1.3 MATLAB安装与基础配置

【安装步骤】
1. 获取安装包:
   - 学校版:通过学校软件中心,使用校园邮箱注册MathWorks账号
   - 试用版:官网下载R2024a/R2024b(30天试用)
   - 注意:建议使用R2021b及以上版本,对YALMIP兼容性最好

2. 安装注意事项:
   - 勾选"MATLAB Compiler"和"Optimization Toolbox"(虽然我们用Gurobi,但这两个工具箱有时辅助用)
   - 安装路径不要有中文和空格,例如:D:\MATLAB\R2024b
   - 安装后首次启动,选择"激活",输入许可证或登录MathWorks账号

3. 基础配置(关键!):
   >> cd('D:\MATLAB\R2024b\bin')  % 进入安装目录
   >> matlab -register            % 注册环境变量(Windows)
   
   % 设置默认工作路径(避免每次打开都在C盘)
   >> userpath('D:\MyMATLABProjects')
   >> savepath

1.4 Gurobi学术许可证申请(全程图文)

这是最容易卡壳的环节,务必按步骤操作:

【申请流程(学生版)】
1. 访问 Gurobi 官网:https://www.gurobi.com/
2. 点击 "Free Trial" → "Academic Licenses"
3. 选择 "Gurobi Academic License"(不是Gurobi Cloud!)
4. 填写信息:
   - First Name: 你的名(拼音,如 Xiaoming)
   - Last Name: 你的姓(拼音,如 Zhang)
   - Email: 教育网邮箱(关键!必须是 .edu.cn 或学校邮箱)
   - Institution: 你的大学全称
5. 提交后,邮箱会收到验证邮件,点击链接
6. 登录Gurobi账户,进入 "Downloads" 页面
7. 下载对应版本(建议 11.0.3 及以上)
8. 安装时勾选 "Install for all users"(避免权限问题)

【激活许可证】
1. 安装完成后,打开 "Gurobi License Center"
2. 复制你的许可证密钥(格式:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
3. 打开MATLAB,输入:
   >> grbgetkey xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
4. 等待激活成功提示

【验证安装】
在MATLAB命令行输入:
>> g = gurobi()
如果显示 "Gurobi Optimizer version 11.0.3...",说明安装成功

1.5 YALMIP安装(最简单但最重要)

【安装步骤】
1. 访问 YALMIP 官网:http://yalmip.github.io/
2. 点击 "Download" → "Download ZIP"
3. 解压到:D:\MATLAB\R2024b\toolbox\yalmip(推荐路径)
4. 在MATLAB中添加路径:
   >> addpath('D:\MATLAB\R2024b\toolbox\yalmip')
   >> savepath  % 永久保存路径

【验证安装】
在MATLAB命令行输入:
>> yalmiptest

预期输出应包含:

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|       Test|   CPLEX|   GUROBI|   MOSEK|   SDPT3|   SEDUMI|   FMINCON|
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|    Linear |    OK  |    OK   |   OK   |   OK   |   OK    |    OK    |
|      SOCP |    OK  |    OK   |   OK   |   OK   |   OK    |    OK    |
|   InfeasLP|    OK  |    OK   |   OK   |   OK   |   OK    |    OK    |
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

关键:Gurobi栏必须显示”OK”,否则需检查许可证或路径。

0.1.1.3. 晚上(1小时):环境测试与第一个程序

1.6 运行你的第一个优化问题

% 文件名:test_optimization.m
% 问题描述:minimize x^2 + y^2,满足 x + y = 1

clear all; clc;  % 清除变量和命令行

% 步骤1:定义变量
x = sdpvar(1,1);  % 实数变量x
y = sdpvar(1,1);  % 实数变量y

% 步骤2:设置约束
Constraints = [x + y == 1];  % 注意:== 表示等式约束

% 步骤3:设置目标函数
Objective = x^2 + y^2;  % 最小化平方和

% 步骤4:求解设置
options = sdpsettings('solver','gurobi','verbose',1);  % 显示求解过程

% 步骤5:求解
diary('solver_log.txt');  % 记录日志
result = optimize(Constraints, Objective, options);
diary off;  % 关闭日志

% 步骤6:提取结果
if result.problem == 0  % 求解成功
    x_opt = value(x);
    y_opt = value(y);
    fprintf('最优解: x = %.4f, y = %.4f\n', x_opt, y_opt);
    fprintf('最优目标值: %.4f\n', value(Objective));
else
    fprintf('求解失败,错误代码: %d\n', result.problem);
end

常见问题排查:

  • 错误”Undefined function ‘sdpvar’”:YALMIP路径未添加成功,重新addpath
  • 错误”Gurobi license error”:许可证未激活或过期,重新运行grbgetkey
  • 错误”Infeasible model”:约束矛盾,检查==和<=的使用

0.1.2. 第二天:数学基础补全与YALMIP核心语法

0.1.2.1. 上午(2.5小时):优化问题的数学分类

2.1 问题的分类体系(必须理解)

优化问题分类树:

               优化问题
                 │
        ┌────────┴────────┐
    线性规划(LP)        非线性规划
                         │
              ┌──────────┴──────────┐
         二次规划(QP)            一般非线性(NLP)
              │
        ┌─────┴─────┐
    凸QP          非凸QP

详细解释(逐项拆解):

① 线性规划 (LP - Linear Programming)

  • 数学形式:所有表达式都是线性的(变量的1次方)
    minimize: c₁x₁ + c₂x₂ + ... + cₙxₙ
    subject to: a₁₁x₁ + a₁₂x₂ + ... + a₁ₙxₙ ≤ b₁
                a₂₁x₁ + a₂₂x₂ + ... + a₂ₙxₙ ≤ b₂
                xᵢ ≥ 0
  • YALMIP特点:使用sdpvar定义变量,目标函数和约束都是线性表达式
  • 求解难度:★☆☆☆☆(Gurobi可在毫秒级求解百万变量问题)

② 混合整数线性规划 (MILP - Mixed Integer LP)

  • 数学形式:LP + 部分变量必须是整数(通常是0或1)
    xᵢ ∈ {0,1} 表示"是否选择i",或 xᵢ ∈ ℤ 表示"整数个"
  • YALMIP特点:使用binvarintvar定义变量
  • 求解难度:★★★★☆(NP-hard问题,Gurobi的最强项)

③ 二次规划 (QP - Quadratic Programming)

  • 数学形式:目标函数是二次的,约束是线性的
    minimize: xᵀQx + cᵀx  (Q是二次项系数矩阵)
    subject to: Ax ≤ b
  • YALMIP特点:目标函数可写为x'*Q*x + c'*x
  • 求解难度:★★★☆☆(凸QP易解,非凸QP极难)

④ 二阶锥规划 (SOCP - Second Order Cone Programming)

  • 数学形式:包含范数约束
    ||Ax + b||₂ ≤ cᵀx + d
  • YALMIP特点:可写为norm(A*x + b) <= c'*x + d
  • 求解难度:★★★☆☆

2.2 YALMIP核心语法深度解析

语法1:定义变量(sdpvar/binvar/intvar)

% 1. 实数变量(sdpvar)
x = sdpvar(1);              % 标量实数变量
x_vec = sdpvar(3,1);        % 3×1列向量 [x1;x2;x3]
x_mat = sdpvar(2,3);        % 2×3矩阵(每个元素都是变量)

% 2. 二元变量(binvar)- 只能取0或1
y = binvar(1);              % 开关变量
y_vec = binvar(5,1);        % 5个开关的组合

% 3. 整数变量(intvar)
z = intvar(1);              % 整数变量(如购买数量)
z_mat = intvar(3,3);        % 3×3整数矩阵

% 4. 特殊形式:对称矩阵变量(用于SDP问题)
X = sdpvar(3,3,'symmetric'); % 3×3对称矩阵,X = X'

% 5. 多维变量:决策变量是三维张量(高阶问题)
W = sdpvar([2,3,4]);        % 2×3×4三维变量数组

% 重要技巧:变量命名规范
P_gen = sdpvar(10,1);       % 发电机功率(10台机组)
u_on = binvar(10,1);        % 机组启停状态(1=运行)

语法2:构建约束(Constraints)

% 方式1:逐个添加(适合小型问题)
C1 = [x >= 0];              % 单个约束
C2 = [x + y <= 10];         % 线性约束
C = [C1, C2];               % 约束集合

% 方式2:批量添加(推荐,效率高)
n = 100;
x = sdpvar(n,1);
A = randn(50,n);  b = rand(50,1);
C = [A*x <= b, x >= 0];   % 一次性添加50个约束

% 方式3:逻辑约束(需要最新版YALMIP)
y = binvar(1);
C = [implies(y==1, x>=5)]; % 如果y=1,则x必须≥5

% 方式4:绝对值约束(自动转化为线性约束)
C = [abs(x) <= 5];          % 等价于 -5 <= x <= 5

% 方式5:分段约束(分段线性)
C = [-5 <= x <= 5, -3 <= y <= 3];

% 常见错误示例:
% 错误1:使用等号赋值约束
C = [x = 5];  % 错误!必须用 == 
% 错误2:未初始化的约束数组
C = [];  % 正确做法:先初始化为空数组
for i = 1:10
    C = [C, x(i) <= i];  % 逐个添加
end

语法3:设置目标函数(Objective)

% 类型1:最小化(最常用)
obj = c'*x;                 % 线性目标
obj = x'*Q*x + c'*x;        % 二次目标
obj = sum(x.^2);            % 平方和

% 类型2:最大化
obj = -c'*x;                % 最大化c'*x 等价于 最小化 -c'*x
% 或在求解时指定:
ops = sdpsettings('solver','gurobi','solvefor','max');

% 类型3:多目标(加权和)
obj1 = c1'*x;  obj2 = c2'*x;
weights = [0.7, 0.3];
obj = weights(1)*obj1 + weights(2)*obj2;

% 重要:目标函数必须是标量
obj = sum(x);               % 正确,标量
obj = x;                    % 错误,当x是向量时,这是向量目标

语法4:求解与结果提取(optimize & value)

% 基础求解
ops = sdpsettings('solver','gurobi');
optimize(C, obj, ops);      % 最小化obj
optimize(C, -obj, ops);     % 最大化obj

% 高级设置
ops = sdpsettings(...
    'solver','gurobi',...        % 指定求解器
    'verbose',1,...              % 显示求解过程
    'debug',1,...                % 开启调试模式
    'saveinput','model.mps');    % 保存模型到文件

% 结果诊断
result = optimize(C, obj, ops);
disp(result);  % 显示求解信息

% result.problem 取值含义:
%   0: 求解成功
%   1: 问题无可行解(约束矛盾)
%   2: 问题无界(目标函数可无限优化)
%   3: 达到迭代限制
%   4: 求解器错误

% 提取结果
if result.problem == 0
    x_opt = value(x);               % 提取最优解
    obj_opt = value(obj);           % 提取最优目标值
    dual_opt = dual(C{1});          % 提取约束的对偶变量(灵敏度分析)
    
    % 格式化输出
    fprintf('最优目标值: %.4f\n', obj_opt);
    disp('最优解:');
    disp(x_opt);
else
    % 错误处理
    switch result.problem
        case 1
            error('模型无可行解,请检查约束条件!');
        case 2
            error('模型无界,请检查目标函数和约束!');
        otherwise
            error('求解失败,错误代码: %d', result.problem);
    end
end

0.1.2.2. 下午(2小时):YALMIP调试技巧与常见错误

2.3 调试工具箱

你的问题非常好!你提供的代码片段反映了你在使用 YALMIP 进行建模和调试,但其中确实 混入了一些非 YALMIP 的函数或用法错误。下面我将:

  1. 逐条分析你例子中的问题
  2. 系统介绍 YALMIP 正确的调试方法
  3. 提供修正后的可运行示例

0.1.3. 一、你例子中的问题分析

0.1.3.1. ❌ analyze(C)

  • 问题:YALMIP 没有名为 analyze 的函数。
  • 正确做法:可用 sdisplay(C) 查看约束表达式,或用 depends(C) 查看涉及的变量,但 没有一键显示变量数、约束数、问题类型的函数
  • 若想查看模型结构,推荐使用:
    % 查看约束内容
    sdisplay(C)
    % 查看所有变量
    all_vars = allvariables(C, obj);  % obj 是目标函数
    disp(['Number of variables: ', num2str(length(all_vars))]);

0.1.3.2. ❌ assign(x,5); check(C)

  • 问题 1:你定义的是 n = binvar(10...,但后面用 assign(x,5),变量名不一致(x 未定义)。
  • 问题 2check 不是 YALMIP 函数。
  • 正确做法
    assign(n, some_binary_vector);  % 必须是 0/1 向量,长度1000
    value(C)  % 返回每个约束的数值(如 sum(n) <= 500 的左边值)
    % 检查是否满足:所有 value(C) <= 0(因为 YALMIP 把约束表示为 <= 0 形式)

0.1.3.3. ❌ feasibility = optimize(C,[],ops);

  • 语法问题:YALMIP 的 optimize 不能接受空目标([])
  • 正确做法:使用 0 作为目标(常数):
    feasibility = optimize(C, 0, ops);  % 仅求可行解

0.1.3.4. ❌ disp(value(obj)); 但 obj 未定义

  • 在工具1和工具4中你都没有定义 obj,但工具5却用了它。
  • 这会导致运行错误。

0.1.3.5. ❌ saveinput 参数拼写错误

  • 正确参数是 'savesolverinput''saveduals',但 YALMIP 没有 'saveinput'
  • 若想导出 .lp 文件,应使用:
    ops = sdpsettings('solver','gurobi', 'savesolverinput', 'debug.lp');
    或更推荐用:
    export(C, obj, 'debug.lp');  % 直接导出模型,不求解

0.1.4. 二、YALMIP 正确的调试方法(系统总结)

0.1.4.1. ✅ 1. 检查模型结构

n = binvar(1000,1);
C = [sum(n) <= 500];
obj = sum(n);  % 示例目标

% 查看约束表达式
sdisplay(C)

% 查看所有变量
vars = allvariables(C, obj);
fprintf('变量数量: %d (应为1000)\n', length(vars));

% 查看问题是否为混合整数
is_mip = any([vars(:).binvar]) || any([vars(:).intvar]);

0.1.4.2. ✅ 2. 导出模型为 LP/MPS 文件

export(C, obj, 'model.lp');  % 不求解,仅导出
% 然后用文本编辑器打开 model.lp

0.1.4.3. ✅ 3. 手动赋值并验证约束

% 构造一个可行解:前500个为1,其余为0
test_val = [ones(500,1); zeros(500,1)];
assign(n, test_val);
con_vals = value(C);  % 例如返回 -1(表示 sum(n)-500 = -1 <= 0)
fprintf('约束值: %.2f (应 <= 0)\n', con_vals);

⚠️ 注意:assign 只用于调试,不会影响 optimize 的求解。求解时 YALMIP 会忽略手动赋值。

0.1.4.4. ✅ 4. 仅求可行解(Feasibility Check)

ops = sdpsettings('verbose', 1);  % 显示求解器输出
feas = optimize(C, 0, ops);       % 目标为 0
if feas.problem == 0
    disp('找到可行解');
else
    disp('问题不可行');
end

0.1.4.5. ✅ 5. 检查目标函数值(在某赋值下)

assign(n, test_val);
obj_val = value(obj);
disp(['目标值: ', num2str(obj_val)]);

0.1.4.6. ✅ 6. 查看求解器返回信息

sol = optimize(C, obj, ops);
if sol.problem ~= 0
    disp(['求解失败,错误代码: ', num2str(sol.problem)]);
    % 常见:1=不可行,2=无界,3=求解失败
else
    optimal_n = value(n);
    optimal_obj = value(obj);
end

0.1.4.7. ✅ 7. 使用 spy 查看稀疏结构(对大型问题)

% 查看约束矩阵结构(需先构造内部模型)
diagnostic = optimize(C, obj, sdpsettings('solver','gurobi','debug',1));
% 或使用 internal model
[F, f] = model(C, obj);  % 不推荐直接用,但可用于分析

0.1.5. 三、修正后的完整示例

% 定义变量
n = binvar(1000, 1);

% 约束
C = [sum(n) <= 500];

% 目标(假设最小化 sum(n))
obj = sum(n);

% 1. 检查模型
fprintf('变量数: %d\n', length(allvariables(C, obj)));
sdisplay(C)

% 2. 导出模型
export(C, obj, 'debug.lp');

% 3. 可行性检查
ops = sdpsettings('solver','gurobi','verbose',0);
feas = optimize(C, 0, ops);
if feas.problem == 0
    disp('模型可行');
else
    disp('模型不可行!');
end

% 4. 手动赋值验证
assign(n, [ones(300,1); zeros(700,1)]);
con_val = value(C);
obj_val = value(obj);
fprintf('手动赋值下:约束值=%.1f (<=0?), 目标值=%.1f\n', con_val, obj_val);

% 5. 正常求解
sol = optimize(C, obj, ops);
if sol.problem == 0
    fprintf('最优目标: %.1f\n', value(obj));
else
    error('求解失败');
end

0.1.6. 总结

  • analyze, check 不是 YALMIP 函数;
  • 空目标 [] 应替换为 0
  • 变量名要一致(你用了 n 但赋值 x);
  • 使用 assign + value 是调试赋值的有效方法;
  • export 导出 .lp 文件,比 saveinput 更可靠;
  • 始终检查 sol.problem 判断求解状态。

如果你有具体的模型不可行、求解慢、结果异常等问题,也可以贴出来,我可以帮你进一步诊断。

2.4 十个新手必犯错误与解决方案

错误现象 原因分析 解决方案
Undefined function 'sdpvar' YALMIP路径未添加 addpath('.../yalmip'); savepath
Gurobi license error 10009 学术许可证未激活 重新运行grbgetkey,检查系统时间
Model is infeasible 约束矛盾 relax(C)或逐个注释约束排查
Matrix dimensions must agree 矩阵维度不匹配 size()检查变量和系数矩阵维度
Objective must be scalar 目标函数是向量 使用sum()square_pos()转换
Cannot convert sdpvar to double 在optimize前使用value() 只在求解后使用value()提取结果
Solver not found Gurobi未正确安装 运行gurobi_setup,检查MATLAB路径
Out of memory 问题规模太大 使用sparse()创建稀疏矩阵
Undefined operator '*' 非线性项错误 检查二次项是否写成x'*Q*x形式
Warning: Solver not applicable 问题类型与求解器不匹配 检查是否是MILP却用了不支持整数求解器

0.1.7. 第三天:问题类型实战与参数调优

0.1.7.1. 上午(2.5小时):参数调优详解

3.1 Gurobi参数体系(性能调优关键)

% 参数设置结构
ops = sdpsettings(...
    'solver','gurobi',...
    'gurobi.MIPGap',0.01,...        % MIP间隙容忍度(1%)
    'gurobi.TimeLimit',3600,...     % 时间限制(秒)
    'gurobi.Threads',8,...          % 使用CPU核数
    'gurobi.OutputFlag',1,...       % 显示迭代过程
    'gurobi.Heuristics',0.5,...     % 启发式算法强度
    'gurobi.CutPasses',2,...        % 割平面法迭代次数
    'gurobi.Presolve',2,...         % 预求解级别(0-2)
    'gurobi.Method',-1,...          % 算法选择(-1=自动)
    'gurobi.LogFile','gurobi.log');% 日志文件

% 常用参数组合模板:

% 模板1:快速可行解(适合初次运行)
ops_fast = sdpsettings(...
    'solver','gurobi',...
    'gurobi.MIPFocus',1,...         % 重点找可行解
    'gurobi.Heuristics',0.8,...     % 加强启发式
    'gurobi.TimeLimit',300);        % 5分钟上限

% 模板2:精确最优解(适合最终求解)
ops_accurate = sdpsettings(...
    'solver','gurobi',...
    'gurobi.MIPGap',0.001,...       % 0.1%精度
    'gurobi.MIPFocus',2,...         % 重点优化界
    'gurobi.Cuts',3,...             % 激进割平面
    'gurobi.NodeMethod',2);         % 节点用对偶单纯形

% 模板3:大规模问题(内存受限)
ops_large = sdpsettings(...
    'solver','gurobi',...
    'gurobi.Threads',4,...          % 限制线程数
    'gurobi.NodefileStart',0.5,...  % 内存占用50%时转硬盘
    'gurobi.Method',2,...           % 使用内点法
    'gurobi.BarConvTol',1e-4);     % 降低精度换速度

3.2 参数调优案例:背包问题性能对比

% 问题:100个物品中选择,价值最大化
n = 100;
value = rand(n,1)*100;
weight = rand(n,1)*50;
capacity = 1000;

% 定义变量
x = binvar(n,1);

% 约束
C = [sum(weight.*x) <= capacity];

% 目标
obj = -value'*x;  % 最大化价值

% 测试不同参数
param_sets = {
    {'MIPGap',0.01,'Threads',4};
    {'MIPGap',0.05,'Heuristics',0.8};
    {'MIPGap',0.001,'Cuts',3,'MIPFocus',2};
};

for i = 1:length(param_sets)
    fprintf('\n===== 参数组合 %d =====\n', i);
    ops = sdpsettings('solver','gurobi',...
        param_sets{i}{:},'verbose',1,'savesolveroutput',1);
    
    tic;
    result = optimize(C, obj, ops);
    time = toc;
    
    if result.problem == 0
        fprintf('求解时间: %.2f 秒\n', time);
        fprintf('目标值: %.2f\n', -value(value(x)));
        fprintf('节点数: %d\n', result.solveroutput.IterCount);
    end
end

0.1.7.2. 下午(2.5小时):第一阶段总结与实战检验

3.3 第一阶段综合测试题

% 任务:生产排程问题
% 工厂生产A、B两种产品,需要经过机器M1、M2
% 数据:
%  - 产品A利润:30元/件,M1耗时2h,M2耗时3h
%  - 产品B利润:50元/件,M1耗时4h,M2耗时2h
%  - M1可用时间:120h/周,M2可用时间:100h/周
%  - 产品A最大销量:25件/周
% 求:利润最大的生产方案

% 请完成以下代码(填空):
% 1. 定义变量
x_A = ______(1,1);  % 产品A产量
x_B = ______(1,1);  % 产品B产量

% 2. 约束条件
C = [2*x_A + 4*x_B <= ______,  % M1时间约束
     3*x_A + 2*x_B <= ______,  % M2时间约束
     ______ <= 25,             % 销量约束
     x_A >= 0, x_B >= 0];

% 3. 目标函数
Profit = ______*x_A + ______*x_B;

% 4. 求解
ops = sdpsettings('solver','gurobi');
result = optimize(C, -Profit, ops);  % 最大化利润

% 5. 结果输出
if result.problem == ____
    fprintf('最优生产:A=%.0f件, B=%.0f件\n', ______, ______);
    fprintf('最大利润:%.0f元\n', ______);
end

答案与解析:

% 答案:
x_A = sdpvar(1,1);
x_B = sdpvar(1,1);
C = [2*x_A + 4*x_B <= 120,
     3*x_A + 2*x_B <= 100,
     x_A <= 25,
     x_A >= 0, x_B >= 0];
Profit = 30*x_A + 50*x_B;
result = optimize(C, -Profit, ops);
if result.problem == 0
    fprintf('最优生产:A=%.0f件, B=%.0f件\n', value(x_A), value(x_B));
    fprintf('最大利润:%.0f元\n', value(Profit));
end
% 最优解:A=20件, B=20件, 利润=1600元

3.4 第一阶段学习检查清单

  • [ ] 能独立安装MATLAB、Gurobi、YALMIP
  • [ ] 运行yalmiptest显示Gurobi为OK
  • [ ] 理解sdpvar/binvar/intvar的区别
  • [ ] 能写出至少3种约束条件
  • [ ] 会设置目标函数(最小化和最大化)
  • [ ] 会调用optimize并提取value
  • [ ] 能读懂Gurobi求解日志
  • [ ] 遇到”Infeasible”知道如何排查

0.2. 第二阶段:经典模型实战(第2周)

0.2.1. 第4天:线性规划(LP)深度实战

0.2.1.1. 上午(3小时):线性规划理论体系

4.1 线性规划标准形式与转换

标准形式:
minimize    cᵀx
subject to  Ax = b
            x ≥ 0

实际问题的转换技巧:
1. 不等式转等式:添加松弛变量
   aᵢᵀx ≤ bᵢ   →   aᵢᵀx + sᵢ = bᵢ, sᵢ ≥ 0
   
2. 自由变量处理:x无约束 → x = x⁺ - x⁻, x⁺,x⁻ ≥ 0
   
3. 最大化转最小化:
   max cᵀx  →  min -cᵀx

4.2 生产排程问题完整案例

% 问题描述:
% 某工厂生产4种产品(P1-P4),使用3种原材料(R1-R3)
% 数据:
% 利润向量(万元/吨):[5, 8, 6, 7]
% 原材料消耗矩阵(吨原材料/吨产品):
%        P1  P2  P3  P4
% R1     2   3   1   2
% R2     1   2   3   1
% R3     3   1   2   3
% 原材料可用量(吨):R1=150, R2=180, R3=200
% 市场需求上限(吨):P1=30, P2=40, P3=35, P4=25

% MATLAB实现(工业级代码):

%% 第一步:数据准备(通常从Excel读取)
profit = [5; 8; 6; 7];  % 利润向量
A = [2 3 1 2;          % 原材料消耗矩阵
     1 2 3 1;
     3 1 2 3];
b = [150; 180; 200];    % 原材料上限
demand = [30; 40; 35; 25]; % 市场需求

%% 第二步:定义决策变量
x = sdpvar(4,1);  % x(i)表示产品i的产量

%% 第三步:构建约束条件(矩阵化风格)
Constraints = [
    A*x <= b,        % 原材料约束(3个约束,一行代码)
    x >= 0,          % 非负约束
    x <= demand      % 市场需求约束
];

%% 第四步:设置目标函数(最大化利润)
Objective = -profit'*x;  % 负号因为YALMIP默认最小化

%% 第五步:求解设置(工业级参数)
ops = sdpsettings(...
    'solver','gurobi',...
    'verbose',1,...
    'gurobi.Method',0,...         % 自动选择算法
    'gurobi.FeasibilityTol',1e-6,... % 可行性容差
    'gurobi.OptimalityTol',1e-6);   % 最优性容差

%% 第六步:求解与结果输出
result = optimize(Constraints, Objective, ops);

%% 第七步:结果分析与可视化
if result.problem == 0
    x_opt = value(x);
    profit_opt = -value(Objective);
    
    % 创建结果表格
    fprintf('\n========== 生产排程最优方案 ==========\n');
    fprintf('%-10s %-15s %-15s\n', '产品', '产量(吨)', '利润(万元)');
    fprintf('----------------------------------------\n');
    for i = 1:4
        fprintf('%-10s %-15.2f %-15.2f\n', ['P',num2str(i)], x_opt(i), profit(i)*x_opt(i));
    end
    fprintf('----------------------------------------\n');
    fprintf('总利润: %.2f 万元\n', profit_opt);
    
    % 可视化
    figure;
    subplot(2,1,1);
    bar(x_opt);
    set(gca,'xticklabel',{'P1','P2','P3','P4'});
    title('最优生产方案');
    ylabel('产量(吨)');
    
    subplot(2,1,2);
    pie(x_opt, {'P1','P2','P3','P4'});
    title('产量分布');
else
    error('求解失败,错误代码: %d', result.problem);
end

4.3 灵敏度分析(Shadow Price)

% 在求解后提取对偶变量
if result.problem == 0
    % 原材料约束的对偶变量(影子价格)
    shadow_price = dual(Constraints(1:3));
    fprintf('\n原材料影子价格(万元/吨):\n');
    fprintf('R1: %.4f\n', shadow_price(1));
    fprintf('R2: %.4f\n', shadow_price(2));
    fprintf('R3: %.4f\n', shadow_price(3));
    % 影子价格>0表示该原材料是瓶颈资源
end

0.2.1.2. 下午(3小时):运输问题与指派问题

4.4 运输问题(Transportation Problem)

% 问题:3个工厂 → 4个仓库,最小化运输成本

% 数据
supply = [100; 150; 200];  % 工厂供应量
demand = [80; 90; 120; 160]; % 仓库需求量
cost = [4 5 6 8;           % 单位运输成本矩阵(工厂×仓库)
        6 4 3 5;
        7 8 5 6];

% 决策变量:x(i,j)表示从工厂i到仓库j的运量
x = sdpvar(3,4,'full');  % 'full'表示非对称矩阵

% 约束
Constraints = [
    sum(x,2) == supply,    % 行和=供应(每个工厂运出量)
    sum(x,1)' == demand,   % 列和=需求(每个仓库接收量)
    x >= 0                 % 非负
];

% 目标
Objective = sum(sum(cost.*x));  % 总成本

% 求解
optimize(Constraints, Objective, sdpsettings('solver','gurobi'));

% 结果展示
x_opt = value(x);
fprintf('\n最优运输方案:\n');
for i = 1:3
    for j = 1:4
        if x_opt(i,j) > 0.1
            fprintf('工厂%d → 仓库%d: %.1f 吨\n', i, j, x_opt(i,j));
        end
    end
end

4.5 指派问题(Assignment Problem)——纯0-1问题

% 问题:5个工人 → 5个任务,最小化总工时

% 数据:工时矩阵
time_cost = [9 2 7 8 2;
             6 4 3 7 6;
             5 8 1 4 7;
             7 6 9 8 5;
             2 3 4 5 6];

n = 5;
% 决策变量:x(i,j)=1表示工人i做任务j
x = binvar(n,n);  % 5×5二元矩阵

% 约束:每个工人只做1个任务,每个任务只由1个工人做
Constraints = [
    sum(x,2) == 1,  % 行和=1(每个工人1个任务)
    sum(x,1)' == 1  % 列和=1(每个任务1个工人)
];

% 目标
Objective = sum(sum(time_cost.*x));

% 求解(Gurobi在此类问题上性能卓越)
optimize(Constraints, Objective, sdpsettings('solver','gurobi'));

% 结果
x_opt = value(x);
fprintf('\n任务分配方案:\n');
for i = 1:n
    for j = 1:n
        if x_opt(i,j) > 0.5
            fprintf('工人%d ←→ 任务%d (工时:%d)\n', i, j, time_cost(i,j));
        end
    end
end

0.2.2. 第5天:混合整数线性规划(MILP)——Gurobi最擅长的领域

0.2.2.1. 上午(3小时):MILP核心理论与建模技巧

5.1 为什么MILP这么难?(NP-hard问题)

MILP的求解复杂度:
- 若有n个二元变量,理论上需要检查2ⁿ种组合
- 当n=100时,2¹⁰⁰ ≈ 1.27×10³⁰,比宇宙中原子总数还多
- Gurobi的Branch-and-Cut算法:通过剪枝和割平面,实际只需检查极小一部分组合

Gurobi在MILP上的优势:
1. 先进的预求解技术(Presolve):可消去30-50%的变量和约束
2. 启发式算法(Heuristics):快速找到高质量可行解
3. 割平面(Cutting Planes):强化松弛问题,逼近整数解
4. 并行计算:利用多核CPU加速分支定界

5.2 背包问题(Knapsack Problem)——MILP入门

% 问题:容量15kg的背包,选哪些物品使总价值最大

% 数据:10个物品
weights = [2,3,4,5,6,1,2,3,4,5];  % 重量(kg)
values  = [3,5,6,8,9,2,3,4,6,7];  % 价值
capacity = 15;

n = length(weights);
% 决策变量:x(i)=1表示选第i个物品
x = binvar(n,1);

% 约束
Constraints = [weights*x <= capacity];

% 目标
Objective = -values'*x;  % 最大化价值

% 求解
ops = sdpsettings('solver','gurobi','gurobi.MIPGap',0.01);
result = optimize(Constraints, Objective, ops);

% 结果
x_opt = value(x);
fprintf('\n选中物品: ');
for i = 1:n
    if x_opt(i) > 0.5
        fprintf('%d ', i);
    end
end
fprintf('\n总价值: %.0f\n', -value(Objective));

5.3 设施选址问题(Facility Location)——经典MILP

% 问题:在5个候选地建仓库,服务10个客户,最小化总成本(建设+运输)

% 数据
build_cost = [100,150,120,90,110];  % 建设成本(万元)
transport_cost = randn(5,10);       % 运输成本矩阵(5仓库×10客户)
demand = ones(10,1)*50;             % 每个客户需求50吨

% 决策变量1:y(i)=1表示在i地建仓库
y = binvar(5,1);

% 决策变量2:x(i,j)表示仓库i供应客户j的量
x = sdpvar(5,10);

% 约束
Constraints = [
    sum(x,1)' == demand,           % 满足所有客户需求
    sum(x,2) <= 1000*y,            % 只有在建的仓库才能供应(大M法)
    x >= 0
];

% 目标
Objective = build_cost'*y + sum(sum(transport_cost.*x));

% 求解(Gurobi会自动处理大M法)
optimize(Constraints, Objective, sdpsettings('solver','gurobi'));

0.2.2.2. 下午(3小时):MILP高级建模技巧

5.4 逻辑约束建模(Indicator Constraints)

% 场景:如果工厂开启(y=1),则产量x≥100;否则x=0

y = binvar(1);  % 开关变量
x = sdpvar(1);  % 产量

% 传统大M法(可能数值不稳定)
M = 1000;
Constraints = [
    x <= M*y,
    x >= 100*y
];

% YALMIP高级语法(自动转化为Indicator)
Constraints = [implies(y==1, x>=100), implies(y==0, x==0)];

% YALMIP会自动转换为Gurobi的indicator约束,数值更稳定

5.5 分段线性成本函数建模

% 场景:用电成本分段计价
% 0-100度:0.5元/度
% 100-200度:0.8元/度
% 200度以上:1.2元/度

x = sdpvar(1);  % 用电量
cost = sdpvar(1); % 成本

% 方法1:引入辅助变量和约束
x1 = sdpvar(1); x2 = sdpvar(1); x3 = sdpvar(1);
Constraints = [
    x == x1 + x2 + x3,
    0 <= x1 <= 100,
    0 <= x2 <= 100,
    0 <= x3 <= inf,
    cost == 0.5*x1 + 0.8*x2 + 1.2*x3
];

% 方法2:使用YALMIP的pwlf函数(推荐)
cost = pwlf(x, [0 100 200], [0 50 130]); % 断点[0,100,200],累计成本[0,50,130]

0.2.3. 第6天:二次规划(QP)与投资组合优化

0.2.3.1. 上午(3小时):QP理论与凸性判断

6.1 二次规划的数学本质

% 标准形式:
% minimize  0.5*x'*Q*x + c'*x
% subject to Ax <= b

% 关键概念:矩阵Q必须是半正定(PSD)才是凸QP
% Gurobi只能高效求解凸QP

% 判断凸性方法:
Q = [2 -1; -1 2];  % 正定矩阵
eig_Q = eig(Q);
if all(eig_Q >= -1e-6)  % 容忍小负特征值
    disp('是凸QP,Gurobi可解');
else
    error('是非凸QP,Gurobi可能无法找到全局最优');
end

6.2 投资组合优化(Markowitz模型)——经典QP

% 问题:在10支股票中选择投资组合,期望收益≥12%,风险最小

% 数据准备(通常从CSV/Excel读取)
returns = [0.1;0.12;0.09;0.11;0.13;0.08;0.1;0.09;0.12;0.14];  % 期望收益
cov_matrix = cov(randn(100,10));  % 10×10协方差矩阵

% 决策变量:x(i)表示投资比例
x = sdpvar(10,1);

% 约束
Constraints = [
    sum(x) == 1,           % 总投资100%
    x >= 0,                % 不允许做空
    returns'*x >= 0.12     % 期望收益≥12%
];

% 目标:最小化风险(方差)
Objective = x'*cov_matrix*x;  % 二次项

% 求解(Gurobi对凸QP有专门算法)
ops = sdpsettings('solver','gurobi','gurobi.BarHomogeneous',1);
optimize(Constraints, Objective, ops);

% 结果分析
x_opt = value(x);
risk_opt = sqrt(value(Objective));  % 标准差
return_opt = returns'*x_opt;

fprintf('\n最优投资组合:\n');
for i = 1:10
    if x_opt(i) > 0.01  % 只显示占比>1%的股票
        fprintf('股票%d: %.2f%%\n', i, x_opt(i)*100);
    end
end
fprintf('预期收益: %.2f%%\n', return_opt*100);
fprintf('组合风险: %.4f\n', risk_opt);

0.2.3.2. 下午(3小时):二次约束规划(QCP)与鲁棒优化

6.3 带二次约束的问题

% 场景:投资组合中,要求90%的置信水平下风险不超过15%

% 数据同上
% 约束中引入二次项
VaR_constraint = [x'*cov_matrix*x <= 0.15^2];  % 风险约束

Constraints = [
    sum(x) == 1,
    x >= 0,
    returns'*x >= 0.12,
    VaR_constraint
];

% Gurobi会自动识别为QCP问题
optimize(Constraints, -returns'*x, ops);  % 最大化收益

6.4 鲁棒优化初步(仿射可调鲁棒)

% 场景:收益不确定,在±0.02范围内波动,求最坏情况下的最优解

% 定义不确定集
delta = sdpvar(10,1);  % 收益波动
Gamma = 2;             % 预算不确定参数(控制保守度)

% 鲁棒约束
Robust_Constraints = [
    sum(x) == 1,
    x >= 0,
    (returns+delta)'*x >= 0.12,  % 收益约束需满足所有delta
    sum(abs(delta)) <= Gamma,    % 不确定预算约束
    -0.02 <= delta <= 0.02       % 波动范围
];

% YALMIP会自动将鲁棒约束转化为确定性等价形式
optimize(Robust_Constraints, -min((returns+delta)'*x), ops);

0.2.4. 第7天:第二阶段综合实战与复盘

0.2.4.1. 全天(6小时):完整项目——微电网调度

%% 微电网经济调度问题(MILP+QP混合)

% 系统组成:
% - 柴油发电机:成本函数 0.1P² + 5P + 100(二次成本)
% - 光伏:发电成本0,但不确定
% - 电池:充放电效率90%,容量限制
% - 负荷:必须满足

% 预测数据(24小时)
load_profile = [50 45 40 38 35 40 55 70 85 90 88 85 82 80 78 75 80 88 92 85 78 70 62 55];
solar_forecast = [0 0 0 0 0 5 15 30 45 60 70 75 80 75 65 50 35 20 8 0 0 0 0 0];

%% 参数设置
n = 24;  % 24小时调度
P_diesel = sdpvar(n,1);  % 柴油发电机功率
P_battery = sdpvar(n,1); % 电池充放电(正=放电,负=充电)
P_grid = sdpvar(n,1);    % 主网购售电(正=购电,负=售电)

SoC = sdpvar(n+1,1);     % 电池荷电状态(0-100%)
u_diesel = binvar(n,1);  % 柴油机启停状态

% 技术参数
P_diesel_max = 100;      % 柴油机最大功率
P_battery_max = 50;      % 电池功率限制
SoC_min = 0.2; SoC_max = 0.9;  % SoC限制
SoC_initial = 0.5;       % 初始SoC
battery_eff = 0.9;       % 充放电效率
diesel_cost_coeff = [0.1, 5, 100];  % 成本系数[a,b,c]对应aP²+bP+c
grid_buy_price = 0.8;    % 购电价格
grid_sell_price = 0.5;   % 售电价格

%% 约束条件
Constraints = [];

% 1. 功率平衡约束(最关键的等式约束)
for t = 1:n
    Constraints = [Constraints, 
        P_diesel(t) + P_battery(t) + solar_forecast(t) + P_grid(t) == load_profile(t)];
end

% 2. 柴油机约束
Constraints = [Constraints,
    0 <= P_diesel <= P_diesel_max*u_diesel,  % 不运行时功率为0
    u_diesel <= 1, u_diesel >= 0
];

% 3. 电池约束(动力学模型)
Constraints = [Constraints,
    -P_battery_max <= P_battery <= P_battery_max,
    SoC(1) == SoC_initial  % 初始荷电状态
];
for t = 1:n
    Constraints = [Constraints,
        SoC(t+1) == SoC(t) - P_battery(t)/P_battery_max*battery_eff,
        SoC(t+1) >= SoC_min,
        SoC(t+1) <= SoC_max
    ];
end

% 4. 主网约束(设双向功率限制)
P_grid_max = 200;
Constraints = [Constraints, -P_grid_max <= P_grid <= P_grid_max];

%% 目标函数(分段成本)
% 柴油机成本 = 0.1*P² + 5*P + 100*u
diesel_cost = diesel_cost_coeff(1)*sum(P_diesel.^2) + ...
              diesel_cost_coeff(2)*sum(P_diesel) + ...
              diesel_cost_coeff(3)*sum(u_diesel);

% 电网交互成本(购电正成本,售电负成本)
grid_cost = grid_buy_price*sum(max(P_grid,0)) - grid_sell_price*sum(max(-P_grid,0));

% 总成本
Objective = diesel_cost + grid_cost;

%% 求解
ops = sdpsettings('solver','gurobi', 'gurobi.TimeLimit',600, 'verbose',1);
result = optimize(Constraints, Objective, ops);

%% 结果分析与绘图
if result.problem == 0
    % 提取结果
    P_diesel_opt = value(P_diesel);
    P_battery_opt = value(P_battery);
    P_grid_opt = value(P_grid);
    SoC_opt = value(SoC(1:n));  % 取前24小时的
    
    % 绘制调度曲线
    figure('Position',[100 100 1200 800]);
    
    subplot(3,1,1);
    plot(1:n, load_profile, 'k-', 'LineWidth',2); hold on;
    plot(1:n, P_diesel_opt, 'r--');
    plot(1:n, solar_forecast, 'y:');
    plot(1:n, P_battery_opt, 'b-.');
    plot(1:n, P_grid_opt, 'g--');
    legend('负荷','柴油机','光伏','电池','主网');
    title('功率调度曲线');
    xlabel('时间(h)'); ylabel('功率(kW)');
    grid on;
    
    subplot(3,1,2);
    stairs(1:n, value(u_diesel), 'r-', 'LineWidth',2);
    title('柴油机启停状态');
    ylabel('状态'); ylim([-0.1 1.1]); grid on;
    
    subplot(3,1,3);
    plot(0:n, value(SoC), 'b-o', 'LineWidth',2);
    title('电池荷电状态');
    xlabel('时间(h)'); ylabel('SoC(%)');
    grid on;
    
    % 成本分析
    total_cost = value(Objective);
    fprintf('\n总运行成本: %.2f 元\n', total_cost);
    fprintf('柴油机成本: %.2f 元\n', value(diesel_cost));
    fprintf('电网交互成本: %.2f 元\n', value(grid_cost));
end

0.3. 第三阶段:进阶技巧与参数调优(第3周)

0.3.1. 第8天:大规模问题处理技巧

0.3.1.1. 上午(3小时):稀疏矩阵与向量化编程

8.1 为什么不能用for循环?(性能对比)

% 场景:创建1000个发电机的出力约束
n = 1000;
P = sdpvar(n,1);
P_max = rand(n,1);

%% 方法1:for循环(不推荐)
tic;
Constraints = [];
for i = 1:n
    Constraints = [Constraints, P(i) <= P_max(i)];
end
toc;  % 耗时约 0.5-2秒,内存占用高

%% 方法2:向量化(推荐)
tic;
Constraints = [P <= P_max];  % 一行代码
toc;  % 耗时约 0.01秒,内存占用低

% 性能对比:
% n=1000时,方法2快50-200倍
% n=10000时,方法1可能内存溢出,方法2依然流畅

8.2 稀疏矩阵在电力系统中的应用

%% 场景:IEEE 118节点系统(大规模潮流约束)

% 导入系统数据(导纳矩阵)
load('case118.mat');  % 包含bus, branch, gen等结构体

nb = 118;  % 节点数
ng = 54;   % 发电机数

% 决策变量
P_gen = sdpvar(ng,1);
V = sdpvar(nb,1);  % 电压幅值

% 构建节点功率平衡(稀疏方式)
% 传统稠密矩阵:A = zeros(nb,nb); 占用内存 118*118*8 ≈ 105KB
% 稀疏矩阵:A = sparse(nb,nb); 占用内存 < 10KB

% 导纳矩阵G和B通常是稀疏的
G = sparse(gen_G_matrix);  % 电导矩阵
B = sparse(gen_B_matrix);  % 电纳矩阵

% 功率注入表达式(向量化计算)
P_inj = V.*(G*V) + V.*(B*V);  % 节点注入功率

% 负荷向量
P_load = bus(:,3);  % 第3列是负荷

% 功率平衡(稀疏约束)
Constraints = [P_inj == P_load + A_gen*P_gen];  % A_gen是稀疏的关联矩阵

8.3 参数化的约束生成

%% 场景:不同场景下的鲁棒优化

% 定义场景数
n_scenarios = 100;
P_load = sdpvar(n_scenarios,1);  % 每个场景下的负荷

% 基础约束
Constraints = [];
for s = 1:n_scenarios
    % 从数据文件读取场景s的负荷
    load_data = load(['scenario_',num2str(s),'.mat']);
    
    % 参数化约束:根据不同场景调整约束右端项
    Constraints = [Constraints, 
        P_gen(s) + P_grid(s) == load_data.P_load];
end

% 等价的高效写法(如果需要):
% 使用cell数组预先生成所有约束
constraint_cell = cell(n_scenarios,1);
for s = 1:n_scenarios
    constraint_cell{s} = (P_gen(s) + P_grid(s) == load_data.P_load);
end
Constraints = [constraint_cell{:}];

0.3.1.2. 下午(3小时):求解器日志解读与性能调优

8.4 Gurobi日志深度解读

典型日志输出分析:

Optimize a model with 100 rows, 200 columns and 500 nonzeros
Model fingerprint: 0x12345678
Variable types: 150 continuous, 50 integer (50 binary)
Coefficient statistics:
  Matrix range     [1e+00, 1e+03]
  Objective range  [1e-01, 2e+02]
  Bounds range     [1e+00, 1e+00]
  RHS range        [1e+02, 1e+03]
Presolve removed 25 rows and 50 columns (presolve time = 0.05s)...

【解读】
- 100 rows = 100个约束
- 200 columns = 200个变量
- 500 nonzeros = 500个非零系数(稀疏度=500/(100*200)=2.5%)
- Presolve removed:预求解消去了25个冗余约束和50个变量
- 50 binary:50个二元变量 → MILP问题

Presolve time: 0.12s
Presolved: 75 rows, 150 columns, 375 nonzeros

Explored 0 nodes (0 simplex iterations) in 0.05 seconds
Thread count was 8 (of 8 available processors)

Solution count 2: 1234.5 1567.8
Optimal solution found (tolerance 1.00e-04)
Best objective 1.234500000000e+03, best bound 1.234500000000e+03, gap 0.0000%

【解读】
- Explored 0 nodes:根节点就找到了最优解(幸运!)
- Solution count 2:找到2个可行解
- gap 0.0000%:最优性间隙为0,证明全局最优
- Best objective = Best bound:上下界重合,最优性得证

8.5 性能调优实战案例

%% 场景:混合整数规划求解太慢

% 问题:机组组合(Unit Commitment),1440个二元变量
% 目标:24小时调度,最小化成本

% 初始设置(慢,需600秒)
ops_slow = sdpsettings('solver','gurobi');
% optimize(...)  % 600秒

% 调优后设置(快,仅需45秒)
ops_fast = sdpsettings(...
    'solver','gurobi',...
    'gurobi.MIPGap',0.005,...              % 接受0.5%间隙
    'gurobi.Heuristics',0.8,...            % 加强启发式
    'gurobi.VarBranch',3,...               % 最大冲突分支策略
    'gurobi.CutPasses',5,...               % 增加割平面迭代
    'gurobi.Presolve',2,...                % 最高级预求解
    'gurobi.NumFocus',1,...                % 侧重找到可行解
    'gurobi.Threads',12);                 % 使用更多线程

% 性能提升原理:
% 1. MIPGap放宽:从默认0.01%到0.5%,减少枚举
% 2. Heuristics加强:更快找到优质初始解
% 3. VarBranch策略:选择对目标影响大的变量分支
% 4. Cuts增加:收紧松弛问题,更接近整数解
% 5. 并行加速:12线程 vs 4线程,理论加速3倍(实际约2.5倍)

0.3.2. 第9天:不可行性诊断与模型修复

0.3.2.1. 上午(3小时):冲突约束检测

9.1 使用conflict函数定位矛盾

%% 场景:模型无可行解,但不知道哪个约束冲突

% 构造一个故意矛盾的模型
x = sdpvar(1);
C1 = [x >= 5];
C2 = [x <= 3];
C3 = [x == 4];  % 与前两个矛盾
Constraints = [C1, C2, C3];

% 求解
result = optimize(Constraints, x, sdpsettings('solver','gurobi'));

% 检测冲突
if result.problem == 1  % 无可行解
    [conflict_constraints, conflict_flags] = conflict(Constraints);
    
    % conflict_flags显示哪些约束参与冲突
    disp('冲突约束索引:');
    find(conflict_flags)
    
    % 详细分析
    analyze(conflict_constraints)
end

% 输出示例:
% Conflict set size 2
% Constraint 1:  x >= 5
% Constraint 2:  x <= 3
% 说明这两个约束直接矛盾,C3是冗余的

9.2 软约束(Soft Constraints)与罚函数

%% 场景:负荷平衡约束可能无法满足,允许少量切负荷

P_gen = sdpvar(10,1);
P_load = ones(10,1)*50;  % 预测负荷

% 硬约束(可能导致无解)
% Constraints = [sum(P_gen) == sum(P_load)];  % 可能不可行

% 软约束(允许偏差,但惩罚)
violation = sdpvar(1);  % 偏差变量
penalty_weight = 1000;  % 惩罚系数

% 修改约束
Constraints = [
    sum(P_gen) + violation == sum(P_load),  % 允许偏差
    violation >= 0
];

% 目标函数增加惩罚项
Base_Cost = sum(P_gen);
Penalty_Cost = penalty_weight * violation;
Objective = Base_Cost + Penalty_Cost;

% 求解后会优先满足硬约束,软约束尽量满足
optimize(Constraints, Objective, sdpsettings('solver','gurobi'));

0.3.2.2. 下午(3小时):数值稳定性处理

9.3 数据缩放(Scaling)

%% 场景:模型系数范围太大导致数值问题

% 原始数据(极差巨大)
coeff = [1e-6, 1e3, 1e-3, 1e6];  % 矩阵范围[1e-6,1e6]

% Gurobi会警告:Matrix coefficient range exceeds 1e+06

% 解决方法:缩放
scale_factor = 1e3;
coeff_scaled = coeff / scale_factor;  % 新范围[1e-9,1e3]

% 求解后再反缩放结果
% 或在YALMIP中自动处理:
ops = sdpsettings('solver','gurobi','gurobi.ScaleFlag',1);
% ScaleFlag=1启用自动缩放

9.4 大M法的问题与改进

%% 场景:逻辑约束中的大M值选择

y = binvar(1);
x = sdpvar(1);

% 错误做法:M过大导致数值不稳定
M_bad = 1e6;
Constraints_bad = [x <= M_bad*y]; 

% 正确做法:M尽量紧
% 先估算x的最大可能值
x_max = 100;  % 基于业务逻辑估算
Constraints_good = [x <= x_max*y];

% 最佳做法:使用YALMIP的implies(自动避免大M)
Constraints_best = [implies(y==0, x==0), implies(y==1, x>=0)];
% YALMIP会生成Gurobi的indicator约束,无需手动大M

0.3.3. 第10天:结果后处理与可视化

0.3.3.1. 上午(2小时):提取对偶变量与灵敏度分析

10.1 对偶变量(Shadow Price)的经济学意义

%% 场景:电力系统节点电价计算

% 节点功率平衡约束
P_inj = sdpvar(n,1);  % 节点注入
P_load = bus(:,3);    % 节点负荷
Gen_incidence = ...;  % 发电机-节点关联矩阵

% 约束
Constraints = [P_inj == P_load + Gen_incidence*P_gen];

% 求解
optimize(Constraints, Cost, sdpsettings('solver','gurobi'));

% 提取对偶变量(节点电价)
if result.problem == 0
    nodal_price = dual(Constraints);  % 每个节点的影子价格
    % 对偶变量表示该节点每增加1MW负荷,总成本增加量($/MW)
    
    % 绘制电价地图
    figure;
    scatter(bus(:,2), bus(:,3), 100, nodal_price, 'filled');
    colorbar;
    title('节点电价分布');
    xlabel('经度'); ylabel('纬度');
end

10.2 参数灵敏度分析

%% 场景:天然气价格变化对机组调度影响

gas_price_range = 2:0.5:5;  % 天然气价格范围($/MMBtu)
total_cost_curve = zeros(size(gas_price_range));

for i = 1:length(gas_price_range)
    % 重新计算目标函数(系数含gas_price)
    gas_price = gas_price_range(i);
    Fuel_Cost = gas_price * Heat_Rate * P_gen;
    
    % 求解
    optimize(Constraints, Fuel_Cost, ops);
    
    if result.problem == 0
        total_cost_curve(i) = value(Fuel_Cost);
    end
end

% 绘制成本曲线
plot(gas_price_range, total_cost_curve, '-o');
xlabel('天然气价格 ($/MMBtu)');
ylabel('总运行成本 ($)');
title('价格灵敏度分析');
grid on;

0.3.3.2. 下午(2小时):结果可视化高级技巧

10.3 Gantt图(调度问题必备)

%% 场景:5台机组24小时启停计划

% 求解后得到u_on矩阵(5台×24小时)
u_on_opt = value(u_on);  % 5×24的二元矩阵

% 绘制Gantt图
figure('Position',[100 100 1200 400]);
y_tics = 1:5;
for i = 1:5
    on_periods = find(u_on_opt(i,:)==1);
    if ~isempty(on_periods)
        for t = on_periods
            x_start = t-1;
            y_bottom = i-0.3;
            rectangle('Position',[x_start,y_bottom,1,0.6],...
                     'FaceColor',[0.2 0.6 0.9],...
                     'EdgeColor','none');
        end
    end
end

xlim([0 24]);
ylim([0.5 5.5]);
set(gca,'YTick',y_tics,'YTickLabel',{'G1','G2','G3','G4','G5'});
xlabel('时间 (h)');
ylabel('机组');
title('机组启停计划甘特图');
grid on;

10.4 3D曲面图(展示多参数影响)

%% 场景:电池容量 vs 光伏容量 对系统成本影响

battery_cap_range = 0:10:100;  % 电池容量范围
solar_cap_range = 0:5:50;      % 光伏容量范围
cost_matrix = zeros(length(battery_cap_range), length(solar_cap_range));

for i = 1:length(battery_cap_range)
    for j = 1:length(solar_cap_range)
        % 重新设置容量参数
        battery_capacity = battery_cap_range(i);
        solar_capacity = solar_cap_range(j);
        
        % 更新约束(电池容量约束)
        Constraints = [SoC <= battery_capacity/1000, ...];
        
        % 求解
        optimize(Constraints, Objective, ops);
        
        if result.problem == 0
            cost_matrix(i,j) = value(Objective);
        end
    end
end

% 绘制3D曲面
[B,S] = meshgrid(solar_cap_range, battery_cap_range);
surf(B,S,cost_matrix/1e6);
xlabel('光伏容量 (MW)');
ylabel('电池容量 (MWh)');
zlabel('年化成本 (百万元)');
title('容量配置优化分析');
colorbar;
shading interp;  % 平滑着色

0.4. 第四阶段:综合项目实战(第4周)

0.4.1. 第11天:电力系统——机组组合问题(Unit Commitment)

0.4.1.1. 全天(6小时):IEEE 30节点系统UC问题

%% IEEE 30节点系统机组组合问题(完整工业级代码)

clear; clc; close all;

%% 1. 系统数据加载
% 数据包括:机组参数、负荷曲线、网络参数
load('case30data.mat');  % 假设已准备好

n_gen = 6;    % 6台发电机
n_hour = 24;  % 24小时调度

% 机组参数(通常从Excel读取)
gen_param = [
    10 20 100 50 0.1 0.2 5 100;  % [Pmin Pmax a b c start_cost min_down min_up]
    20 50 150 80 0.08 0.15 8 120;
    15 35 120 60 0.09 0.18 6 110;
    12 40 130 70 0.11 0.22 7 130;
    18 45 140 75 0.12 0.24 9 140;
    25 60 160 90 0.13 0.26 10 150
];

P_min = gen_param(:,1);
P_max = gen_param(:,2);
cost_a = gen_param(:,3);
cost_b = gen_param(:,4);
cost_c = gen_param(:,5);
start_cost = gen_param(:,6);
min_down = gen_param(:,7);
min_up = gen_param(:,8);

% 24小时负荷曲线(MW)
load_curve = 100*[2.4 2.3 2.2 2.1 2.0 2.1 2.3 2.8 3.2 3.5 3.6 3.4 3.2 3.0 3.1 3.3 3.5 3.8 3.6 3.4 3.2 2.9 2.6 2.5];

%% 2. 决策变量定义
P = sdpvar(n_gen, n_hour);      % 发电功率
u = binvar(n_gen, n_hour);      % 启停状态
v_start = binvar(n_gen, n_hour); % 启动变量
v_shutdown = binvar(n_gen, n_hour); % 停机变量

%% 3. 约束条件
Constraints = [];

% 3.1 功率平衡(每个小时)
for t = 1:n_hour
    Constraints = [Constraints, 
        sum(P(:,t)) == load_curve(t)];
end

% 3.2 机组出力限制
for t = 1:n_hour
    for g = 1:n_gen
        Constraints = [Constraints,
            P(g,t) >= P_min(g)*u(g,t),
            P(g,t) <= P_max(g)*u(g,t)
        ];
    end
end

% 3.3 最小启停时间约束(逻辑复杂,需仔细)
% 假设初始状态:已运行3小时
initial_status = ones(n_gen,1);
initial_runtime = 3*ones(n_gen,1);

% 启动/停机变量定义
for g = 1:n_gen
    for t = 1:n_hour
        if t == 1
            Constraints = [Constraints,
                v_start(g,t) >= u(g,t) - initial_status(g),
                v_shutdown(g,t) >= initial_status(g) - u(g,t)
            ];
        else
            Constraints = [Constraints,
                v_start(g,t) >= u(g,t) - u(g,t-1),
                v_shutdown(g,t) >= u(g,t-1) - u(g,t)
            ];
        end
        Constraints = [Constraints,
            v_start(g,t) <= u(g,t),
            v_shutdown(g,t) <= 1 - u(g,t)
        ];
    end
end

% 最小运行/停机时间
for g = 1:n_gen
    for t = 1:n_hour
        % 最小运行时间
        for tau = max(1,t-min_up(g)+1):t
            Constraints = [Constraints,
                u(g,t) <= sum(v_start(g,tau:t)) + (initial_runtime(g) >= min_up(g))];
        end
        
        % 最小停机时间
        for tau = max(1,t-min_down(g)+1):t
            Constraints = [Constraints,
                1-u(g,t) <= sum(v_shutdown(g,tau:t)) + (initial_status(g)==0)];
        end
    end
end

%% 4. 目标函数
Generation_Cost = 0;
Start_Cost = 0;

for g = 1:n_gen
    for t = 1:n_hour
        Generation_Cost = Generation_Cost + ...
            cost_c(g)*P(g,t)^2 + cost_b(g)*P(g,t) + cost_a(g)*u(g,t);
        Start_Cost = Start_Cost + start_cost(g)*v_start(g,t);
    end
end

Objective = Generation_Cost + Start_Cost;

%% 5. 求解设置与运行
ops = sdpsettings(...
    'solver','gurobi',...
    'verbose',1,...
    'gurobi.MIPGap',0.01,...          % 1%间隙
    'gurobi.TimeLimit',1800,...       % 30分钟
    'gurobi.MIPFocus',1,...           % 重点找可行解
    'gurobi.Heuristics',0.8);        % 强化启发式

% 添加初始解(暖启动)
% 假设全部机组以最小出力运行
P_init = repmat(P_min, 1, n_hour);
u_init = ones(n_gen, n_hour);
ops = sdpsettings(ops,'gurobi.Start',struct('P',P_init,'u',u_init));

% 求解
tic;
result = optimize(Constraints, Objective, ops);
solve_time = toc;

%% 6. 结果分析与验证
if result.problem == 0
    P_opt = value(P);
    u_opt = value(u);
    v_start_opt = value(v_start);
    
    total_cost = value(Objective);
    
    fprintf('\n========== 机组组合求解成功 ==========\n');
    fprintf('求解时间: %.2f 秒\n', solve_time);
    fprintf('总成本: $%.2f\n', total_cost);
    
    % 绘制调度曲线
    figure('Position',[100 100 1400 800]);
    
    % 发电堆叠图
    subplot(2,1,1);
    area(1:n_hour, P_opt', 'LineStyle','none');
    hold on;
    plot(1:n_hour, load_curve, 'k-', 'LineWidth',2);
    legend('G1','G2','G3','G4','G5','G6','负荷');
    title('机组出力曲线');
    xlabel('时间(h)'); ylabel('功率(MW)');
    grid on;
    
    % 启停状态图
    subplot(2,1,2);
    imagesc(1:n_hour, 1:n_gen, u_opt);
    set(gca,'YTick',1:n_gen,'YTickLabel',{'G1','G2','G3','G4','G5','G6'});
    xlabel('时间(h)'); ylabel('机组');
    title('启停状态(蓝色=运行)');
    colorbar;
    
    % 成本构成分析
    gen_cost_val = value(Generation_Cost);
    start_cost_val = value(Start_Cost);
    
    figure;
    pie([gen_cost_val, start_cost_val], {'发电成本','启停成本'});
    title('成本构成');
else
    error('机组组合求解失败,错误代码: %d', result.problem);
end

0.4.2. 第12天:控制工程——模型预测控制(MPC)

0.4.2.1. 全天(6小时):MPC完整实现

%% 线性MPC控制器设计(以双积分系统为例)

% 系统模型:x(k+1) = A*x(k) + B*u(k)
% 状态:x = [位置;速度]
% 控制输入:u = 加速度

%% 1. 系统参数
Ts = 0.1;  % 采样时间(s)
A = [1 Ts; 0 1];  % 离散状态矩阵
B = [0.5*Ts^2; Ts];  % 离散输入矩阵

nx = 2;  % 状态数
nu = 1;  % 控制输入数

% 约束
u_min = -5; u_max = 5;  % 加速度限制
x_max = [10; 5];        % 位置和速度限制

% MPC参数
N = 20;  % 预测时域
Q = eye(nx);  % 状态权重
R = 0.1;      % 输入权重

%% 2. 决策变量定义
X = sdpvar(nx, N+1);  % 预测时域内所有状态
U = sdpvar(nu, N);    % 预测时域内所有输入

x0 = sdpvar(nx,1);    % 当前状态(已知参数)

%% 3. MPC模型约束
Constraints = [];

% 初始条件
Constraints = [X(:,1) == x0];

% 系统动力学(对每个预测步)
for k = 1:N
    Constraints = [Constraints, 
        X(:,k+1) == A*X(:,k) + B*U(:,k)];
end

% 状态约束
for k = 1:N+1
    Constraints = [Constraints, 
        -x_max <= X(:,k) <= x_max];
end

% 输入约束
for k = 1:N
    Constraints = [Constraints, 
        u_min <= U(:,k) <= u_max];
end

%% 4. 目标函数
Objective = 0;
for k = 1:N
    Objective = Objective + X(:,k)'*Q*X(:,k) + R*U(:,k)^2;
end
% 终端代价
Objective = Objective + X(:,N+1)'*Q*X(:,N+1);

%% 5. 创建MPC求解器(离线编译)
% 将x0作为参数
Controller = optimizer(Constraints, Objective, sdpsettings('solver','gurobi'), x0, U(:,1));

%% 6. 仿真闭环控制
sim_time = 50;  % 仿真步数
x_current = [5; 0];  % 初始状态[位置;速度]
x_history = zeros(nx, sim_time);
u_history = zeros(nu, sim_time);

for t = 1:sim_time
    % 求解MPC(在线计算)
    u_opt = Controller{x_current};
    
    % 应用第一个控制输入
    u_current = value(u_opt);
    
    % 记录
    x_history(:,t) = x_current;
    u_history(:,t) = u_current;
    
    % 模拟系统演化(真实系统)
    x_current = A*x_current + B*u_current;
    
    % 可视化
    clf;
    subplot(2,1,1);
    plot(1:t, x_history(1,1:t), 'b-', 'LineWidth',2);
    hold on;
    plot(t, x_current(1), 'ro');
    title('位置轨迹'); xlabel('步数'); ylabel('位置');
    grid on;
    
    subplot(2,1,2);
    stairs(1:t, u_history(:,1:t), 'r-', 'LineWidth',2);
    title('控制输入'); xlabel('步数'); ylabel('加速度');
    grid on;
    ylim([u_min u_max]);
    
    drawnow;
    pause(0.1);
end

%% 7. 性能分析
figure;
subplot(2,1,1);
plot(x_history(1,:), x_history(2,:), 'b-', 'LineWidth',2);
title('状态相轨迹');
xlabel('位置'); ylabel('速度');
grid on;

subplot(2,1,2);
plot(u_history, 'r-', 'LineWidth',2);
title('控制输入时序');
xlabel('步数'); ylabel('加速度');
grid on;

0.4.3. 第13天:路径规划与旅行商问题(TSP)

0.4.3.1. 全天(6小时):TSP建模与求解

%% 旅行商问题(TSP)—— 经典组合优化

% 问题:访问10个城市,每个城市一次,返回起点,路程最短

% 城市坐标
cities = [
    0 0; 10 0; 5 8.66; 15 8.66; 20 0;
    25 8.66; 30 0; 35 8.66; 40 0; 45 8.66
];
n = size(cities,1);

% 计算距离矩阵D(i,j)
D = zeros(n,n);
for i = 1:n
    for j = 1:n
        D(i,j) = norm(cities(i,:) - cities(j,:));
    end
end

%% 1. Miller-Tucker-Zemlin (MTZ) 建模法
% 决策变量:x(i,j)=1表示路径i→j
x = binvar(n,n,'full');  % 'full'表示非对称

% 辅助变量:u(i)用于消除子回路
u = sdpvar(n,1);

% 约束
Constraints = [];

% 每个城市恰好一次入边和出边
Constraints = [Constraints, sum(x,1)' == 1];  % 出度=1
Constraints = [Constraints, sum(x,2) == 1];   % 入度=1

% 消除子回路(MTZ约束)
M = n;  % 大M
for i = 2:n
    for j = 2:n
        if i ~= j
            Constraints = [Constraints, u(i) - u(j) + M*x(i,j) <= M-1];
        end
    end
end

% 固定起点(城市1)
Constraints = [Constraints, x(1,1) == 0];  % 不自我连接

% 目标函数
Objective = sum(sum(D.*x));

%% 2. 求解(TSP是MILP,Gurobi强项)
ops = sdpsettings(...
    'solver','gurobi',...
    'verbose',1,...
    'gurobi.MIPGap',0.001,...      % 0.1%精度
    'gurobi.Heuristics',0.8,...    % 强化启发式
    'gurobi.CutPasses',5);         % 加强割平面

result = optimize(Constraints, Objective, ops);

%% 3. 结果可视化
if result.problem == 0
    x_opt = value(x);
    
    % 提取路径
    tour = zeros(1,n+1);
    tour(1) = 1;  % 起点
    for i = 2:n+1
        next_city = find(x_opt(tour(i-1),:) > 0.5);
        tour(i) = next_city;
    end
    
    % 绘制路径图
    figure('Position',[100 100 800 600]);
    plot(cities(:,1), cities(:,2), 'ko', 'MarkerSize',10, 'LineWidth',2);
    hold on;
    
    % 绘制路线
    plot(cities(tour,1), cities(tour,2), 'b-', 'LineWidth',2);
    plot([cities(tour(end),1), cities(tour(1),1)],...
         [cities(tour(end),2), cities(tour(1),2)], 'b-', 'LineWidth',2);
    
    % 标注城市
    for i = 1:n
        text(cities(i,1), cities(i,2)+1, sprintf('C%d',i),...
            'HorizontalAlignment','center');
    end
    
    title(sprintf('TSP最优路径 (距离=%.2f)', value(Objective)));
    xlabel('X坐标');
    ylabel('Y坐标');
    grid on;
    axis equal;
    
    % 绘制时间序列(如果是动态TSP)
    figure;
    plot(1:n+1, tour, 'b-o', 'LineWidth',2);
    yticks(1:n);
    title('访问顺序');
    xlabel('步骤');
    ylabel('城市编号');
    grid on;
else
    error('TSP求解失败');
end

%% 4. 对比:使用Gurobi的预热启动(Warm Start)
% 提供初始可行解(如最近邻启发式)
initial_tour = nearest_neighbor_tour(cities); % 自定义函数
x_initial = zeros(n,n);
for k = 1:n-1
    x_initial(initial_tour(k), initial_tour(k+1)) = 1;
end
x_initial(initial_tour(n), initial_tour(1)) = 1;

% 设置初始解
ops_warm = sdpsettings(ops, 'gurobi.Start', struct('x',x_initial));

% 再次求解
tic;
result_warm = optimize(Constraints, Objective, ops_warm);
time_warm = toc;

fprintf('\n预热启动效果:\n');
fprintf('原求解时间: %.2f秒\n', result.solverinfo.solvetime);
fprintf('预热后时间: %.2f秒\n', time_warm);
fprintf('加速比: %.2f%%\n', (1-time_warm/result.solverinfo.solvetime)*100);

0.4.4. 第14天:考试与项目答辩

0.4.4.1. 全天(6小时):模拟项目答辩

14.1 答辩题目(随机抽取)

题目1:微电网调度优化
- 需求:设计一个含5台柴油发电机、2个储能、光伏和负荷的微电网日前调度模型
- 约束:功率平衡、机组爬坡、储能动力学、旋转备用
- 目标:最小化运行成本
- 要求:提交代码、结果曲线、经济性分析报告

题目2:无人机路径规划
- 需求:5架无人机从基地出发,访问20个目标点,返回基地
- 约束:每架无人机航程<50km,避障(禁飞区),时间窗
- 目标:最小化总航程
- 要求:3D可视化路径,冲突检测分析

题目3:智能制造排程
- 需求:10台机器,20个订单,每个订单多道工序
- 约束:机器能力、工序先后、交付时间窗
- 目标:最小化延迟交付惩罚
- 要求:Gantt图,机器利用率分析

14.2 评分标准

维度 权重 评分细则
模型正确性 40% 约束完整、逻辑正确、变量定义合理
代码质量 30% 结构化、注释清晰、矩阵化(无for循环)
结果分析 20% 可视化专业、灵敏度分析、经济性评估
创新性 10% 额外功能(如鲁棒性、多目标)

14.3 项目报告模板

%% 项目报告生成脚本(自动生成Word/PDF)

% 1. 设置报告模板
import mlreportgen.report.*;
import mlreportgen.dom.*;

rpt = Report('optimization_project','pdf');

% 2. 添加标题页
tp = TitlePage();
tp.Title = '微电网经济调度优化项目';
tp.Author = '学习者';
tp.Date = date;
add(rpt,tp);

% 3. 添加问题描述
chapter1 = Chapter('问题描述');
add(chapter1, Paragraph('本研究考虑一个含...'));
add(rpt, chapter1);

% 4. 添加数学模型
chapter2 = Chapter('数学模型');
eq1 = FormalEq('$min \sum_{t=1}^{24} \sum_{g=1}^{6} (a_g P_{g,t}^2 + b_g P_{g,t} + c_g u_{g,t})$');
add(chapter2, eq1);
add(rpt, chapter2);

% 5. 添加代码
chapter3 = Chapter('MATLAB实现');
code = ProgrammaticCode(which('microgrid_uc.m'));
add(chapter3, code);
add(rpt, chapter3);

% 6. 添加结果图
chapter4 = Chapter('结果分析');
fig = Figure();
fig.Snapshot = 'results_figure1.png';
add(chapter4, fig);
add(rpt, chapter4);

% 7. 生成报告
close(rpt);
rptview(rpt);

0.5. 第五阶段:避坑指南与进阶学习(第4周补充)

0.5.1. 第15天:新手必踩的100个坑(精选TOP 20)

0.5.1.1. 上午(3小时):代码级错误

TOP 1-5:变量类型混淆

% ❌ 错误1:数值变量和sdpvar混用
x = sdpvar(1);
if x > 0  % 错误!x是符号变量,不能用于逻辑判断
    y = 1;
end

% ✅ 正确:用YALMIP的逻辑约束
y = binvar(1);
Constraints = [implies(x>=0, y==1), implies(x<0, y==0)];

% ❌ 错误2:赋值与约束混淆
x = sdpvar(1);
x = 5;  % 这是赋值,不是约束!x不再是sdpvar

% ✅ 正确:
Constraints = [x == 5];

% ❌ 错误3:使用非线性函数
x = sdpvar(1);
obj = sin(x);  % 错误!YALMIP不支持sin

% ✅ 正确:用分段线性近似
obj = pwlf(x, [-pi -pi/2 0 pi/2 pi], [-1 0 1 0 -1]);  % 近似sin(x)

% ❌ 错误4:矩阵乘法维度错误
x = sdpvar(3,1);
A = rand(2,3);
obj = x'*A*x;  % 错误!x'是1×3,A是2×3

% ✅ 正确:
obj = x'*(A'*A)*x;  % A'*A是3×3
% 或
obj = sum((A*x).^2);

% ❌ 错误5:未初始化的约束数组
Constraints = [];  % 正确初始化
for i = 1:10
    Constraints = [Constraints, x(i) <= i];  % 逐个添加
end
% 如果忘记初始化:Constraints未定义,报错

TOP 6-10:求解器设置错误

% ❌ 错误6:求解器名称拼写错误
ops = sdpsettings('solver','gurobi');  % 正确
ops = sdpsettings('solver','Gurobi');  % 错误!大小写敏感

% ❌ 错误7:参数名称错误
ops = sdpsettings('solver','gurobi','MIPGap',0.01);  % 错误!应加前缀
% ✅ 正确:
ops = sdpsettings('solver','gurobi','gurobi.MIPGap',0.01);

% ❌ 错误8:设置了求解器不支持的参数
ops = sdpsettings('solver','gurobi','barrier_iterations',100);
% Gurobi没有barrier_iterations参数,正确的是'gurobi.BarIterLimit'

% ❌ 错误9:时间单位混淆
ops = sdpsettings('solver','gurobi','gurobi.TimeLimit',100);
% 单位是秒!设置为100以为100分钟,实际是100秒

% ❌ 错误10:verbose和OutputFlag混淆
sdpsettings('verbose',0)  % 抑制YALMIP输出
sdpsettings('gurobi.OutputFlag',0)  % 抑制Gurobi输出
% 两者都要设才能完全静默

0.5.1.2. 下午(3小时):建模级错误

TOP 11-15:约束建模错误

% ❌ 错误11:严格不等式
Constraints = [x > 0];  % 错误!MILP不支持严格不等式
% ✅ 正确:
epsilon = 1e-6;
Constraints = [x >= epsilon];  % 或x >= 0(如果允许零)

% ❌ 错误12:双向约束写错
% 意图:10 <= x <= 20
Constraints = [10 <= x <= 20];  % MATLAB解析错误
% ✅ 正确:
Constraints = [10 <= x, x <= 20];

% ❌ 错误13:非凸二次约束
x = sdpvar(2,1);
Constraints = [x'*[1 0;0 -1]*x >= 0];  % 非凸!Gurobi报错
% 解决方案:用Cplex或Bonmin求解器,或改写约束

% ❌ 错误14:循环约束索引错误
x = sdpvar(10,1);
Constraints = [];
for i = 1:10
    Constraints = [Constraints, x(i) <= x(i+1)];  % i+1=11越界!
end
% ✅ 正确:
for i = 1:9
    Constraints = [Constraints, x(i) <= x(i+1)];
end

% ❌ 错误15:使用绝对值导致非线性
x = sdpvar(1);
obj = abs(x);  % 这不是线性!
% ✅ 正确:引入辅助变量
y = sdpvar(1);
Constraints = [y >= x, y >= -x];
obj = y;  % 最小化y等价于最小化|x|

TOP 16-20:算法与理论错误

% ❌ 错误16:QP问题Q矩阵非正定
Q = [1 2; 2 1];  % 特征值=-1,3,非正定
x = sdpvar(2,1);
obj = x'*Q*x;  % Gurobi警告"Q matrix is not positive semi-definite"
% 解决方案:Q = 0.5*(Q+Q') + epsilon*eye(n); 强制对称正定

% ❌ 错误17:未检查求解状态直接使用结果
result = optimize(C,obj,ops);
x_opt = value(x);  % 如果求解失败,value返回NaN
% ✅ 正确:
if result.problem == 0
    x_opt = value(x);
else
    error('求解未成功,结果无效');
end

% ❌ 错误18:MILP问题设置了连续算法
ops = sdpsettings('solver','gurobi','gurobi.Method',0);  % 对MILP,Method只在根节点有效
% 对MILP,主要控制分支定界的是'MIPFocus'、'Heuristics'等

% ❌ 错误19:混合精度问题
% 数据:load = 100.000000000001(CSV导入产生的微小误差)
% 约束:sum(gen) == load
% 结果:可行解可能因数值误差被判为不可行
% ✅ 正确:
load = round(load, 6);  % 保留6位小数
% 或设置容差:
ops = sdpsettings('gurobi.FeasibilityTol',1e-5);

% ❌ 错误20:未使用暖启动重复求解
% 场景:滚动优化中,每次从头求解
for k = 1:100
    optimize(C,obj,ops);  % 每次都冷启动,慢!
end
% ✅ 正确:利用前次解
x_previous = [];
for k = 1:100
    if ~isempty(x_previous)
        ops.Start = x_previous;  % 暖启动
    end
    result = optimize(C,obj,ops);
    x_previous = value(x);  % 保存当前解
end


0.5.2. 第16天:进阶主题与前沿技术

0.5.2.1. 上午(3小时):鲁棒优化与分布鲁棒

16.1 经典鲁棒优化(Box Uncertainty)

%% 场景:需求不确定,在±10%范围内波动

P_load_nominal = 100;  % 标称负荷
uncertainty = 0.1;     % 10%波动

% 传统确定性优化
P_gen = sdpvar(1);
Constraints_det = [P_gen == P_load_nominal];
optimize(Constraints_det, P_gen^2);

% 鲁棒优化(最坏情况)
delta = sdpvar(1);  % 不确定性变量
Constraints_robust = [
    P_gen >= P_load_nominal + delta,  % 必须覆盖最坏情况
    -uncertainty*P_load_nominal <= delta <= uncertainty*P_load_nominal
];
optimize(Constraints_robust, P_gen^2);  % 最大化最坏情况下的性能

16.2 分布鲁棒优化(Wasserstein球)

%% 场景:基于历史数据的分布鲁棒

% 假设有100个历史负荷场景
load_scenarios = load_data_hist;  % 100×1向量

% Wasserstein球半径
epsilon = 0.1;

% 定义概率分布变量
p = sdpvar(100,1);
Constraints = [
    p >= 0,
    sum(p) == 1
];

% 构造分布鲁棒约束(较复杂,需对偶转换)
% YALMIP的distribute函数可简化
% 此处为概念性代码

0.5.2.2. 下午(3小时):多目标优化与帕累托前沿

16.3 加权和方法

%% 场景:成本 vs 排放 双目标

Cost = ...;   % 经济性目标
Emission = ...; % 环境目标

% 加权和
weights = linspace(0,1,11);  % 11个权重组合
pareto_front = zeros(2,length(weights));

for i = 1:length(weights)
    Objective = weights(i)*Cost + (1-weights(i))*Emission;
    optimize(Constraints, Objective, ops);
    
    pareto_front(1,i) = value(Cost);
    pareto_front(2,i) = value(Emission);
end

% 绘制帕累托前沿
plot(pareto_front(1,:), pareto_front(2,:), '-o');
xlabel('成本');
ylabel('排放');
title('帕累托前沿');
grid on;

16.4 epsilon约束法

%% 更精确的方法:epsilon约束

cost_target = linspace(min_cost, max_cost, 20);
emission_min = zeros(size(cost_target));

for i = 1:length(cost_target)
    Constraints_epsilon = [Constraints, Cost <= cost_target(i)];
    optimize(Constraints_epsilon, Emission, ops);
    emission_min(i) = value(Emission);
end

plot(cost_target, emission_min, '-x');
xlabel('成本上限');
ylabel('最小排放');
title('epsilon约束法帕累托前沿');

0.5.3. 第17天:工业界最佳实践

0.5.3.1. 上午(3小时):代码工程化

17.1 函数封装与模块化

%% 规范的优化项目结构

% 项目文件夹结构:
% microgrid_project/
%   ├── main.m                    % 主脚本
%   ├── data/
%   │   ├── case30.mat           % 系统数据
%   │   └── load_forecast.csv    % 负荷预测
%   ├── src/
%   │   ├── build_model.m        % 模型构建
%   │   ├── solve_model.m        % 求解器调用
%   │   ├── post_process.m       % 后处理
%   │   └── visual_results.m     % 可视化
%   └── results/
%       ├── figures/
%       └── logs/

%%% build_model.m 示例
function [Constraints, Objective, vars] = build_model(system_data, params)
% 构建微电网调度模型
% 输入:
%   system_data - 系统数据结构体
%   params - 参数结构体
% 输出:
%   Constraints - 约束集合
%   Objective - 目标函数
%   vars - 变量结构体

% 解包数据
n_gen = system_data.n_gen;
n_hour = params.n_hour;

% 定义变量
vars.P_gen = sdpvar(n_gen, n_hour);
vars.u_on = binvar(n_gen, n_hour);

% 构建约束(此处简化,实际更复杂)
Constraints = [];
Constraints = [Constraints, vars.P_gen >= 0];
Constraints = [Constraints, vars.P_gen <= system_data.P_max*vars.u_on];

% 目标函数
cost_coeff = system_data.cost_coeff;
Objective = sum(sum(cost_coeff(1)*vars.P_gen.^2 + ...
                    cost_coeff(2)*vars.P_gen + ...
                    cost_coeff(3)*vars.u_on));

end

17.2 错误处理与日志系统

%%% solve_model.m 示例(工业级错误处理)
function [result, vars_opt] = solve_model(Constraints, Objective, vars, ops)
% 求解模型并处理错误

% 创建日志文件
log_file = sprintf('solver_log_%s.txt', datestr(now,'yyyymmdd_HHMMSS'));
diary(log_file);

try
    % 求解
    result = optimize(Constraints, Objective, ops);
    
    % 检查求解状态
    switch result.problem
        case 0
            fprintf('求解成功!\n');
            % 提取结果
            vars_opt = extract_results(vars);
        case 1
            warning('模型无可行解');
            vars_opt = [];
            % 调用冲突分析
            conflict_analysis(Constraints);
        case 2
            warning('模型无界');
            vars_opt = [];
        otherwise
            error('求解失败,错误代码: %d', result.problem);
    end
    
catch ME
    % 捕获异常
    fprintf('求解过程中发生异常: %s\n', ME.message);
    vars_opt = [];
    result.problem = -1;
end

diary off;
end

%%% 辅助函数
function vars_opt = extract_results(vars)
% 递归提取所有变量的值
fields = fieldnames(vars);
for i = 1:length(fields)
    field = fields{i};
    if isa(vars.(field), 'sdpvar')
        vars_opt.(field) = value(vars.(field));
    elseif isstruct(vars.(field))
        vars_opt.(field) = extract_results(vars.(field));
    end
end
end

0.5.3.2. 下午(3小时):性能优化与并行计算

17.3 YALMIP + Parallel Computing Toolbox

%% 场景:多场景并行优化(场景间独立)

% 创建并行池
parpool(8);  % 启动8个worker

% 定义场景
scenarios = 1:50;

% 并行求解
results = parallel_fcn(@solve_single_scenario, scenarios);

% 关闭并行池
delete(gcp);

%%% 辅助函数
function result = solve_single_scenario(scenario_id)
% 每个worker独立求解一个场景

% 加载场景数据
load(['scenario_',num2str(scenario_id),'.mat']);

% 构建并求解模型(与主程序隔离)
Constraints = ...;
Objective = ...;
ops = sdpsettings('solver','gurobi','gurobi.OutputFlag',0);  % 抑制输出

result = optimize(Constraints, Objective, ops);
end

17.4 Gurobi分布式优化(Tuning Tool)

%% 使用Gurobi Tuning Tool自动调参

% 保存模型
savemodel(Constraints, Objective, 'model.mps');

% 调用gurobi调参工具
% 命令行:gurobi_cl TuneTimeLimit=3600 TuneCriterion=2 model.mps
% 在MATLAB中:
system('gurobi_cl TuneTimeLimit=3600 TuneCriterion=2 model.mps');

% 调参后生成.prm文件,加载使用
ops = sdpsettings('solver','gurobi','gurobi.Params','tuned.prm');

0.6. 附录:学习资源与工具链

0.6.1. A-1 必备资源清单

【软件安装】
- MATLAB R2024b(官网:mathworks.com)
- Gurobi 11.0(官网:gurobi.com,学术许可证免费)
- YALMIP(GitHub:yalmip/yalmip)

【文档教程】
- YALMIP Wiki:yalmip.github.io(最好的教材,所有函数说明)
- Gurobi Documentation:www.gurobi.com/documentation(参数详解)
- MATLAB Optimization Toolbox:mathworks.com/help/optim/

【书籍推荐】
- 《Convex Optimization》(Boyd & Vandenberghe)- 理论基石
- 《Optimization in Engineering》(Kirby)- 工程应用
- 《Model Predictive Control》(Rawlings & Mayne)- MPC圣经

【在线课程】
- Coursera: Discrete Optimization(含MILP)
- edX: Optimization Methods for Business Analytics
- B站:搜索"YALMIP教程"(有中文视频)

【论文复现】
- 电力系统:《Unit commitment in electric power systems》(Wood & Wollenberg)
- 路径规划:《A survey on the vehicle routing problem》
- MPC:《Model predictive control: theory and practice》

【社区支持】
- Stack Overflow: 标签[yalmip], [gurobi]
- YALMIP Google Groups: 提问和查看历史问题
- GitHub Issues: 报告bug和请求功能

0.6.2. A-2 自测题与答案

%% 自测题1:判断模型类型
% 根据以下目标函数和约束,判断问题类型(LP/MILP/QP/QCP)

% (1) 
% minimize x1 + 2*x2
% s.t. x1 + x2 >= 5
%      x1, x2 >= 0
% 答案:LP(线性目标,线性约束,连续变量)

% (2)
% minimize x1^2 + x2^2
% s.t. x1 + x2 >= 5
%      x1, x2 ∈ {0,1}
% 答案:非凸QP(二元变量+二次目标)→ Gurobi可处理但可能慢

% (3)
% minimize x1 + y1
% s.t. x1 + 2*y1 >= 3
%      x1 >= 0, y1 ∈ {0,1}
% 答案:MILP(线性混合整数)

% (4)
% minimize x1'*Q*x1 + c'*x1 + y1'*d
% s.t. ||x1||_2 <= 10
%      y1 ∈ {0,1}
% 答案:MIQCP(混合整数二次约束)

%% 自测题2:代码纠错
% 以下代码有3处错误,请找出并修正

x = sdpvar(5,1);
y = binvar(5,1);
Constraints = [];
for i = 1:5
    Constraints = [x(i) + y(i) <= 10];  % 错误1:覆盖而非追加
end
obj = sum(x.^2 + y);
ops = sdpsettings('solver','gurobi','MIPGap',0.01);  % 错误2:参数名
optimize(Constraints, obj, ops);
disp('最优值:', value(obj));  % 错误3:语法

% 修正后:
x = sdpvar(5,1);
y = binvar(5,1);
Constraints = [];
for i = 1:5
    Constraints = [Constraints, x(i) + y(i) <= 10];  % 追加
end
obj = sum(x.^2 + y);
ops = sdpsettings('solver','gurobi','gurobi.MIPGap',0.01);  % 加前缀
result = optimize(Constraints, obj, ops);
if result.problem == 0
    fprintf('最优值: %.4f\n', value(obj));  % 正确语法
end

%% 自测题3:建模练习
% 建模问题:仓库选址,最小化建设+运输成本
% 数据:
%  - 5个候选仓库位置,建设成本[100,150,120,90,110]万元
%  - 10个客户,需求量[30,20,15,25,18,22,30,12,28,35]吨
%  - 运输成本:距离(km)×0.5元/吨km
% 要求:写出完整MATLAB代码

% 答案框架:
clear;
% 数据
build_cost = [100,150,120,90,110];
demand = [30,20,15,25,18,22,30,12,28,35];
distance = rand(5,10)*50;  % 随机生成距离
transport_rate = 0.5;

% 变量
y = binvar(5,1);  % 选址
x = sdpvar(5,10); % 运输量

% 约束
C = [];
C = [C, sum(x,2) <= 1000*y];  % 只有建的仓库才能供应
C = [C, sum(x,1)' == demand]; % 满足需求
C = [C, x >= 0];

% 目标
Build_Cost = build_cost'*y;
Transport_Cost = sum(sum(distance*transport_rate.*x));
Objective = Build_Cost + Transport_Cost;

% 求解
optimize(C, Objective, sdpsettings('solver','gurobi'));
disp(value(Objective));

0.7. 结语:从入门到精通

恭喜你完成了这个万字学习路线图!回顾四周的学习:

  • 第1周:环境搭建 + LP/MILP/QP基础 → 掌握YALMIP语法
  • 第2周:经典模型实战 → 理解建模思维
  • 第3周:参数调优 + 调试技巧 → 解决实际问题
  • 第4周:综合项目 + 避坑指南 → 具备独立项目能力

下一步建议:

  1. 立即行动:明天就开始第1天的环境安装,不要拖延
  2. 项目驱动:结合你的研究方向,1个月内完成一个真实项目
  3. 代码复用:保存所有写过的代码,建立个人函数库
  4. 持续学习:关注YALMIP GitHub更新,每年Gurobi新版本都有性能提升
  5. 社区贡献:在Stack Overflow回答YALMIP问题,教学相长

记住:优化建模是艺术 + 科学的结合。多练习、多调试、多总结,你很快就能成为团队中的”优化专家”!

祝学习顺利!


文档信息:

  • 版本:v2.5(2025年12月更新)
  • 作者:基于YALMIP官方文档与工业实践整理
  • 许可证:CC BY-NC-SA 4.0(非商业用途可自由分享)

文章作者: 小白菜
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 小白菜 !
评论
  目录