1. 为什么需要手写微电网经济调度代码?
去年夏天,我在参与一个偏远地区微电网项目时,遇到了一个典型问题:现有商业软件无法处理当地特殊的柴油发电机与光伏混合供电约束。那天凌晨3点,当我第17次修改MATLAB脚本时突然意识到——真正搞懂微电网经济调度,必须亲手实现一次核心算法。
微电网经济调度的本质是在满足负荷需求的前提下,通过优化分布式电源(光伏、风机、柴油机等)和储能系统的运行状态,实现总运行成本最低。与主网调度不同,微电网需要处理更多不确定性因素:
- 可再生能源的间歇性(光伏出力在云层遮挡时的剧烈波动)
- 负荷预测误差(偏远地区用电行为更难建模)
- 旋转备用约束(柴油发电机最小稳定运行时间)
- 蓄电池的充放电效率非线性
商业软件如HOMER或PSCAD虽然提供可视化界面,但遇到下列情况时就会捉襟见肘:
- 需要添加自定义约束(如当地环保法规对柴油机启停的特殊限制)
- 算法对比测试(比较粒子群优化与传统线性规划的效果)
- 与SCADA系统深度集成(需直接调用优化模块)
关键认知:手写代码不是重复造轮子,而是为了获得"算法透明性"——你能准确知道每个约束是如何被处理的,这对调试实际系统异常至关重要。
2. 经济调度数学模型构建
2.1 目标函数设计
我们以24小时为调度周期,以1小时为时间分辨率,构建总运行成本最小化目标:
python复制def objective_rule(model):
return sum(
(a[i]*model.P[i,t]**2 + b[i]*model.P[i,t] + c[i]*model.u[i,t])
for i in model.G for t in model.T
) + sum(
shed_cost * model.Lshed[t] for t in model.T
)
其中:
a[i],b[i],c[i]是第i台发电机的成本系数(通常柴油机为二次函数)P[i,t]是发电机i在时段t的有功出力u[i,t]是启停状态二进制变量(0/1)Lshed[t]是时段t的负荷削减量(最后手段)
2.2 核心约束条件
2.2.1 功率平衡方程
python复制def power_balance_rule(model, t):
return sum(model.P[i,t] for i in model.G) + model.Ppv[t] == model.Load[t] - model.Lshed[t]
注意光伏出力Ppv[t]作为预测值输入,实际项目中需要留出±15%的调整裕度。
2.2.2 发电机运行约束
柴油发电机的独特约束需要特别注意:
python复制# 最小启停时间约束
def min_up_time_rule(model, i, t):
if t >= model.min_up[i]:
return sum(1 - model.u[i,k] for k in range(t-model.min_up[i],t)) <= 0
else:
return Constraint.Skip
2.2.3 蓄电池建模
蓄电池的充放电效率采用分段线性化处理:
python复制# 充电状态
model.Pch = Var(model.T, bounds=(0, Pch_max))
# 放电状态
model.Pdis = Var(model.T, bounds=(0, Pdis_max))
# 不能同时充放电
def storage_logic_rule(model, t):
return model.Pch[t] * model.Pdis[t] == 0
3. Pyomo实现技巧与坑点
3.1 模型构建加速
直接使用ConcreteModel()在变量超过500个时会出现明显延迟,推荐:
python复制model = AbstractModel()
# 先定义集合和参数
model.G = Set() # 发电机组
model.T = Set(ordered=True) # 时间段
# 延迟变量创建
def P_bounds_rule(model, i, t):
return (Pmin[i], Pmax[i])
model.P = Var(model.G, model.T, bounds=P_bounds_rule)
3.2 求解器选择策略
不同问题规模下的求解器选择经验:
- 50变量以内:
glpk(开源) - 50-1000变量:
cplex(商业)/cbc(开源) - 1000+变量:
gurobi(商业,对MIQP问题特别有效)
实测案例:一个含3台柴油机+光伏+储能的24时段调度问题:
- GLPK求解时间:142秒
- CBC求解时间:39秒
- Gurobi求解时间:7秒
3.3 典型报错处理
错误1:"Solver does not support quadratic objectives"
- 原因:未安装支持二次规划的求解器
- 解决:
pip install cplex或使用model.q = Suffix(direction=Suffix.MINIMIZE)
错误2:"No value for uninitialized NumericValue object"
- 触发场景:在定义约束时直接使用未初始化的参数
- 正确做法:
python复制model.Pmax = Param(model.G, initialize=lambda model, i: Pmax_data[i])
4. 实际项目中的增强策略
4.1 鲁棒优化实现
针对光伏预测误差,采用两阶段鲁棒优化:
python复制# 定义不确定集
model.delta_pv = Var(model.T, bounds=(-0.15, 0.15))
# 第二阶段补偿变量
model.P_comp = Var(model.G, model.T)
# 鲁棒约束
def robust_balance_rule(model, t):
return sum(model.P[i,t] + model.P_comp[i,t] for i in model.G) >= \
(Load[t] - (1+model.delta_pv[t])*Ppv[t])
4.2 与预测模块联动
实际项目中建议采用滚动优化框架:
- 每15分钟更新光伏出力预测(使用LSTM模型)
- 重新求解当前时段至调度周期末的优化问题
- 只执行当前时段的调度指令
4.3 结果可视化技巧
使用matplotlib绘制调度甘特图:
python复制import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(12,6))
for i in model.G:
ax.bar(model.T, [value(model.P[i,t]) for t in model.T],
bottom=bottom, label=f'Gen{i}')
bottom += [value(model.P[i,t]) for t in model.T]
ax.plot(model.T, [Load[t] for t in model.T], 'k--', label='Load')
5. 从仿真到实际部署
当代码需要集成到SCADA系统时,必须处理:
- 实时数据接口:通过OPC UA读取当前储能SOC状态
- 异常处理机制:当求解失败时自动切换至规则库策略
- 性能优化:将Pyomo模型预编译为
.nl文件,运行时直接热加载
一个实用的部署架构:
code复制[SCADA] ←OPC UA→ [调度服务器] ←Redis→ [优化引擎]
↑
[预测服务]
在青海某微电网项目中,这套Python实现的调度核心代码已稳定运行3年,平均降低运行成本12.7%。最关键的是,当出现柴油机异常停机时,我们能快速定位到约束条件中的最小出力参数需要调整——这正是手写代码带来的诊断优势。
