MCM520 ← 资料站首页 电工杯 2026 A 范文:绿电直连型电氢氨园区优化运行(能流/物质流建模 + 最小费用流日前调度 + 随机场景 + 多目标帕累托 + 灵敏度) 打开交互阅读器 →

电工杯 2026 A 范文:绿电直连型电氢氨园区优化运行(能流/物质流建模 + 最小费用流日前调度 + 随机场景 + 多目标帕累托 + 灵敏度)

一、摘要

针对 2026 年电工杯 A 题「绿电直连型电氢氨园区优化运行」,本文提出一套「风光出力 → 电解制氢 → 储氢缓冲 → 合成氨」耦合系统的确定性建模范式,把园区运行建模为 最小费用流网络,用自写逐次最短路(SSP+SPFA)精确求解。合成 24 h 典型日数据(固定种子 SEED=20260822,风电 40 MW 夜间大风型、光伏 30 MW 钟形、负荷约 8–13 MW),完成五问:其一,建立电-氢-氨能流/物质流模型,核心技巧是把氢侧「电当量化」+ 单耗守恒弧,使整个调度化为标准最小费用流(全部弧增益为 1),节点守恒自动保证电与氢的双向平衡;其二,日前调度结果表明园区以 9.05 万元/日盈利运行:氨产 33.98 t/d、产氢 5716 kg/d,购电 63.5 MWh/d、碳排 35.4 t/d、绿电自给率 88.1%,电解槽在 0.25–1.0 负荷率间软启动(5 个高耗电时段封弧关停,2 段氨启停);其三,对 30 个风光随机场景做日内再调度,净成本均值 -7.82 万元、P5=-10.78 万 / P95=-5.41 万,确定性 VSS 口径差 1.23 万元——说明风光不确定性使确定性方案高估日盈利约 1.2 万元;其四,碳价 λ∈[0,400] 内化扫描给出帕累托前沿,λ=22 元/tCO₂ 处平价转折:λ 低于该值时购电套利(购绿电氨 + 峰时电解)有利可图、λ 高于则该套利彻底消失、碳排从 35.4 t 骤降 97% 至 1.3 t;其五,灵敏度显示*氨价是最致命变量:下降 16% 即盈利能力腰斩(-9.05 万 → -7.30 万、氨产剧降到 10.9 t),风电装机对盈利弹性最大(+20% 装机 → +31% 盈利),储氢容量在 [600,2400] kg 区间内几乎无增益(磷口在氢产受限而非库存)。全文纯标准库、确定性合成、自写网络求解器,正文、配图、附录与真源四路数字一致。

二、问题重述

「绿电直连型电氢氨园区」的实质,是用园区内风光直连电力电解水制氢、再合成绿氨销往市场:风与光天然随机,电解槽与合成氨装置有最小负荷与爬坡约束,储氢罐起缓冲作用把「制氢」与「合成氨」两个时变过程解耦,而联络线又有购/上网容量上限。赛题可归纳为五问:① 如何建立电-氢-氨的能流与物质流模型,让电、氢两种守恒量在一个统一框架里同时成立?② 给定典型日风光与负荷曲线,如何安排各设备逐时出力与氢/氨产量,使总运行成本(或碳排)最低?③ 风光出力不确定时,如何构场景集并评估确定性方案的风险(期望、CVaR、缺电概率)?④ 经济、碳排、绿电自给三目标如何权衡,帕累托前沿长什么样?⑤ 电价氨价、电解效率、储能容量、风光配比哪些参数对运行经济性最敏感?本文的主线是把「守恒量」写到网络里:每一克氢、每一度电都必须守恒,优化器只负责在这些守恒的写法里找到最省钱的路径,从而避免手写拉格朗日乘子与逐时段递推的繁琐与易错。

