# -*- coding: utf-8 -*-
"""
gen_dgcup2022a.py —— 电工杯 2022 A 题「高比例风电电力系统储能运行及配置分析」确定性真源模型
====================================================================================

赛题背景（真实，第十四届全国大学生电工数学建模竞赛 A 题）：
“碳中和”目标驱动下，未来电力系统必将是高比例可再生能源电力系统。可再生能源出力强随机
波动性导致系统功率实时平衡困难；储能被认为是保障系统功率实时平衡的有效手段。以高比例
风电电力系统为例，探究“供给侧”低碳化转型对系统运行经济性与可靠性的影响。

待研究系统含火电、风电、储能、负荷：
  * 火电机组 3 台，装机容量 1050 MW（最大 600/300/150 MW，最小 180/90/45 MW）。
  * 煤耗 F = aP^2 + bP + c（kg/h，P 为机组出力/MW）；运行维护成本按 0.5 倍煤耗成本计；
    碳捕集成本 = 碳排放量 × 碳捕集单价（0/60/80/100 元/t）。
  * 风电仅计运维成本 0.045 元/kWh；弃风损失 0.3 元/kWh。
  * 储能：单位功率成本 3000 元/kW，单位能量成本 3000 元/kWh，运维 0.05 元/kWh，寿命 10 年
    （平摊到每天 = 总投资 /10/365）。
  * 失负荷损失 8 元/kWh。系统单位供电成本 = 系统发电总成本 / 系统总负荷电量。
  * 日负荷最大 900 MW；风电渗透率 = 最大风电功率 / 最大负荷功率。
  * 七个子问题：Q1 无风最小成本运行；Q2 风电 300MW 替代机组3；Q3 风电 600MW 替代机组2；
    Q4 上述两场景在 4 种碳价下的单位供电成本；Q5 风电 900MW 替代机组 2、3 的失负荷与最小
    储能配置；Q6 风电替代容量递增的可靠性与经济性挑战；Q7 附件2 十五日（负荷/风电均 1200MW）
    替代机组 2、3 场景的功率平衡问题与解决方案。

重要声明：原题附录 1（某日风电/负荷归一化序列，96 点/15min）、附件 2（十五日序列）为官方
Excel 附件，本站无法下载。本脚本采用 SEED=2022 的【确定性合成】序列（负荷/风电形状由固定
公式 + 固定随机种子生成，纯标准库、零外部依赖），仅用于跑通建模方法、训练套路，非官方原题
数据。所有数字可由本文件独立复现。论文与图表中均已注明“练习用确定性合成数据”。

输出：
  - assets/problems/data/dgcup2022a.csv  （参数 + 各问关键结果面板）
  - 返回 D（dict），供范文附录与配图脚本独立复现全部权威数字。

用法：cd tools/ && python3 gen_dgcup2022a.py
"""
import os
import csv
import math
import random

SEED = 2022  # 版本锚定；序列由本种子确定性生成
HERE = os.path.dirname(os.path.abspath(__file__))
OUT_DIR = os.path.normpath(os.path.join(HERE, "..", "assets", "problems", "data"))

# ---------------- 系统 / 经济常数 ----------------
# 三台火电机组：最大/最小出力(MW)、煤耗系数 F=aP^2+bP+c(kg/h)、碳排放(kg/kWh)
UNITS = {
    1: dict(Pmax=600.0, Pmin=180.0, a=0.226, b=30.42, c=786.80, carbon=0.72),
    2: dict(Pmax=300.0, Pmin=90.0,  a=0.588, b=65.12, c=451.32, carbon=0.75),
    3: dict(Pmax=150.0, Pmin=45.0,  a=0.785, b=139.6, c=1049.50, carbon=0.79),
}
COAL_PRICE = 700.0     # 电煤价格 元/t
WIND_OM = 0.045        # 风电运维 元/kWh
STO_PC = 3000.0        # 储能单位功率成本 元/kW
STO_EC = 3000.0        # 储能单位能量成本 元/kWh
STO_OM = 0.05          # 储能运维 元/kWh
STO_LIFE = 10          # 储能寿命 年
CURT_COST = 0.3        # 弃风损失 元/kWh
LOSS_COST = 8.0        # 失负荷损失 元/kWh
ETA = 0.9              # 储能充放电效率（往返）
CARBON_PRICES = [0, 60, 80, 100]   # 单位碳捕集成本 元/t
LOAD_MAX = 900.0       # 日负荷最大功率 MW（Q1–Q6）
DT = 0.25              # 时间步长 h（15min）
N = 96                 # 每日点数


