对比两个情景

目标。 量化基线与反事实情景之间的差异——总成本、装机结构、按载体的发电量、LMP。

结果 NetCDF 让并排比较变得简单:每个文件都是一个 xarray.Dataset,变量名一致,因此可以直接做减法和 groupby

准备

使用后缀参数运行两个情景 (参见 收紧碳排放上限):

cd examples/southeast_asia
python -m prepshot                           # -> output/baseline.nc
python -m prepshot --carbon_emission_limit=tight  # -> output/baseline_tight.nc

做法

import xarray as xr
import pandas as pd

base = xr.open_dataset("output/baseline.nc")
tight = xr.open_dataset("output/baseline_tight.nc")

# 1. Headline cost delta
cost_delta = float(tight.cost) - float(base.cost)
cost_delta_pct = cost_delta / float(base.cost) * 100
print(f"Cost delta: ${cost_delta:,.0f} ({cost_delta_pct:+.1f}%)")

# 2. Capacity-mix shift in 2030 (the year the cap binds)
registry = pd.read_csv("input/tech_registry.csv")
tech_to_carrier = dict(zip(registry["tech"], registry["carrier"]))

def by_carrier(ds, year):
    df = ds["install"].sel(year=year).to_dataframe().reset_index()
    df["carrier"] = df["tech"].map(tech_to_carrier)
    return df.groupby("carrier")["install"].sum()

compare = pd.concat({
    "baseline": by_carrier(base, 2030),
    "tight":    by_carrier(tight, 2030),
}, axis=1).fillna(0)
compare["delta"] = compare["tight"] - compare["baseline"]
compare = compare[compare.abs().max(axis=1) > 1]
print(compare.sort_values("delta", ascending=False).round(0))

# 3. LMP shift by zone
lmp_base = base["shadow_price_demand"].mean(["hour", "month"]).to_pandas()
lmp_tight = tight["shadow_price_demand"].mean(["hour", "month"]).to_pandas()
lmp_delta = lmp_tight - lmp_base
print("\nAverage LMP delta (USD/MWh, NPV):")
print(lmp_delta.round(4))

绘制装机重组

import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(7, 4))
compare["delta"].plot.barh(
    ax=ax,
    color=compare["delta"].apply(lambda v: "green" if v > 0 else "firebrick"),
)
ax.axvline(0, color="black", linewidth=0.5)
ax.set_xlabel("Capacity change vs. baseline in 2030 (MW)")
ax.set_title("Tighter carbon cap: capacity reshuffle")
fig.tight_layout()
plt.show()

常见陷阱

  • 维度不一致。 如果两个情景的 hourmonth 设置不同,``ds_a - ds_b`` 会因广播错误而失败。比较时请保持配置一致。

  • 净现值 vs 名义值。 ds.cost 已折现为净现值;``ds.gen`` 单位为每时段 MWh。不要在未除以 var_factor[year, zone] 进行单位转换的情况下混用。

  • 多情景批量处理。 当变体超过两个时,把模式提升为对后缀列表的字典推导:{s: xr.open_dataset(f"output/baseline_{s}.nc") for s in suffixes}

完整的端到端对比示例参见 ../examples/southeast_asia/SoutheastAsia (SoutheastAsia 笔记本的第 18-20 单元演示了这一模式)。