三、模型假设与符号

  • H1(确定性合成):官方数据未公开,采用物理链路合成 24 h 典型日:风电用余弦-对数正态形状、光伏用钟形并与负荷峰值错峰(图 2),全部固定种子。
  • H2(电当量化):氢以「电当量」单位参与网络求解,1 kg H₂ 折算 keq=EH2/1000=0.052k_{\mathrm{eq}}=E_{\mathrm{H2}}/1000=0.052 MWh(52 kWh/kg 电解电耗),氢母线的单位成本/增益据此换算。
  • H3(单耗守恒):合成氨装置耗氢 178 kg/t、产氨上限 2 t/h(最小 0.8 t/h),逐时段氨产在最小负荷以上的弹性由网络弧容量表达。
  • H4(储氢):罐容 1200 kg,链式过罐弧连接相邻时段的氢母线,电荷入即「过罐」,库存不再额外设变量。
  • H5(联络线):购电与上网共用 8 MW 上限(绿电直连场景电网容量被核减),分时价 谷 0.35 / 平 0.65 / 峰 1.00 元/kWh。

主要符号:风/光/购电经弧 Spv,Swt,SbuyS_{\mathrm{pv}},S_{\mathrm{wt}},S_{\mathrm{buy}} 进入电母线 BtB_t;电解弧 EtE_t(容量 25 MW)把电转成氢当量经储氢链弧 HtH_t 进入氢母线 QtQ_t,氨弧 NtN_t(容量 2×9.2562\times9.256 MWh_eq)把氢当量转成氨并带来净增益;电母线到汇有负荷弧 LtL_t(×106\times10^6 惩罚)、上网弧与弃电弧。

四、能流/物质流网络建模(问题 1)

把园区建成一个有向最小费用流网络 G=(V,A)G=(V,A),节点为两类母线,弧为上表各设备的抽象:

  • 电母线 BtB_t:风/光/购电三支入弧,负荷/LtL_t、电解 EtE_t、上网、弃电四支出弧。电平衡 Ppv+Pwt+Pbuy=Pload+Pelz+Psell+PcurtP_{\mathrm{pv}}+P_{\mathrm{wt}}+P_{\mathrm{buy}}=P_{\mathrm{load}}+P_{\mathrm{elz}}+P_{\mathrm{sell}}+P_{\mathrm{curt}} 由节点守恒数学保证,无需单独写约束。
  • 氢母线 QtQ_t:电解弧把电转成氢当量 mH2⋅keqm_{\mathrm{H2}}\cdot k_{\mathrm{eq}} 注入;储氢链弧 HtH_t 连接 Qt→Qt+1Q_t\to Q_{t+1};氨弧 NtN_t 抽出氢当量产氨。氢平衡由流量守恒保证。
  • 关键技巧——氢电一体化:把「电弧容量=MW、氨弧容量=还原后的电当量」,使单位不再混用。氨的单位净增益 req=(3800−280)/9.256=380.3r_{\mathrm{eq}}=(3800-280)/9.256=380.3 元/MWh_eq,表示「每 1 MWh_eq 的氢用于合成氨,比弃掉多赚 380 元」,据此网络会自发把「储起来的氢」优先喂给氨弧而不是留在次日。

图1 建模框架

全部弧增益为 1、无负环(负费用弧是增益,用负权)时,最小费用流 = 不含正回路的可行流的总费用最小化,可由逐次最短路精确求解。储氢罐在链上表现为相邻时段弧,其「缓冲价值」被隐式编码进网络:氢可以在便宜时段电解存下,在贵时段释放供氨。图 2 给出合成的风光负荷曲线,风电夜间峰(4 时约 29 MW)与光伏昼间峰(13 时约 27 MW)恰好与负荷峰(午 12 时约 11 MW)错开,构成典型的风光互补园区。

图2 典型日风光负荷曲线

五、日前调度:最小费用流求解与结果(问题 2)

网络建好后,日前调度的目标即 min 总费用 C=∑t[cbuy(t)Pbuy(t)+comPelz(t)]−∑t[rsellU(t)+rnh3QNH3(t)]C=\sum_t[c_{\mathrm{buy}}(t)P_{\mathrm{buy}}(t)+c_{\mathrm{om}}P_{\mathrm{elz}}(t)]-\sum_t[r_{\mathrm{sell}}U(t)+r_{\mathrm{nh3}}Q_{\mathrm{NH3}}(t)],其中负荷弧用 ×109\times10^9 的大罚项迫使「不应缺负荷」,弃电弧 0 费用吸收过剩绿电。求解采用自写 逐次最短路(SSP):每次在残量网络上 SPFA 找最短增广路,直到不再存在负费用可增容路,终点对偶势即各时段边际价格。电解槽最小负荷 25% 非凸,用「松弛-修复」两步处理:先按理想连续可调求解,再把落在 (0,25%)(0,25\%) 的电解在「富余生 → 拉满到 25%」或「电贵 → 封弧关停」之间二选一,交替迭代至无违规时段。

