Note
You can download this example as a Jupyter notebook or start it in interactive mode.
LOPF with coupling to heating sector
In this example three locations are optimised, each with an electric bus and a heating bus and corresponding loads. At each location the electric and heating buses are connected with heat pumps; heat can also be supplied to the heat bus with a boiler. The electric buses are connected with transmission lines and there are electrical generators at two of the nodes.
[1]:
import pypsa
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
sns.set(rc={"figure.figsize":(9, 5)})
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
/tmp/ipykernel_471/634408091.py in <module>
3 import pandas as pd
4 import matplotlib.pyplot as plt
----> 5 import seaborn as sns
6 sns.set(rc={"figure.figsize":(9, 5)})
ModuleNotFoundError: No module named 'seaborn'
[2]:
network = pypsa.Network()
Add three buses of AC and heat carrier each
[3]:
for i in range(3):
network.add("Bus","electric bus {}".format(i),v_nom=20.)
network.add("Bus","heat bus {}".format(i),carrier="heat")
network.buses
[3]:
| attribute | v_nom | type | x | y | carrier | unit | v_mag_pu_set | v_mag_pu_min | v_mag_pu_max | control | sub_network |
|---|---|---|---|---|---|---|---|---|---|---|---|
| electric bus 0 | 20.0 | 0.0 | 0.0 | AC | None | 1.0 | 0.0 | inf | PQ | ||
| heat bus 0 | 1.0 | 0.0 | 0.0 | heat | None | 1.0 | 0.0 | inf | PQ | ||
| electric bus 1 | 20.0 | 0.0 | 0.0 | AC | None | 1.0 | 0.0 | inf | PQ | ||
| heat bus 1 | 1.0 | 0.0 | 0.0 | heat | None | 1.0 | 0.0 | inf | PQ | ||
| electric bus 2 | 20.0 | 0.0 | 0.0 | AC | None | 1.0 | 0.0 | inf | PQ | ||
| heat bus 2 | 1.0 | 0.0 | 0.0 | heat | None | 1.0 | 0.0 | inf | PQ |
[4]:
network.buses["carrier"].value_counts()
[4]:
AC 3
heat 3
Name: carrier, dtype: int64
Add three lines in a ring
[5]:
for i in range(3):
network.add("Line","line {}".format(i),
bus0="electric bus {}".format(i),
bus1="electric bus {}".format((i+1)%3),
x=0.1,
s_nom=1000)
network.lines
[5]:
| attribute | bus0 | bus1 | type | x | r | g | b | s_nom | s_nom_extendable | s_nom_min | ... | v_ang_min | v_ang_max | sub_network | x_pu | r_pu | g_pu | b_pu | x_pu_eff | r_pu_eff | s_nom_opt |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| line 0 | electric bus 0 | electric bus 1 | 0.1 | 0.0 | 0.0 | 0.0 | 1000.0 | False | 0.0 | ... | -inf | inf | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | ||
| line 1 | electric bus 1 | electric bus 2 | 0.1 | 0.0 | 0.0 | 0.0 | 1000.0 | False | 0.0 | ... | -inf | inf | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | ||
| line 2 | electric bus 2 | electric bus 0 | 0.1 | 0.0 | 0.0 | 0.0 | 1000.0 | False | 0.0 | ... | -inf | inf | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
3 rows × 29 columns
Connect the electric to the heat buses with heat pumps with COP 3
[6]:
for i in range(3):
network.add("Link",
"heat pump {}".format(i),
bus0="electric bus {}".format(i),
bus1="heat bus {}".format(i),
p_nom=100,
efficiency=3.)
network.links
[6]:
| attribute | bus0 | bus1 | type | carrier | efficiency | build_year | lifetime | p_nom | p_nom_extendable | p_nom_min | p_nom_max | p_set | p_min_pu | p_max_pu | capital_cost | marginal_cost | length | terrain_factor | p_nom_opt |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| heat pump 0 | electric bus 0 | heat bus 0 | 3.0 | 0 | inf | 100.0 | False | 0.0 | inf | 0.0 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 1.0 | 0.0 | ||
| heat pump 1 | electric bus 1 | heat bus 1 | 3.0 | 0 | inf | 100.0 | False | 0.0 | inf | 0.0 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 1.0 | 0.0 | ||
| heat pump 2 | electric bus 2 | heat bus 2 | 3.0 | 0 | inf | 100.0 | False | 0.0 | inf | 0.0 | 0.0 | 1.0 | 0.0 | 0.0 | 0.0 | 1.0 | 0.0 |
Add carriers
[7]:
network.add("Carrier","gas", co2_emissions=0.27)
network.add("Carrier","biomass", co2_emissions=0.)
network.carriers
[7]:
| attribute | co2_emissions | color | nice_name | max_growth |
|---|---|---|---|---|
| gas | 0.27 | inf | ||
| biomass | 0.00 | inf |
Add a gas generator at bus 0, a biomass generator at bus 1 and a boiler at all heat buses
[8]:
network.add("Generator","gas generator",
bus="electric bus 0",
p_nom=100,
marginal_cost=50,
carrier="gas",
efficiency=0.3)
network.add("Generator","biomass generator",
bus="electric bus 1",
p_nom=100,
marginal_cost=100,
efficiency=0.3,
carrier="biomass")
for i in range(3):
network.add("Generator","boiler {}".format(i),
bus="heat bus {}".format(i),
p_nom=1000,
efficiency=0.9,
marginal_cost=20.,
carrier="gas")
network.generators
[8]:
| attribute | bus | control | type | p_nom | p_nom_extendable | p_nom_min | p_nom_max | p_min_pu | p_max_pu | p_set | ... | shut_down_cost | min_up_time | min_down_time | up_time_before | down_time_before | ramp_limit_up | ramp_limit_down | ramp_limit_start_up | ramp_limit_shut_down | p_nom_opt |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| gas generator | electric bus 0 | PQ | 100.0 | False | 0.0 | inf | 0.0 | 1.0 | 0.0 | ... | 0.0 | 0 | 0 | 1 | 0 | NaN | NaN | 1.0 | 1.0 | 0.0 | |
| biomass generator | electric bus 1 | PQ | 100.0 | False | 0.0 | inf | 0.0 | 1.0 | 0.0 | ... | 0.0 | 0 | 0 | 1 | 0 | NaN | NaN | 1.0 | 1.0 | 0.0 | |
| boiler 0 | heat bus 0 | PQ | 1000.0 | False | 0.0 | inf | 0.0 | 1.0 | 0.0 | ... | 0.0 | 0 | 0 | 1 | 0 | NaN | NaN | 1.0 | 1.0 | 0.0 | |
| boiler 1 | heat bus 1 | PQ | 1000.0 | False | 0.0 | inf | 0.0 | 1.0 | 0.0 | ... | 0.0 | 0 | 0 | 1 | 0 | NaN | NaN | 1.0 | 1.0 | 0.0 | |
| boiler 2 | heat bus 2 | PQ | 1000.0 | False | 0.0 | inf | 0.0 | 1.0 | 0.0 | ... | 0.0 | 0 | 0 | 1 | 0 | NaN | NaN | 1.0 | 1.0 | 0.0 |
5 rows × 30 columns
Add electric loads and heat loads.
[9]:
for i in range(3):
network.add("Load","electric load {}".format(i),
bus="electric bus {}".format(i),
p_set=i*10)
for i in range(3):
network.add("Load","heat load {}".format(i),
bus="heat bus {}".format(i),
p_set=(3-i)*10)
network.loads
[9]:
| attribute | bus | carrier | type | p_set | q_set | sign |
|---|---|---|---|---|---|---|
| electric load 0 | electric bus 0 | 0.0 | 0.0 | -1.0 | ||
| electric load 1 | electric bus 1 | 10.0 | 0.0 | -1.0 | ||
| electric load 2 | electric bus 2 | 20.0 | 0.0 | -1.0 | ||
| heat load 0 | heat bus 0 | 30.0 | 0.0 | -1.0 | ||
| heat load 1 | heat bus 1 | 20.0 | 0.0 | -1.0 | ||
| heat load 2 | heat bus 2 | 10.0 | 0.0 | -1.0 |
We define a function for the LOPF
[10]:
def run_lopf():
network.lopf()
df = pd.concat([network.generators_t.p.loc['now'],
network.links_t.p0.loc['now'],
network.loads_t.p.loc['now']],
keys=['Generators', 'Links', 'Line'],
names=['Component', 'index']).reset_index(name='Production')
sns.barplot(data=df, x='index', y='Production', hue='Component')
plt.title(f'Objective: {network.objective}')
plt.xticks(rotation=90)
plt.tight_layout()
[11]:
run_lopf()
INFO:pypsa.opf:Performed preliminary steps
INFO:pypsa.opf:Building pyomo model using `kirchhoff` formulation
INFO:pypsa.opf:Solving model using glpk
WARNING:pyomo.solvers:Could not locate the 'glpsol' executable, which is required for solver 'glpk'
---------------------------------------------------------------------------
ApplicationError Traceback (most recent call last)
/tmp/ipykernel_471/262315099.py in <module>
----> 1 run_lopf()
/tmp/ipykernel_471/3405664651.py in run_lopf()
1 def run_lopf():
----> 2 network.lopf()
3 df = pd.concat([network.generators_t.p.loc['now'],
4 network.links_t.p0.loc['now'],
5 network.loads_t.p.loc['now']],
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pypsa/components.py in lopf(self, snapshots, pyomo, solver_name, solver_options, solver_logfile, formulation, keep_files, extra_functionality, multi_investment_periods, **kwargs)
646
647 if pyomo:
--> 648 return network_lopf(self, **args)
649 else:
650 return network_lopf_lowmem(self, **args)
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pypsa/opf.py in network_lopf(network, snapshots, solver_name, solver_io, skip_pre, extra_functionality, multi_investment_periods, solver_logfile, solver_options, keep_files, formulation, ptdf_tolerance, free_memory, extra_postprocessing)
1663 solver_logfile=solver_logfile, solver_options=solver_options,
1664 keep_files=keep_files, free_memory=free_memory,
-> 1665 extra_postprocessing=extra_postprocessing)
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pypsa/opf.py in network_lopf_solve(network, snapshots, formulation, solver_options, solver_logfile, keep_files, free_memory, extra_postprocessing)
1563 network.results = network.opt.solve(*args, suffixes=["dual"], keepfiles=keep_files, logfile=solver_logfile, options=solver_options)
1564 else:
-> 1565 network.results = network.opt.solve(*args, suffixes=["dual"], keepfiles=keep_files, logfile=solver_logfile, options=solver_options)
1566
1567 if logger.isEnabledFor(logging.INFO):
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pyomo/opt/base/solvers.py in solve(self, *args, **kwds)
510 """ Solve the problem """
511
--> 512 self.available(exception_flag=True)
513 #
514 # If the inputs are models, then validate that they have been
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pyomo/opt/solver/shellcmd.py in available(self, exception_flag)
123 if exception_flag:
124 msg = "No executable found for solver '%s'"
--> 125 raise ApplicationError(msg % self.name)
126 return False
127 return True
ApplicationError: No executable found for solver 'glpk'
Now, rerun with marginal costs for the heat pump operation.
[12]:
network.links.marginal_cost = 10
run_lopf()
INFO:pypsa.opf:Performed preliminary steps
INFO:pypsa.opf:Building pyomo model using `kirchhoff` formulation
INFO:pypsa.opf:Solving model using glpk
WARNING:pyomo.solvers:Could not locate the 'glpsol' executable, which is required for solver 'glpk'
---------------------------------------------------------------------------
ApplicationError Traceback (most recent call last)
/tmp/ipykernel_471/4148435902.py in <module>
1 network.links.marginal_cost = 10
----> 2 run_lopf()
/tmp/ipykernel_471/3405664651.py in run_lopf()
1 def run_lopf():
----> 2 network.lopf()
3 df = pd.concat([network.generators_t.p.loc['now'],
4 network.links_t.p0.loc['now'],
5 network.loads_t.p.loc['now']],
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pypsa/components.py in lopf(self, snapshots, pyomo, solver_name, solver_options, solver_logfile, formulation, keep_files, extra_functionality, multi_investment_periods, **kwargs)
646
647 if pyomo:
--> 648 return network_lopf(self, **args)
649 else:
650 return network_lopf_lowmem(self, **args)
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pypsa/opf.py in network_lopf(network, snapshots, solver_name, solver_io, skip_pre, extra_functionality, multi_investment_periods, solver_logfile, solver_options, keep_files, formulation, ptdf_tolerance, free_memory, extra_postprocessing)
1663 solver_logfile=solver_logfile, solver_options=solver_options,
1664 keep_files=keep_files, free_memory=free_memory,
-> 1665 extra_postprocessing=extra_postprocessing)
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pypsa/opf.py in network_lopf_solve(network, snapshots, formulation, solver_options, solver_logfile, keep_files, free_memory, extra_postprocessing)
1563 network.results = network.opt.solve(*args, suffixes=["dual"], keepfiles=keep_files, logfile=solver_logfile, options=solver_options)
1564 else:
-> 1565 network.results = network.opt.solve(*args, suffixes=["dual"], keepfiles=keep_files, logfile=solver_logfile, options=solver_options)
1566
1567 if logger.isEnabledFor(logging.INFO):
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pyomo/opt/base/solvers.py in solve(self, *args, **kwds)
510 """ Solve the problem """
511
--> 512 self.available(exception_flag=True)
513 #
514 # If the inputs are models, then validate that they have been
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pyomo/opt/solver/shellcmd.py in available(self, exception_flag)
123 if exception_flag:
124 msg = "No executable found for solver '%s'"
--> 125 raise ApplicationError(msg % self.name)
126 return False
127 return True
ApplicationError: No executable found for solver 'glpk'
Finally, rerun with no CO2 emissions.
[13]:
network.add("GlobalConstraint",
"co2_limit",
sense="<=",
constant=0.)
run_lopf()
INFO:pypsa.opf:Performed preliminary steps
INFO:pypsa.opf:Building pyomo model using `kirchhoff` formulation
INFO:pypsa.opf:Solving model using glpk
WARNING:pyomo.solvers:Could not locate the 'glpsol' executable, which is required for solver 'glpk'
---------------------------------------------------------------------------
ApplicationError Traceback (most recent call last)
/tmp/ipykernel_471/2487073327.py in <module>
4 constant=0.)
5
----> 6 run_lopf()
/tmp/ipykernel_471/3405664651.py in run_lopf()
1 def run_lopf():
----> 2 network.lopf()
3 df = pd.concat([network.generators_t.p.loc['now'],
4 network.links_t.p0.loc['now'],
5 network.loads_t.p.loc['now']],
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pypsa/components.py in lopf(self, snapshots, pyomo, solver_name, solver_options, solver_logfile, formulation, keep_files, extra_functionality, multi_investment_periods, **kwargs)
646
647 if pyomo:
--> 648 return network_lopf(self, **args)
649 else:
650 return network_lopf_lowmem(self, **args)
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pypsa/opf.py in network_lopf(network, snapshots, solver_name, solver_io, skip_pre, extra_functionality, multi_investment_periods, solver_logfile, solver_options, keep_files, formulation, ptdf_tolerance, free_memory, extra_postprocessing)
1663 solver_logfile=solver_logfile, solver_options=solver_options,
1664 keep_files=keep_files, free_memory=free_memory,
-> 1665 extra_postprocessing=extra_postprocessing)
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pypsa/opf.py in network_lopf_solve(network, snapshots, formulation, solver_options, solver_logfile, keep_files, free_memory, extra_postprocessing)
1563 network.results = network.opt.solve(*args, suffixes=["dual"], keepfiles=keep_files, logfile=solver_logfile, options=solver_options)
1564 else:
-> 1565 network.results = network.opt.solve(*args, suffixes=["dual"], keepfiles=keep_files, logfile=solver_logfile, options=solver_options)
1566
1567 if logger.isEnabledFor(logging.INFO):
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pyomo/opt/base/solvers.py in solve(self, *args, **kwds)
510 """ Solve the problem """
511
--> 512 self.available(exception_flag=True)
513 #
514 # If the inputs are models, then validate that they have been
~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pyomo/opt/solver/shellcmd.py in available(self, exception_flag)
123 if exception_flag:
124 msg = "No executable found for solver '%s'"
--> 125 raise ApplicationError(msg % self.name)
126 return False
127 return True
ApplicationError: No executable found for solver 'glpk'