# ===================== 序列合成（确定性） =====================
def synth_load():
    """某日负荷归一化序列（96 点），晚高峰(20h)归一到 1.0，夜间谷值约 0.5。"""
    random.seed(SEED)
    raw = []
    for k in range(N):
        h = k * DT
        base = 0.45
        m1 = 0.13 * math.exp(-((h - 10.0) / 3.0) ** 2)     # 上午小峰
        m2 = 0.45 * math.exp(-((h - 20.0) / 2.6) ** 2)     # 晚高峰
        v = base + m1 + m2
        v += (random.random() - 0.5) * 0.02
        raw.append(v)
    vmax = max(raw)
    return [v / vmax for v in raw]


def synth_wind():
    """某日风电归一化序列（96 点）：凌晨(3h)大风归一到 1.0，傍晚(20h)偏弱约 0.2。"""
    random.seed(SEED + 7)
    raw = []
    for k in range(N):
        h = k * DT
        base = 0.07
        night = 0.95 * math.exp(-((h - 3.0) / 3.0) ** 2)      # 凌晨大风
        eve = 0.13 * math.exp(-((h - 20.0) / 3.0) ** 2)      # 傍晚弱风
        v = base + night + eve
        v += (random.random() - 0.5) * 0.05
        raw.append(max(0.02, v))
    vmax = max(raw)
    return [v / vmax for v in raw]


# ===================== 经济调度（lambda 迭代） =====================
def econ_dispatch(units_idx, T):
    """在给定可用机组集合上，按最小煤耗将总出力 T 分配到各机组。"""
    us = [(i, UNITS[i]) for i in units_idx]
    tmin = sum(u["Pmin"] for i, u in us)
    tmax = sum(u["Pmax"] for i, u in us)
    if T <= tmin:
        return {i: u["Pmin"] for i, u in us}
    if T >= tmax:
        return {i: u["Pmax"] for i, u in us}

    def Ps_of(lam):
        d = {}
        for i, u in us:
            P = (lam - u["b"]) / (2.0 * u["a"])
            d[i] = max(u["Pmin"], min(u["Pmax"], P))
        return d

    lo, hi = 0.0, 6000.0
    for _ in range(90):
        lam = (lo + hi) / 2.0
        s = sum(Ps_of(lam).values())
        if s < T:
            lo = lam
        else:
            hi = lam
    return Ps_of((lo + hi) / 2.0)


# ===================== 单步无储能调度 =====================
def dispatch_no_storage(units_idx, load, wind_avail):
    """每 15min 一步：以风电优先（运维最低）消纳、火电补足，核算弃风/失负荷。"""
    tmin = sum(UNITS[i]["Pmin"] for i in units_idx)
    tmax = sum(UNITS[i]["Pmax"] for i in units_idx)
    w_lo = max(0.0, load - tmax)
    w_hi = load - tmin
    if w_hi < 0:
        w_hi = 0.0
    if wind_avail <= w_lo:
        w_used = wind_avail
        thermal = load - wind_avail
        loss = max(0.0, thermal - tmax)
        curt = 0.0
    else:
        w_used = min(wind_avail, w_hi)
        thermal = load - w_used
        curt = wind_avail - w_used
        loss = 0.0
    Ps = econ_dispatch(units_idx, thermal)
    return Ps, w_used, curt, loss