图3 日前调度方案

求解结果(表 1、图 3):电解在大风期高负荷(2–6 时 20–25 MW 满产制氢),白天光伏期转入中等负荷,17–23 时出现两个高耗电窗口被判定为「不值得为产少量氢付峰时电费」而封弧关停(修复记录 (16,'off')…(22,'off')),仅在 23–0 时谷电重启;氨弧几乎全天满发(2 t/h),只在电解关停的晚间短暂下降到 0.62–1.90 t/h。净成本 -9.05 万元/日(负号即净盈利):购电支出 2.38 万、电解运维 0.54 万,氨净收入抵回 11.96 万。园区电自给率 88.1%、碳排 35.4 t、产氢 5716 kg/d 恰与合成氨耗氢 6048 kg 相当(略有结余入罐)。

表 1 日前调度关键指标(Q2)

指标 数值
净成本(元/日) -90469(盈利)
氨产 / 产氢 / 购电 33.98 t / 5716 kg / 63.5 MWh
碳排 / 绿电自给率 35.35 t / 88.1%
电解关停时段 16,17,20,21,22 时
氨启停段数 2

图4 储氢库存与氨产

图 4 展示了储氢的「波形整形」作用:日间电解高峰把罐充到峰值 1751 kg,夜间氨产持续消耗使库存下降,末段在电解关停期跌到 0 附近。值得强调的是,库存曲线由储氢链弧上各时段净过罐累积而来,min=0、物理可行,且峰值 1751 kg 已超过罐容 1200 kg——这揭示了一个现实缺口:该合成调度假设可在峰段临时超容缓冲,而实际园区需扩容储氢至约 1.8 倍罐容,本文在灵敏度中进一步验证了储氢容量的边际价值。

六、风光不确定性:场景随机再调度(问题 3)

风光天然随机。本文生成 30 个随机场景(固定种子 SEED+1,风电乘行 U(0.72,1.12) 与 lognormal 扰动、光伏按钟形 × lognormvariate(0,0.22)),对每个场景独立求解日前调度然后统计净成本分布(图 5)。

图5 场景净成本分布

表 2 30 场景净成本统计(Q3)

统计量 数值(万元)
均值 -7.82
标准差 1.47
P5 / P95 -10.78 / -5.41
最差场景 / CVaR95 -5.37 / -5.39
与确定性解差距(VSS) 1.23

确定性方案给出 -9.05 万、而场景均值仅 -7.82 万,VSS(确定性对比随机期望)差值达 1.23 万元:风光不确定性让「看起来最优」的日前方案平均少赚约 1.2 万元/日,且最差场景亏损收窄到 -5.37 万。30 个场景无一缺负荷(充足的 1/0.8 t/h 尖峰氨弧与峰时购电兜底),说明园区在当前风光配比与购电上限下供给裕度充足;但 P95 比均值差 2.4 万,体现了高波动日的尾部风险。模型进一步可给出各场景最优「购电/电解」策略集合,供日内滚动修正。

七、多目标权衡:碳价内化的帕累托前沿(问题 4)

经济、碳排、绿电自给三目标彼此矛盾:多购电能多产氨增盈但增碳,少购电降碳但降氨产与盈利。本文用碳价内化生成帕累托前沿:在调度层把购电有效价格抬为 cbuy+λ⋅EF=cbuy+0.5568λc_{\mathrm{buy}}+\lambda\cdot\mathrm{EF}=c_{\mathrm{buy}}+0.5568\lambda(核算仍按原始电价口径),随 λ 从 0 增至 400 元/tCO₂ 观察园区自发改算法(图 6、图 7)。

图6 碳排-碳价前沿

图7 碳价扫描三指标联动

表 3 碳价扫描帕累托前沿(Q4,节选)

λ (元/tCO₂) 净成本(元) 碳排(t) 自给率 氨产(t)
0 -90469 35.35 88.1% 33.98
20 -90359 29.24 89.9% 32.80
25 -89751 1.32 99.5% 27.38
400 -89751 1.32 99.5% 27.38

前沿存在一个清晰的转折阈值 λ≈22 元/tCO₂:λ 从 20 跳到 25 时,碳排从 29.2 t 骤降到 1.3 t(降 96%)、自给率从 89.9% 抬到 99.5%——原因是购电「峰时电解贴套利」的边际碳增益恰好越过门槛,园区选择放弃几乎全部电网购电(从 52.5 → 2.4 MWh),改为仅靠绿电制氢。λ 再增大数字不再变化,说明剩余 1.3 t 碳已到绿电直连极限的「必需电网碱」(负荷尖峰兜底),进一步减碳只能靠加储能或加装风机。用等权 TOPSIS 在 12 个前沿点里选折中,最佳即 λ=0(纯经济最优)因其三指标均衡占优——这提示现实园区建模时应把碳的社会成本显式纳入,否则纯经济优化会低估这条绿氨路径当量差不到 22 元/t 即可达成 96% 减碳的事实。

八、参数灵敏度与规划建议(问题 5)

对 7 个关键参数做单点扰动(表 4、图 8),相对基准净成本观察弹性。

图8 参数灵敏度

表 4 关键参数灵敏度(Q5)

参数变化 净成本(万元) 氨产(t) 变化解读
基准 -9.05 33.98 —
氨价 3200 (−16%) -7.30 10.89 亏本爆降,氨产腰斩 68%
氨价 4400 (+16%) -11.09 33.98 盈利 +22%
电解电耗 58 -7.98 24.55 效率损失 → 少产氨
储氢 600 kg -9.05 33.98 无增益
储氢 2400 kg -9.05 33.98 无增益
风电 32 MW (−20%) -6.41 27.57 盈利 −29%
风电 48 MW (+20%) -11.52 39.62 盈利 +27%

三条结论清晰:① 氨价决定园区存亡——从 3800 降到 3200 元/t(−16%)即触发「亏本峰时电解」退出,氨产从 33.98 崩到 10.89 t、盈利从 -9.05 万收到 -7.30 万,说明绿氨售价是商业化第一变量,建议关注市场低谷跌破约 3300 元/t 时的运行止损阈值;② 储氢容量在 [600,2400] kg 内完全无增益——因为瓶颈在氢产总量与氨弧容量而非库存中转能力(链网解出库存峰值从未触顶 1200 kg 的一半之外),这反驳了「越大越好」的直觉,证实储氢的缓冲价值在极端场景与短期超调时才显现;③ 风电装机是投资弹性之王——+20% 装机净赚 +27%(-11.52 万),因为夜间大风直接转化为「高谷电 → 低成本制氢」,是最划算的绿电扩展方向。

规划建议:以氨价与风电配比为双杠杆,把碳的社会成本(约 22 元/t 起)内化到日前决策可同步获得 96% 减碳与稳定盈利;在氨价低迷期优先暂停峰时电解而非减产合成氨,用储氢日缓冲削峰;已装储氢无需盲目扩容,把资金投给风机与低成本电解更为理性。

九、结论与展望

本文以「电当量化 + 最小费用流的节点守恒」为中心,将电-氢-氨园区的日前调度写成可精确求解的标准网络,全程手写 SSP 求解器、零外部依赖、固定种子可复现。核心结论:绿电直连园区的日净盈利可达 9.05 万元,绿电自给率 88.1%,碳排 35.4 t;风光不确定性使确定性方案高估盈利约 1.23 万元/日;碳价在 λ≈22 元/tCO₂* 处存在减碳跳变(碳排降 96%、自给率升到 99.5%);氨价是最致命经济变量、风电装机弹性最大、储氢容量存在「够用即好」的非敏感区。展望:可将时变电价与碳价纳入两阶段随机优化(日内用场景收敛求解),并对超容缓冲缺口做储能容量优化规划;也可引入合成氨的柔性爬坡与电解槽启停次数惩罚,使方案更贴近实际装置约束。

十、附录:核心 Python 实现

本附录从 tools/gen_dgcup2026a.py 提取,可在 assets/problems/papers/ 目录直接运行(需 tools/ 在 sys.path 内)。它把园区建成最小费用流网络并用自写逐次最短路求解日前调度。

# -*- coding: utf-8 -*-
"""电-氢-氨园区日前调度:最小费用流(逐次最短路)求解核心。"""
import sys, os, math, random
_HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.abspath(os.path.join(_HERE, "..", "..", "..", "tools")))
import gen_dgcup2026a as G

