13  Dispatch with a fixed tank

Problem 13 | Hydrogen Production and Storage | Level 1: Guided problem

13.1 Problem Statement

An electrolyzer operates over six four-hour periods, representing one day. Electricity use is constant at 0.05 MWh/kg of hydrogen. Power is limited to 4 MW. Initial hydrogen inventory is 50 kg, and final inventory must also be 50 kg. Storage has no leakage and no charging-rate restriction. Production and withdrawal rates are each uniform within a period. Inventory therefore varies linearly between period boundaries.

Period Electricity price (USD/MWh) Hydrogen demand (kg/period)
1 30 120
2 25 150
3 80 180
4 100 220
5 45 180
6 35 140

Capacity is enforced at every period boundary, including the 50 kg initial stock. Under the uniform-rate assumption, this also bounds inventory throughout each period. If deliveries are discrete or rates vary within a period, an additional inventory profile or shorter time steps is necessary for physical sizing. A daily capacity charge of 0.15 USD/(kg d) is used where investment is optimized. It is an assumed daily equivalent, not a full capital-cost model.

Your task. Fix the tank at 150 kg and minimize daily electricity cost. Meet every period demand without purchases from an external supplier.

13.1.1 Before you code

Which hours are cheapest? Why might the electrolyzer still need to operate in an expensive period despite cheap electricity earlier?

13.1.2 Deliverables

  1. Define decision variables, units, objective, and constraints. State which data and assumptions are fixed.
  2. Predict one feature of the solution and explain why it should occur.
  3. Build and solve a Pyomo model. Check balances and bounds independently.
  4. Interpret the result and investigate the follow-up question below. For an open-ended challenge, state and defend your chosen policy separately from the reference solution.

13.2 Model Formulation

Let \(P_{st}\) be power in MW, \(I_{st}\) end inventory in kg, and \(K\) capacity in kg. With \(\Delta t=4\) h and \(e=0.05\) MWh/kg, \[I_{st}=I_{s,t-1}+\frac{\Delta t}{e}P_{st}-D_{st},\qquad 0\le P_{st}\le4,\quad0\le I_{st}\le K.\] Use \(I_{s0}=50\) before the first period and \(I_{s6}=50\) at the end. Total production must equal total demand. The electricity cost is \(\sum_t c_t\Delta t P_{st}\) USD/d. Omitting \(\Delta t\) would understate cost by a factor of four.

Fix \(K=150\) kg and minimize electricity cost under the inventory balances. Initial inventory is not free net supply because the terminal condition returns the same amount. The LP does not impose a minimum operating load or a startup charge.

13.3 Pyomo Implementation

The code below is the model-building portion of models/hydrogen.py. The level argument selects this problem’s assumptions. Run the complete module from the source bundle to reproduce the solution, including its independent checks:

~/.venvs/optim/bin/python -m models.hydrogen 1
"""Daily hydrogen dispatch, tank design, and scenario-dependent operation."""

import pyomo.environ as pyo
from models.common import value

PRICE = [30, 25, 80, 100, 45, 35]  # USD/MWh
DEMAND = [120, 150, 180, 220, 180, 140]  # kg per four-hour period
DT = 4  # h
SPECIFIC_ENERGY = 0.05  # MWh/kg
TANK_CHARGE = 0.15  # USD/(kg capacity d)


def build(level):
    states = (
        {"nominal": (1, 1)} if level < 3 else {"low": (0.9, 0.5), "high": (1.15, 0.5)}
    )
    m = pyo.ConcreteModel(name="Hydrogen production and storage")
    m.S = pyo.Set(initialize=list(states))
    m.T = pyo.RangeSet(0, 5)
    m.k = pyo.Var(bounds=(50, 500))
    if level == 1:
        m.k.fix(150)
    m.power = pyo.Var(m.S, m.T, bounds=(0, 4))
    m.stock = pyo.Var(m.S, m.T, domain=pyo.NonNegativeReals)
    m.balance = pyo.Constraint(
        m.S,
        m.T,
        rule=lambda m, s, t: (
            m.stock[s, t]
            == (50 if t == 0 else m.stock[s, t - 1])
            + DT * m.power[s, t] / SPECIFIC_ENERGY
            - DEMAND[t] * states[s][0]
        ),
    )
    m.capacity = pyo.Constraint(m.S, m.T, rule=lambda m, s, t: m.stock[s, t] <= m.k)
    m.terminal = pyo.Constraint(m.S, rule=lambda m, s: m.stock[s, 5] == 50)
    if level == 3:
        m.on = pyo.Var(m.S, m.T, domain=pyo.Binary)
        m.start = pyo.Var(m.S, m.T, domain=pyo.Binary)
        m.upper = pyo.Constraint(
            m.S, m.T, rule=lambda m, s, t: m.power[s, t] <= 4 * m.on[s, t]
        )
        m.lower = pyo.Constraint(
            m.S, m.T, rule=lambda m, s, t: m.power[s, t] >= m.on[s, t]
        )
        m.startup = pyo.Constraint(
            m.S,
            m.T,
            rule=lambda m, s, t: (
                m.start[s, t] >= m.on[s, t] - (0 if t == 0 else m.on[s, t - 1])
            ),
        )
    fixed = 0 if level == 1 else TANK_CHARGE * m.k
    m.obj = pyo.Objective(
        expr=fixed
        + sum(
            states[s][1]
            * sum(
                DT * PRICE[t] * m.power[s, t]
                + (10 * m.start[s, t] if level == 3 else 0)
                for t in m.T
            )
            for s in m.S
        )
    )
    m._states = states
    return m

The common solve function calls appsi_highs, checks optimal termination before loading a solution, and checks constraint residuals, bounds, and integer domains. After building the model, use:

from models.common import solve
m = solve(build(1))

13.4 Optimal Solution

The reference objective is 2,287.5000 USD/d. This value is optimal for the explicitly stated model and data.

State Period Power (MW) Demand (kg) End stock (kg)
nominal 1 0.8750 120 0.0000
nominal 2 3.7500 150 150.0000
nominal 3 2.2500 180 150.0000
nominal 4 0.8750 220 0.0000
nominal 5 2.2500 180 0.0000
nominal 6 2.3750 140 50.0000
Quantity Value
Tank capacity (kg) 150.0000
Expected electricity (MWh/d) 49.5000
Figure 13.1: Hydrogen Production and Storage: reference solution for level 1.

13.4.1 Check your solution

Check daily production equals 990 kg and electricity use equals 49.5 MWh. Verify nonnegative inventory in all periods and the 50 kg terminal stock.

The automated residual, bound, and integrality audit passed with a maximum violation of 0.00e+00 in model units. Domain-specific checks passed. Model units differ across equations, so this numerical audit does not replace dimensional analysis.

13.5 Brief Discussion

Daily electricity costs 2287.50 USD. Total energy is 49.50 MWh: the storage benefit comes from purchase timing, not reduced energy use.

13.5.1 Extension to investigate

Remove the terminal condition and quantify the apparent savings. Explain why consuming starting stock is not a repeatable daily operating policy.