# ===================== 单步成本 =====================
def step_cost(units_idx, Ps, w_used, curt, loss, carbon_price):
    coal = om = carbon = 0.0
    for i in units_idx:
        u = UNITS[i]
        P = Ps[i]
        F = u["a"] * P * P + u["b"] * P + u["c"]          # kg/h
        fc = F * DT / 1000.0 * COAL_PRICE                 # 元
        omc = 0.5 * fc
        emis = P * DT * u["carbon"]                       # 吨（=P*DT*carbon）
        coal += fc
        om += omc
        carbon += emis * carbon_price
    wind = w_used * DT * 1000.0 * WIND_OM
    curt_c = curt * DT * 1000.0 * CURT_COST
    loss_c = loss * DT * 1000.0 * LOSS_COST
    total = coal + om + carbon + wind + curt_c + loss_c
    return dict(coal=coal, om=om, carbon=carbon, wind=wind,
                curt=curt_c, loss=loss_c, total=total)


# ===================== 场景总成本核算 =====================
def scenario_cost(units_idx, load_arr, wind_inst, carbon_price):
    """对一整天（96 步）核算：返回成本明细、弃风/失负荷电量、机组出力序列。"""
    tmin = sum(UNITS[i]["Pmin"] for i in units_idx)
    tmax = sum(UNITS[i]["Pmax"] for i in units_idx)
    tot = dict(coal=0.0, om=0.0, carbon=0.0, wind=0.0, curt=0.0, loss=0.0, total=0.0)
    curt_kwh = 0.0
    loss_kwh = 0.0
    load_kwh = 0.0
    unit_out = {i: [] for i in units_idx}
    wind_use = []
    for load, wn in zip(load_arr, wind_inst):
        wind_avail = wn
        Ps, w_used, curt, loss = dispatch_no_storage(units_idx, load, wind_avail)
        c = step_cost(units_idx, Ps, w_used, curt, loss, carbon_price)
        for k in tot:
            tot[k] += c[k]
        curt_kwh += curt * DT * 1000.0
        loss_kwh += loss * DT * 1000.0
        load_kwh += load * DT * 1000.0
        for i in units_idx:
            unit_out[i].append(Ps[i])
        wind_use.append(w_used)
    tot["curt_kwh"] = curt_kwh
    tot["loss_kwh"] = loss_kwh
    tot["load_kwh"] = load_kwh
    tot["unit_supply"] = tot["total"] / load_kwh if load_kwh > 0 else 0.0
    return tot, unit_out, wind_use, tmin, tmax


# ===================== 储能容量求解（Q5） =====================
def storage_size(units_idx, load_arr, wind_arr):
    """求「避免失负荷」所需最小储能：富余风电可充电、不足时放电；允许弃风（不强制全消纳）。

    以火电上下限为边界：S(t) 为可充电功率（富余风电），D(t) 为需放电功率（火电顶满仍缺额）。
    通过二分搜索最小能量容量 cap，使逐时仿真中放电足以覆盖 D(t)（多充的弃掉），并返回所需功率。
    """
    tmin = sum(UNITS[i]["Pmin"] for i in units_idx)
    tmax = sum(UNITS[i]["Pmax"] for i in units_idx)
    S = []   # 可充电功率 MW
    D = []   # 需放电功率 MW
    for load, wa in zip(load_arr, wind_arr):
        s = max(0.0, wa - (load - tmin))          # 富余风电（火电在最小出力仍过剩）
        d = max(0.0, (load - wa) - tmax)          # 火电顶满仍缺额
        S.append(s)
        D.append(d)

    def ok(cap):
        soc = 0.0
        for s, d in zip(S, D):
            room = max(0.0, cap - soc)
            ch = min(s, room / (DT * ETA))         # 充电，超出容量则弃风
            soc += ch * DT * ETA
            dis = min(d, soc / DT)                # 放电补足缺额
            soc -= dis * DT
            if dis < d - 1e-9:
                return False
        return True

    hi = sum(d * DT for d in D) + 1.0
    lo = 0.0
    for _ in range(60):
        mid = (lo + hi) / 2.0
        if ok(mid):
            hi = mid
        else:
            lo = mid
    cap = hi
    # 在所选容量下回放，记录充放电功率
    soc = 0.0
    pch = 0.0
    pdis = 0.0
    for s, d in zip(S, D):
        room = max(0.0, cap - soc)
        ch = min(s, room / (DT * ETA))
        soc += ch * DT * ETA
        dis = min(d, soc / DT)
        soc -= dis * DT
        pch = max(pch, ch)
        pdis = max(pdis, dis)
    power = max(pch, pdis)
    surplus = sum(s * DT * ETA for s in S)
    deficit = sum(d * DT for d in D)
    feasible = deficit <= surplus + 1e-6
    return dict(S=S, D=D, capacity_MWh=cap, power_MW=power,
                surplus_MWh=surplus, deficit_MWh=deficit, feasible=feasible)