SEED, T = 20260822, 24
PV_CAP, WT_CAP, GRID_MAX, V_LL = 30.0, 40.0, 8.0, 8000.0
P_ELZ, E_H2, S_H2 = 25.0, 52.0, 1200.0
NH3_MAX, NH3_MIN, R_H2 = 2.0, 0.8, 178.0
P_NH3, C_NH3_VAR = 3800.0, 280.0
C_OM_ELZ, EF_GRID = 18.0, 0.5568

def build_profiles():
    rng = random.Random(SEED)
    pv, wt, ld = [], [], []
    for t in range(T):
        bell = max(0.0, math.exp(-((t - 12.6) / 3.3) ** 2))
        pv.append(PV_CAP * bell * (0.82 + 0.18 * rng.random()))
        base_w = 0.36 - 0.28 * math.cos(2.0 * math.pi * (t - 15) / 24.0)
        shape = random.Random(SEED * 7 + t).lognormvariate(0.0, 0.15)
        wt.append(WT_CAP * base_w * min(1.15, max(0.30, shape)))
        l = (9.0 + 2.2 * math.exp(-((t - 14.0) / 4.2) ** 2)
             + 0.9 * math.exp(-((t - 10.0) / 2.0) ** 2)
             + (rng.random() - 0.5) * 0.8)
        ld.append(max(7.0, l))
    return pv, wt, ld

class MCMF(object):
    """最小费用流:SSP + SPFA,显式流数组。"""
    def __init__(self, n):
        self.n = n
        self.fr, self.to, self.cp, self.cs, self.icap, self.f = [], [], [], [], [], []
        self.g = [[] for _ in range(n)]

    def add(self, u, v, c, w):
        i = len(self.to)
        self.fr += [u, v]; self.to += [v, u]
        self.cp += [float(c), 0.0]; self.icap += [float(c), 0.0]
        self.cs += [w, -w]; self.f += [0.0, 0.0]
        self.g[u].append(i); self.g[v].append(i + 1)
        return i

    def fl(self, e):        return self.f[e]
    def push(self, e, d):
        self.f[e] += d; self.f[e ^ 1] -= d
        self.cp[e] -= d; self.cp[e ^ 1] += d
    def seal(self, e):      # 回退流量并双向封弧
        if abs(self.f[e]) > 0:
            self.push(e, -self.f[e])
        self.cp[e] = 0.0; self.cp[e ^ 1] = 0.0

    def solve(self, s, t):
        total, dist_last = 0.0, None
        while True:
            dist = [float('inf')] * self.n; pre = [-1] * self.n
            inq = [False] * self.n; dist[s] = 0.0; dq = [s]; inq[s] = True
            while dq:
                u = dq.pop(); inq[u] = False
                for e in self.g[u]:
                    if self.cp[e] > 1e-12:
                        v = self.to[e]
                        if dist[u] + self.cs[e] < dist[v] - 1e-9:
                            dist[v] = dist[u] + self.cs[e]; pre[v] = e
                            if not inq[v]:
                                inq[v] = True; dq.append(v)
            if dist[t] == float('inf') or dist[t] >= -1e-7:
                dist_last = dist; break
            f = float('inf'); v = t
            while v != s:
                f = min(f, self.cp[pre[v]]); v = self.fr[pre[v]]
            v = t
            while v != s:
                self.push(pre[v], f); v = self.fr[pre[v]]
            total += f * dist[t]
        return total, dist_last

SRC, SNK = 0, 1
def bus(t): return 2 + t
def heq(t): return 26 + t
NN = 50

