查看节点边际电价 (LMP)¶
目标。 读取 PREP-SHOT 的 LMP 输出,用以诊断起作用的约束、比较区域差异、回溯批发电价轨迹。
LMP 是节点功率平衡约束的对偶变量——在每个 (hour, month, year, zone) 处多供一兆瓦时需求的边际成本。自 v1.9.1 起,PREP-SHOT 将其作为 shadow_price_demand 写入结果 NetCDF。
做法¶
import xarray as xr
import pandas as pd
ds = xr.open_dataset("output/year.nc")
lmp = ds["shadow_price_demand"] # dims: (hour, month, year, zone)
# 1. Average LMP by zone and year
lmp_avg = lmp.mean(["hour", "month"]).to_pandas()
print(lmp_avg.round(4))
# 2. Maximum hourly LMP per zone (the most-stressed hour)
lmp_peak = lmp.max(["hour", "month"]).to_pandas()
print("\nPeak LMP by zone:")
print(lmp_peak.round(4))
# 3. LMPs for a specific zone over a representative day
profile = lmp.sel(zone="BA1", year=2025).mean("month").to_pandas()
profile.plot(figsize=(8, 3), title="Hourly LMP at BA1, 2025")
符号约定¶
shadow_price_demand 中的 LMP 当满足更多需求更昂贵时为正——这是 PyPSA、Switch、GenX 等模型采用的标准约定。PREP-SHOT 在写入 NetCDF 前会内部翻转 HiGHS 原始对偶值的符号 (原始值为负,因为需求平衡约束写作 demand - supply == 0)。
净现值 vs 实际年度价格¶
LMP 以 净现值折现的 USD/MWh 写入。如要恢复未折现的实际年度价格,需除以对应年份和区域的可变成本系数:
# var_factor[year, zone] = (1 + r)^(year_min - year) * (year_to_next - year)
# PREP-SHOT writes it to a sidecar; reconstruct from inputs:
discount = pd.read_csv("input/economic_discount_factor.csv")
# ... or pull it back from the model's params dict if you ran it
# programmatically.
对大多数分析 (区域间相对比较、峰值时段识别、规划期内趋势) 而言,NPV 折现是无害的,并能保持跨区域比较的可信度。
常见陷阱¶
MIP 求解不返回对偶值。 若 NetCDF 中缺少
shadow_price_demand,说明本次求解是 MIP (或不可行)。v1.9.1 的提取过程包在 try/except 中:会记录警告并省略该变量,而不会导致运行崩溃。储能区域会扭曲短期 LMP。 起作用的储能荷电状态约束会在某个小时表现为 LMP 尖峰,而在另一时刻表现为洼地 (能量套利)。不要把单一小时的 LMP 当作 "电价";应当按日或按周取平均。
需求曲线形状很重要。 PREP-SHOT 使用典型小时 / 月。所得 LMP 反映的是被采样的小时,而非完整的 8760 小时全年。少数几个典型峰值小时可能主导平均值。
底层约束 (prepshot/_model/demand.py 中的 power_balance_cons) 参见 Mathematical Notation。