def storage_daily_cost(power_MW, capacity_MWh, throughput_kwh):
    """储能日成本：投资平摊 + 运维。"""
    inv = STO_PC * power_MW * 1000.0 + STO_EC * capacity_MWh * 1000.0
    daily_inv = inv / (STO_LIFE * 365.0)
    daily_om = throughput_kwh * STO_OM
    return dict(inv=inv, daily_inv=daily_inv, daily_om=daily_om,
                daily_total=daily_inv + daily_om)


# ===================== 十五日序列（Q7） =====================
def synth_15day():
    """十五日负荷（峰值 1200MW）与风电（装机 1200MW）确定性序列：复用日形状并加日间扰动。"""
    random.seed(SEED + 31)
    ln = synth_load()
    wn = synth_wind()
    load = []
    wind = []
    for day in range(15):
        lsf = 1.0 + 0.05 * (random.random() - 0.5)
        wsf = 1.0 + 0.15 * (random.random() - 0.5)
        for k in range(N):
            load.append(ln[k] * lsf * (1.0 + 0.02 * (random.random() - 0.5)))
            wind.append(wn[k] * wsf * (1.0 + 0.05 * (random.random() - 0.5)))
    lm = max(load)
    wm = max(wind)
    load = [v / lm * 1200.0 for v in load]
    wind = [v / wm * 1200.0 for v in wind]
    return load, wind