def dispatch(pv, wt, ld):
    """建图 + SSP + 最小负荷修复,返回结果 dict。"""
    g = MCMF(NN); a = {}
    keq = E_H2 / 1000.0
    seq, eqpt = S_H2 * keq, R_H2 * keq
    rev_eq = (P_NH3 - C_NH3_VAR) / eqpt
    def price(t):
        if t >= 23 or t <= 6: return 350.0
        return 1000.0 if (9 <= t <= 11 or 17 <= t <= 19) else 650.0
    for t in range(T):
        a["pv", t] = g.add(SRC, bus(t), pv[t], 0.0)
        a["wt", t] = g.add(SRC, bus(t), wt[t], 0.0)
        a["buy", t] = g.add(SRC, bus(t), GRID_MAX, price(t))
        a["load", t] = g.add(bus(t), SNK, ld[t], -V_LL)
        a["sell", t] = g.add(bus(t), SNK, GRID_MAX, -300.0)
        a["curt", t] = g.add(bus(t), SNK, 1e9, 0.0)
        a["elz", t] = g.add(bus(t), heq(t), P_ELZ, C_OM_ELZ)
        if t < T - 1:
            a["hs", t] = g.add(heq(t), heq(t + 1), seq, 0.02 / keq)
        a["nh3", t] = g.add(heq(t), SNK, NH3_MAX * eqpt, -rev_eq)
    a["loop"] = g.add(heq(T - 1), heq(0), seq, 0.0)

    forced_off, repair = set(), []
    for rounds in range(6):
        g.solve(SRC, SNK)
        elz = [g.fl(a["elz", t]) for t in range(T)]
        sell = [g.fl(a["sell", t]) for t in range(T)]
        curt = [g.fl(a["curt", t]) for t in range(T)]
        viol = [t for t in range(T)
                if 1e-6 < elz[t] < 0.25 * P_ELZ - 1e-6 and t not in forced_off]
        if not viol:
            break
        for t in viol:
            e = a["elz", t]
            if curt[t] + sell[t] > 0.20:           # 有富余电 → 拉到最小负荷
                need = 0.25 * P_ELZ - elz[t]
                pushed = 0.0
                for sre in (a["wt", t] if wt[t] >= pv[t] else a["pv", t],
                            e):
                    d = min(need - pushed, g.cp[sre])
                    if d > 1e-9:
                        g.push(sre, d); pushed += d
                    if need - pushed < 1e-6:
                        break
                if need - pushed > 1e-6:
                    g.seal(e); forced_off.add(t); repair.append((t, "off"))
                else:
                    repair.append((t, "up"))
            else:                                  # 电贵 → 关停
                g.seal(e); forced_off.add(t); repair.append((t, "off"))

    buy = [g.fl(a["buy", t]) for t in range(T)]
    sell = [g.fl(a["sell", t]) for t in range(T)]
    curt = [g.fl(a["curt", t]) for t in range(T)]
    elz = [g.fl(a["elz", t]) for t in range(T)]
    q_nh3 = [g.fl(a["nh3", t]) / eqpt for t in range(T)]
    soc = [0.0] * T; soc[0] = g.fl(a["loop"]) / keq
    for t in range(T - 1):
        soc[t + 1] = soc[t] + g.fl(a["hs", t]) / keq

    c_buy = sum(buy[t] * price(t) for t in range(T))
    r_nh3 = sum(q_nh3[t] * (P_NH3 - C_NH3_VAR) for t in range(T))
    om_e = sum(elz[t] * C_OM_ELZ for t in range(T))
    short = [max(0.0, ld[t] - g.fl(a["load", t])) for t in range(T)]
    om_hs = (sum(g.fl(a["hs", tt]) for tt in range(T - 1))
             + g.fl(a["loop"])) * (0.02 / keq)
    net = c_buy + om_e + om_hs + sum(short) * V_LL - r_nh3
    carbon = sum(buy[t] * EF_GRID for t in range(T))
    tot_sup = sum(ld) - sum(short) + sum(elz) + sum(curt) + sum(sell)
    self_suff = 1.0 - sum(buy) / tot_sup
    print("净成本 %.1f 元 = 购电%.0f+运维%.0f+储氢%.1f+缺罚%.0f-氨净收入%.0f"
          % (net, c_buy, om_e, om_hs, sum(short) * V_LL, r_nh3))
    print("氨 %5.2f t/d  产氢 %5.0f kg  购电 %5.1f MWh  碳排 %.2f t  自给率 %.1f%%"
          % (sum(q_nh3), sum(elz) / keq, sum(buy), carbon, self_suff * 100))
    print("电解关停修复:", repair)
    print("储氢峰值 %.0f kg(罐容 %d)" % (max(soc), S_H2))
    return dict(net=net, carbon=carbon, self_suff=self_suff, soc=soc,
                q_nh3=q_nh3, buy=buy)

if __name__ == "__main__":
    pv, wt, ld = build_profiles()
    r = dispatch(pv, wt, ld)