Note
You can download this example as a Jupyter notebook or start it in interactive mode.
Two chained reservoirs#
Two disconnected electrical loads are fed from two reservoirs linked by a river; the first reservoir has inflow from rain onto a water basin.
Note that the two reservoirs are tightly coupled, meaning there is no time delay between the first one emptying and the second one filling, as there would be if there were a long stretch of river between the reservoirs. The reservoirs are essentially assumed to be close to each other. A time delay would require a “Link” element between different snapshots, which is not yet supported by PyPSA (but could be enabled by passing network.optimize()
an extra_functionality function).
[1]:
import matplotlib.pyplot as plt
import pandas as pd
import pypsa
[2]:
network = pypsa.Network()
network.set_snapshots(pd.date_range("2016-01-01 00:00", "2016-01-01 03:00", freq="H"))
/tmp/ipykernel_1811/447536113.py:2: FutureWarning: 'H' is deprecated and will be removed in a future version, please use 'h' instead.
network.set_snapshots(pd.date_range("2016-01-01 00:00", "2016-01-01 03:00", freq="H"))
Add assets to the network.
[3]:
network.add("Carrier", "reservoir")
network.add("Carrier", "rain")
network.add("Bus", "0", carrier="AC")
network.add("Bus", "1", carrier="AC")
network.add("Bus", "0 reservoir", carrier="reservoir")
network.add("Bus", "1 reservoir", carrier="reservoir")
network.add(
"Generator",
"rain",
bus="0 reservoir",
carrier="rain",
p_nom=1000,
p_max_pu=[0.0, 0.2, 0.7, 0.4],
)
network.add("Load", "0 load", bus="0", p_set=20.0)
network.add("Load", "1 load", bus="1", p_set=30.0)
[3]:
Index(['1 load'], dtype='object')
The efficiency of a river is the relation between the gravitational potential energy of 1 m^3 of water in reservoir 0 relative to its turbine versus the potential energy of 1 m^3 of water in reservoir 1 relative to its turbine
[4]:
network.add(
"Link",
"spillage",
bus0="0 reservoir",
bus1="1 reservoir",
efficiency=0.5,
p_nom_extendable=True,
)
# water from turbine also goes into next reservoir
network.add(
"Link",
"0 turbine",
bus0="0 reservoir",
bus1="0",
bus2="1 reservoir",
efficiency=0.9,
efficiency2=0.5,
capital_cost=1000,
p_nom_extendable=True,
)
network.add(
"Link",
"1 turbine",
bus0="1 reservoir",
bus1="1",
efficiency=0.9,
capital_cost=1000,
p_nom_extendable=True,
)
network.add(
"Store", "0 reservoir", bus="0 reservoir", e_cyclic=True, e_nom_extendable=True
)
network.add(
"Store", "1 reservoir", bus="1 reservoir", e_cyclic=True, e_nom_extendable=True
)
[4]:
Index(['1 reservoir'], dtype='object')
[5]:
network.optimize(network.snapshots)
print("Objective:", network.objective)
WARNING:pypsa.consistency:Encountered nan's in static data for columns ['efficiency2'] of component 'Link'.
WARNING:pypsa.consistency:The following buses have carriers which are not defined:
Index(['0', '1'], dtype='object', name='Bus')
WARNING:pypsa.consistency:Encountered nan's in static data for columns ['efficiency2'] of component 'Link'.
WARNING:pypsa.consistency:The following buses have carriers which are not defined:
Index(['0', '1'], dtype='object', name='Bus')
/home/docs/checkouts/readthedocs.org/user_builds/pypsa/envs/latest/lib/python3.12/site-packages/linopy/common.py:147: UserWarning: coords for dimension(s) ['Generator'] is not aligned with the pandas object. Previously, the indexes of the pandas were ignored and overwritten in these cases. Now, the pandas object's coordinates are taken considered for alignment.
warn(
INFO:linopy.model: Solve problem using Highs solver
INFO:linopy.io: Writing time: 0.06s
INFO:linopy.solvers:Log file at /tmp/highs.log
INFO:linopy.constants: Optimization successful:
Status: ok
Termination condition: optimal
Solution: 37 primals, 78 duals
Objective: 5.56e+04
Solver model: available
Solver message: optimal
INFO:pypsa.optimization.optimize:The shadow-prices of the constraints Generator-fix-p-lower, Generator-fix-p-upper, Link-ext-p-lower, Link-ext-p-upper, Store-ext-e-lower, Store-ext-e-upper, Store-energy_balance were not assigned to the network.
Running HiGHS 1.7.2 (git hash: 184e327): Copyright (c) 2024 HiGHS under MIT licence terms
Coefficient ranges:
Matrix [5e-01, 1e+00]
Cost [1e+03, 1e+03]
Bound [0e+00, 0e+00]
RHS [2e+01, 7e+02]
Presolving model
11 rows, 18 cols, 33 nonzeros 0s
7 rows, 14 cols, 25 nonzeros 0s
7 rows, 14 cols, 25 nonzeros 0s
Presolve : Reductions: rows 7(-71); columns 14(-23); elements 25(-116)
Solving the presolved LP
Using EKK dual simplex solver - serial
Iteration Objective Infeasibilities num(sum)
0 5.5555555556e+04 Pr: 7(177.778) 0s
10 5.5555555556e+04 Pr: 0(0) 0s
Solving the original LP from the solution after postsolve
Model status : Optimal
Simplex iterations: 10
Objective value : 5.5555555556e+04
HiGHS run time : 0.00
Writing the solution to /tmp/linopy-solve-nqu3zwdx.sol
Objective: 55555.55555555556
[6]:
network.generators_t.p.plot.area(figsize=(9, 4))
plt.tight_layout()
Now, let’s have look at the different outputs of the links.
[7]:
network.links_t.p0.plot(figsize=(9, 4), lw=3)
plt.tight_layout()
[8]:
network.links_t.p1.plot(figsize=(9, 4), lw=3)
plt.tight_layout()
[9]:
network.links_t.p2.plot(figsize=(9, 4), lw=3)
plt.tight_layout()
What are the energy outputs and energy levels at the reservoirs?
[10]:
pd.DataFrame({attr: network.stores_t[attr]["0 reservoir"] for attr in ["p", "e"]})
[10]:
p | e | |
---|---|---|
snapshot | ||
2016-01-01 00:00:00 | 66.666667 | -0.000000 |
2016-01-01 01:00:00 | -0.000000 | -0.000000 |
2016-01-01 02:00:00 | -0.000000 | -0.000000 |
2016-01-01 03:00:00 | -66.666667 | 66.666667 |
[11]:
pd.DataFrame({attr: network.stores_t[attr]["1 reservoir"] for attr in ["p", "e"]})
[11]:
p | e | |
---|---|---|
snapshot | ||
2016-01-01 00:00:00 | -0.0 | -0.0 |
2016-01-01 01:00:00 | -0.0 | -0.0 |
2016-01-01 02:00:00 | -0.0 | -0.0 |
2016-01-01 03:00:00 | -0.0 | -0.0 |
[ ]: