Heat Pumps and Thermal Storage¶
In this example, a heat demand is supplied by a wind turbine in combination with a heat pump and a water tank that stores hot water with a standing loss.
In [2]:
Copied!
import pandas as pd
import pypsa
n = pypsa.Network()
n.set_snapshots(pd.date_range("2025-01-01 00:00", "2025-01-01 03:00", freq="H"))
n.add("Bus", "power", carrier="AC")
n.add("Bus", "heat", carrier="heat")
n.add(
"Generator",
"wind turbine",
bus="power",
carrier="wind",
p_nom_extendable=True,
p_max_pu=[0.0, 0.2, 0.7, 0.4],
capital_cost=500,
)
n.add("Load", "heat demand", bus="heat", p_set=20);
import pandas as pd
import pypsa
n = pypsa.Network()
n.set_snapshots(pd.date_range("2025-01-01 00:00", "2025-01-01 03:00", freq="H"))
n.add("Bus", "power", carrier="AC")
n.add("Bus", "heat", carrier="heat")
n.add(
"Generator",
"wind turbine",
bus="power",
carrier="wind",
p_nom_extendable=True,
p_max_pu=[0.0, 0.2, 0.7, 0.4],
capital_cost=500,
)
n.add("Load", "heat demand", bus="heat", p_set=20);
/tmp/ipykernel_7742/3242665978.py:6: FutureWarning: 'H' is deprecated and will be removed in a future version, please use 'h' instead.
n.set_snapshots(pd.date_range("2025-01-01 00:00", "2025-01-01 03:00", freq="H"))
The heat pump has time-varying efficiency (i.e. its coefficient of performance, COP) due to changing ambient temperatures.
In [3]:
Copied!
n.add(
"Link",
"heat pump",
bus0="power",
bus1="heat",
efficiency=[2.5, 3.0, 3.2, 3.0],
capital_cost=1000,
p_nom_extendable=True,
);
n.add(
"Link",
"heat pump",
bus0="power",
bus1="heat",
efficiency=[2.5, 3.0, 3.2, 3.0],
capital_cost=1000,
p_nom_extendable=True,
);
The hot water tank has a standing loss of 1% of its state of charge per hour.
In [4]:
Copied!
n.add(
"Store",
"water tank",
bus="heat",
e_cyclic=True,
e_nom=100,
standing_loss=0.01,
);
n.add(
"Store",
"water tank",
bus="heat",
e_cyclic=True,
e_nom=100,
standing_loss=0.01,
);
The wind turbine and the heat pump can be sized by the optimisation, while the water tank has a fixed size of 100 MWh.
In [5]:
Copied!
n.optimize();
n.optimize();
/tmp/ipykernel_7742/1450685117.py:1: FutureWarning: The default value of `include_objective_constant` will change from True to False in version 2.0. Set `include_objective_constant` explicitly to suppress this warning. Using False improves LP numerical conditioning by not including the objective constant as a variable. n.optimize(); WARNING:pypsa.consistency:The following buses have carriers which are not defined. Run n.sanitize() to add them. Components with undefined carriers: Index(['power', 'heat'], dtype='object', name='name')
WARNING:pypsa.consistency:The following generators have carriers which are not defined. Run n.sanitize() to add them. Components with undefined carriers: Index(['wind turbine'], dtype='object', name='name')
WARNING:pypsa.consistency:The following links have carriers which are not defined. Run n.sanitize() to add them. Components with undefined carriers: Index(['heat pump'], dtype='object', name='name')
WARNING:pypsa.consistency:The following stores have carriers which are not defined. Run n.sanitize() to add them. Components with undefined carriers: Index(['water tank'], dtype='object', name='name')
INFO:linopy.model: Solve problem using Highs solver
INFO:linopy.model:Solver options: - log_to_console: False
INFO:linopy.io: Writing time: 0.06s
INFO:linopy.constants: Optimization successful: Status: ok Termination condition: optimal Solution: 18 primals, 38 duals Objective: 2.35e+04 Solver model: available Solver message: Optimal
INFO:pypsa.optimization.optimize:The shadow-prices of the constraints Generator-ext-p-lower, Generator-ext-p-upper, Link-ext-p-lower, Link-ext-p-upper, Store-fix-e-lower, Store-fix-e-upper, Store-energy_balance were not assigned to the network.
In [6]:
Copied!
pd.DataFrame({attr: n.stores_t[attr]["water tank"] for attr in ["p", "e"]}).round(3)
pd.DataFrame({attr: n.stores_t[attr]["water tank"] for attr in ["p", "e"]}).round(3)
Out[6]:
| p | e | |
|---|---|---|
| snapshot | ||
| 2025-01-01 00:00:00 | 20.000 | 4.377 |
| 2025-01-01 01:00:00 | 4.333 | -0.000 |
| 2025-01-01 02:00:00 | -13.423 | 13.423 |
| 2025-01-01 03:00:00 | -11.334 | 24.623 |
In [7]:
Copied!
pd.DataFrame({attr: n.links_t[attr]["heat pump"] for attr in ["p0", "p1"]}).round(3)
pd.DataFrame({attr: n.links_t[attr]["heat pump"] for attr in ["p0", "p1"]}).round(3)
Out[7]:
| p0 | p1 | |
|---|---|---|
| snapshot | ||
| 2025-01-01 00:00:00 | -0.000 | 0.000 |
| 2025-01-01 01:00:00 | 5.222 | -15.667 |
| 2025-01-01 02:00:00 | 10.445 | -33.423 |
| 2025-01-01 03:00:00 | 10.445 | -31.334 |