# ===================== 主入口 =====================
def gen_dgcup2022a(seed=SEED, out_csv="dgcup2022a.csv"):
    random.seed(SEED)
    load_norm = synth_load()
    wind_norm = synth_wind()
    load_arr = [v * LOAD_MAX for v in load_norm]          # MW，Q1–Q6 日负荷
    load_kwh_day = sum(load_arr) * DT * 1000.0

    # ---------- Q1：无风电，三机组最小成本运行（4 种碳价） ----------
    q1 = {}
    for cp in CARBON_PRICES:
        tot, uout, wuse, tmin, tmax = scenario_cost([1, 2, 3], load_arr, [0.0] * N, cp)
        q1[cp] = dict(total=tot["total"], unit_supply=tot["unit_supply"],
                      coal=tot["coal"], om=tot["om"], carbon=tot["carbon"],
                      curt=tot["curt_kwh"], loss=tot["loss_kwh"])
    # 记录三机组日计划（取碳价 0 下的调度，调度与碳价无关）
    _, uout, _, _, _ = scenario_cost([1, 2, 3], load_arr, [0.0] * N, 0)
    q1_plan = {i: uout[i] for i in [1, 2, 3]}

    # ---------- Q2：风电 300MW 替代机组3（可用 1,2） ----------
    w2 = [v * 300.0 for v in wind_norm]
    tot2, uout2, wuse2, tmin2, tmax2 = scenario_cost([1, 2], load_arr, w2, 0)
    # 风电可降低装机容量（使弃风≈0）：求满足 max(wind_norm*inst)<=load-tmin 的 inst
    # 约束：wind_norm[k]*inst <= load_arr[k]-tmin2 对所有 k
    ratios = [(load_arr[k] - tmin2) / wind_norm[k]
              for k in range(N) if wind_norm[k] > 1e-6 and load_arr[k] > tmin2]
    inst_no_curt = min(ratios) if ratios else 0.0
    q2 = dict(inst=300.0, units=[1, 2], tmin=tmin2, tmax=tmax2,
              total=tot2["total"], unit_supply=tot2["unit_supply"],
              coal=tot2["coal"], carbon=tot2["carbon"],
              curt_kwh=tot2["curt_kwh"], loss_kwh=tot2["loss_kwh"],
              inst_no_curt=inst_no_curt,
              reduce_MW=300.0 - inst_no_curt,
              plan_unit1=uout2[1], plan_unit2=uout2[2], wind_use=wuse2)

    # ---------- Q3：风电 600MW 替代机组2（可用 1,3） ----------
    w3 = [v * 600.0 for v in wind_norm]
    tot3, uout3, wuse3, tmin3, tmax3 = scenario_cost([1, 3], load_arr, w3, 0)
    # 为不失负荷可增装：需 wind_avail(peak) >= maxload - tmax3
    peak_k = max(range(N), key=lambda k: load_arr[k])
    need_wind_peak = LOAD_MAX - tmax3
    cur_wind_peak = wind_norm[peak_k] * 600.0
    add_MW = max(0.0, need_wind_peak - cur_wind_peak) / wind_norm[peak_k] if wind_norm[peak_k] > 1e-6 else 0.0
    q3 = dict(inst=600.0, units=[1, 3], tmin=tmin3, tmax=tmax3,
              total=tot3["total"], unit_supply=tot3["unit_supply"],
              coal=tot3["coal"], carbon=tot3["carbon"],
              curt_kwh=tot3["curt_kwh"], loss_kwh=tot3["loss_kwh"],
              add_MW=add_MW,
              plan_unit1=uout3[1], plan_unit3=uout3[3], wind_use=wuse3)

    # ---------- Q4：Q2、Q3 场景在 4 种碳价下的单位供电成本 ----------
    q4 = {}
    for tag, uidx, winst in [("Q2", [1, 2], 300.0), ("Q3", [1, 3], 600.0)]:
        wa = [v * winst for v in wind_norm]
        row = {}
        for cp in CARBON_PRICES:
            tot, _, _, _, _ = scenario_cost(uidx, load_arr, wa, cp)
            row[cp] = tot["unit_supply"]
        q4[tag] = row

    # ---------- Q5：风电 900MW 替代机组 2、3（仅机组1） ----------
    w5 = [v * 900.0 for v in wind_norm]
    tot5_0, uout5, wuse5, tmin5, tmax5 = scenario_cost([1], load_arr, w5, 0)
    sto = storage_size([1], load_arr, w5)
    # 储能吞吐（充电=富余风电，放电=缺额）
    throughput_kwh = sum(s * DT * 1000.0 for s in sto["S"]) + sum(d * DT * 1000.0 for d in sto["D"])
    sc = storage_daily_cost(sto["power_MW"], sto["capacity_MWh"], throughput_kwh)
    # 含储能总成本（碳价取 60）：储能消除失负荷，故在无储能成本中扣减失负荷损失后加储能日成本
    tot5_60, _, _, _, _ = scenario_cost([1], load_arr, w5, 60)
    loss5 = tot5_60["loss"]
    sto_total_60 = tot5_60["total"] - loss5 + sc["daily_total"]
    unit_supply_5 = sto_total_60 / load_kwh_day
    q5 = dict(inst=900.0, units=[1], tmin=tmin5, tmax=tmax5,
              loss_kwh_no_sto=tot5_0["loss_kwh"], curt_kwh_no_sto=tot5_0["curt_kwh"],
              total_no_sto=tot5_60["total"],
              unit_supply_no_sto=tot5_60["total"] / load_kwh_day,
              sto_capacity=sto["capacity_MWh"], sto_power=sto["power_MW"],
              sto_feasible=sto["feasible"],
              sto_inv=sc["inv"], sto_daily=sc["daily_total"],
              carbon_price=60, total_with_sto=sto_total_60,
              unit_supply=unit_supply_5,
              S=sto["S"], D=sto["D"], wind_use=wuse5, unit1=uout5[1])

    # ---------- Q6：风电替代容量递增（0,300,600,900,1200） ----------
    pen_list = []
    for inst in [0, 300, 600, 900, 1200]:
        if inst == 0:
            uidx = [1, 2, 3]
        elif inst <= 300:
            uidx = [1, 2]          # 已替代机组3
        elif inst <= 600:
            uidx = [1, 3]          # 已替代机组2
        else:
            uidx = [1]             # 已替代机组2、3
        wa = [v * inst for v in wind_norm]
        tot, _, _, _, _ = scenario_cost(uidx, load_arr, wa, 60)
        pen = inst / LOAD_MAX
        pen_list.append(dict(inst=inst, pen=pen, unit_supply=tot["unit_supply"],
                             curt_kwh=tot["curt_kwh"], loss_kwh=tot["loss_kwh"],
                             total=tot["total"]))
    q6 = dict(series=pen_list)

    # ---------- Q7：十五日，负荷/风电均 1200MW，替代机组 2、3 ----------
    load15, wind15 = synth_15day()
    # 仅机组1（tmax 600）供电，无储能
    tot7, _, wuse7, tmin7, tmax7 = scenario_cost([1], load15, wind15, 60)
    sto7 = storage_size([1], load15, wind15)
    q7 = dict(load_max=1200.0, wind_inst=1200.0, units=[1], tmax=tmax7, tmin=tmin7,
              loss_kwh=tot7["loss_kwh"], curt_kwh=tot7["curt_kwh"],
              total=tot7["total"],
              sto_capacity=sto7["capacity_MWh"], sto_power=sto7["power_MW"],
              sto_feasible=sto7["feasible"],
              load_arr=load15, wind_arr=wind15,
              daily_loss=sorted(tot7["loss_kwh"] / 15.0 for _ in [0])[0] if False else tot7["loss_kwh"] / 15.0)

    # ---------- 写 CSV ----------
    os.makedirs(OUT_DIR, exist_ok=True)
    path = os.path.join(OUT_DIR, out_csv)
    with open(path, "w", newline="", encoding="utf-8-sig") as f:
        w = csv.writer(f)
        w.writerow(["模块", "参数", "数值", "单位", "说明"])
        w.writerow(["系统", "火电机组数", 3, "台", "装机1050MW"])
        w.writerow(["系统", "机组1 最大/最小", "600/180", "MW", "a=0.226 b=30.42 c=786.80 carbon=0.72"])
        w.writerow(["系统", "机组2 最大/最小", "300/90", "MW", "a=0.588 b=65.12 c=451.32 carbon=0.75"])
        w.writerow(["系统", "机组3 最大/最小", "150/45", "MW", "a=0.785 b=139.6 c=1049.50 carbon=0.79"])
        w.writerow(["系统", "电煤价格", COAL_PRICE, "元/t", ""])
        w.writerow(["系统", "风电运维", WIND_OM, "元/kWh", ""])
        w.writerow(["系统", "储能功率/能量成本", "3000/3000", "元/kW,元/kWh", "寿命10年"])
        w.writerow(["系统", "弃风/失负荷损失", "0.3/8", "元/kWh", ""])
        w.writerow(["系统", "日负荷最大", LOAD_MAX, "MW", ""])
        w.writerow(["系统", "碳捕集单价档位", "0/60/80/100", "元/t", ""])
        w.writerow([])
        w.writerow(["Q1", "碳价", "单位供电成本", "元/kWh", "总发电成本"])
        for cp in CARBON_PRICES:
            w.writerow(["Q1", cp, round(q1[cp]["unit_supply"], 5), "元/kWh", round(q1[cp]["total"], 1)])
        w.writerow([])
        w.writerow(["Q2", "风电装机", q2["inst"], "MW", "替代机组3，可用1,2"])
        w.writerow(["Q2", "弃风电量", round(q2["curt_kwh"], 1), "kWh", "全天"])
        w.writerow(["Q2", "失负荷电量", round(q2["loss_kwh"], 1), "kWh", ""])
        w.writerow(["Q2", "无弃风可降装机", round(q2["inst_no_curt"], 1), "MW", ""])
        w.writerow(["Q2", "可降低容量", round(q2["reduce_MW"], 1), "MW", ""])
        w.writerow(["Q2", "单位供电成本", round(q2["unit_supply"], 5), "元/kWh", "碳价0"])
        w.writerow([])
        w.writerow(["Q3", "风电装机", q3["inst"], "MW", "替代机组2，可用1,3"])
        w.writerow(["Q3", "弃风电量", round(q3["curt_kwh"], 1), "kWh", ""])
        w.writerow(["Q3", "失负荷电量", round(q3["loss_kwh"], 1), "kWh", ""])
        w.writerow(["Q3", "不失负荷可增装", round(q3["add_MW"], 1), "MW", ""])
        w.writerow(["Q3", "单位供电成本", round(q3["unit_supply"], 5), "元/kWh", "碳价0"])
        w.writerow([])
        w.writerow(["Q4", "场景", "碳价0", "碳价60", "碳价80", "碳价100"])
        for tag in ["Q2", "Q3"]:
            r = q4[tag]
            w.writerow(["Q4", tag, round(r[0], 5), round(r[60], 5), round(r[80], 5), round(r[100], 5)])
        w.writerow([])
        w.writerow(["Q5", "风电装机", q5["inst"], "MW", "替代2,3，仅机组1"])
        w.writerow(["Q5", "无储能失负荷", round(q5["loss_kwh_no_sto"], 1), "kWh", ""])
        w.writerow(["Q5", "无储能弃风", round(q5["curt_kwh_no_sto"], 1), "kWh", ""])
        w.writerow(["Q5", "最小储能容量", round(q5["sto_capacity"], 2), "MWh", "往返效率0.9"])
        w.writerow(["Q5", "最小储能功率", round(q5["sto_power"], 1), "MW", ""])
        w.writerow(["Q5", "储能日成本", round(q5["sto_daily"], 1), "元/天", "含投资平摊+运维"])
        w.writerow(["Q5", "含储能单位供电成本", round(q5["unit_supply"], 5), "元/kWh", "碳价60"])
        w.writerow([])
        w.writerow(["Q6", "风电装机", "渗透率", "单位供电成本", "弃风kWh", "失负荷kWh"])
        for s in q6["series"]:
            w.writerow(["Q6", s["inst"], round(s["pen"], 3), round(s["unit_supply"], 5),
                        round(s["curt_kwh"], 1), round(s["loss_kwh"], 1)])
        w.writerow([])
        w.writerow(["Q7", "十五日负荷/风电", "1200/1200", "MW", "替代2,3，仅机组1"])
        w.writerow(["Q7", "失负荷电量", round(q7["loss_kwh"], 1), "kWh", "十五日合计"])
        w.writerow(["Q7", "弃风电量", round(q7["curt_kwh"], 1), "kWh", ""])
        w.writerow(["Q7", "所需储能容量", round(q7["sto_capacity"], 2), "MWh", ""])
        w.writerow(["Q7", "所需储能功率", round(q7["sto_power"], 1), "MW", ""])

    return dict(
        load_norm=load_norm, wind_norm=wind_norm, load_arr=load_arr,
        q1=q1, q1_plan=q1_plan,
        q2=q2, q3=q3, q4=q4, q5=q5, q6=q6, q7=q7,
        carbon_prices=CARBON_PRICES, load_kwh_day=load_kwh_day,
        csv_path=path,
        summary="电工杯2022A：Q1单位供电成本%.4f元/kWh(碳价0)；Q2弃风%.0fkWh可降装机%.0fMW；"
                "Q5无储能失负荷%.0fkWh，最小储能%.1fMWh/%.0fMW，含储能单位成本%.4f元/kWh"
                % (q1[0]["unit_supply"], q2["curt_kwh"], q2["reduce_MW"],
                   q5["loss_kwh_no_sto"], q5["sto_capacity"], q5["sto_power"],
                   q5["unit_supply"]),
    )


if __name__ == "__main__":
    D = gen_dgcup2022a()
    print("=" * 70)
    print(D["summary"])
    print("=" * 70)
    print("[Q1] 无风三机组最小成本运行，单位供电成本(元/kWh) by 碳价:")
    for cp in D["carbon_prices"]:
        print("      碳价 %4d 元/t -> %.5f 元/kWh (总成本 %.1f 元)"
              % (cp, D["q1"][cp]["unit_supply"], D["q1"][cp]["total"]))
    print("[Q2] 风电300MW替代机组3: 弃风=%.1f kWh, 失负荷=%.1f kWh, 单位成本=%.5f 元/kWh"
          % (D["q2"]["curt_kwh"], D["q2"]["loss_kwh"], D["q2"]["unit_supply"]))
    print("      为减弃风不失负荷，装机可降 %.1f MW -> %.1f MW"
          % (D["q2"]["reduce_MW"], D["q2"]["inst_no_curt"]))
    print("[Q3] 风电600MW替代机组2: 弃风=%.1f kWh, 失负荷=%.1f kWh, 单位成本=%.5f 元/kWh"
          % (D["q3"]["curt_kwh"], D["q3"]["loss_kwh"], D["q3"]["unit_supply"]))
    print("      为不失负荷，可增装风电 %.1f MW" % D["q3"]["add_MW"])
    print("[Q4] 单位供电成本(元/kWh) 碳价0/60/80/100:")
    print("      Q2场景:", {cp: round(D["q4"]["Q2"][cp], 5) for cp in D["carbon_prices"]})
    print("      Q3场景:", {cp: round(D["q4"]["Q3"][cp], 5) for cp in D["carbon_prices"]})
    print("[Q5] 风电900MW替代2,3(仅机组1):")
    print("      无储能失负荷=%.1f kWh, 弃风=%.1f kWh" % (D["q5"]["loss_kwh_no_sto"], D["q5"]["curt_kwh_no_sto"]))
    print("      最小储能容量=%.2f MWh, 功率=%.1f MW, 可行=%s" % (D["q5"]["sto_capacity"], D["q5"]["sto_power"], D["q5"]["sto_feasible"]))
    print("      储能日成本=%.1f 元, 含储能单位供电成本=%.5f 元/kWh (碳价60)"
          % (D["q5"]["sto_daily"], D["q5"]["unit_supply"]))
    print("[Q6] 替代容量递增 -> 单位供电成本/弃风/失负荷:")
    for s in D["q6"]["series"]:
        print("      装机%4dMW 渗透%.3f -> 成本%.5f 元/kWh, 弃风%.0f, 失负荷%.0f"
              % (s["inst"], s["pen"], s["unit_supply"], s["curt_kwh"], s["loss_kwh"]))
    print("[Q7] 十五日(负荷/风电1200MW,替代2,3):")
    print("      失负荷=%.1f kWh, 弃风=%.1f kWh, 所需储能=%.2f MWh/%.1f MW, 可行=%s"
          % (D["q7"]["loss_kwh"], D["q7"]["curt_kwh"], D["q7"]["sto_capacity"],
             D["q7"]["sto_power"], D["q7"]["sto_feasible"]))
    print("=" * 70)
    print("CSV ->", D["csv_path"])
