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)})
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In [1], line 1
----> 1 import pypsa
      2 import numpy as np
      3 import pandas as pd

File ~/checkouts/readthedocs.org/user_builds/pypsa/conda/v0.21.2/lib/python3.11/site-packages/pypsa/__init__.py:10
      1 # -*- coding: utf-8 -*-
      4 """
      5 Python for Power Systems Analysis (PyPSA)
      6
      7 Grid calculation library.
      8 """
---> 10 from pypsa import (
     11     components,
     12     contingency,
     13     descriptors,
     14     examples,
     15     geo,
     16     io,
     17     linopf,
     18     linopt,
     19     networkclustering,
     20     opf,
     21     opt,
     22     optimization,
     23     pf,
     24     plot,
     25 )
     26 from pypsa.components import Network, SubNetwork
     28 __version__ = "0.21.2"

File ~/checkouts/readthedocs.org/user_builds/pypsa/conda/v0.21.2/lib/python3.11/site-packages/pypsa/components.py:50
     37 from pypsa.io import (
     38     export_to_csv_folder,
     39     export_to_hdf5,
   (...)
     47     import_series_from_dataframe,
     48 )
     49 from pypsa.opf import network_lopf, network_opf
---> 50 from pypsa.optimization.optimize import OptimizationAccessor
     51 from pypsa.pf import (
     52     calculate_B_H,
     53     calculate_dependent_values,
   (...)
     62     sub_network_pf,
     63 )
     64 from pypsa.plot import iplot, plot

File ~/checkouts/readthedocs.org/user_builds/pypsa/conda/v0.21.2/lib/python3.11/site-packages/pypsa/optimization/__init__.py:7
      1 #!/usr/bin/env python3
      2 # -*- coding: utf-8 -*-
      3 """
      4 Build optimisation problems from PyPSA networks with Linopy.
      5 """
----> 7 from pypsa.optimization import abstract, constraints, optimize, variables
      8 from pypsa.optimization.optimize import create_model

File ~/checkouts/readthedocs.org/user_builds/pypsa/conda/v0.21.2/lib/python3.11/site-packages/pypsa/optimization/constraints.py:9
      6 import logging
      8 import pandas as pd
----> 9 from linopy.expressions import LinearExpression, merge
     10 from numpy import arange, cumsum, inf, nan, roll
     11 from scipy import sparse

File ~/checkouts/readthedocs.org/user_builds/pypsa/conda/v0.21.2/lib/python3.11/site-packages/linopy/__init__.py:9
      1 #!/usr/bin/env python3
      2 # -*- coding: utf-8 -*-
      3 """
      4 Created on Wed Mar 10 11:03:06 2021.
      5
      6 @author: fabulous
      7 """
----> 9 from linopy import model, remote
     10 from linopy.expressions import merge
     11 from linopy.io import read_netcdf

File ~/checkouts/readthedocs.org/user_builds/pypsa/conda/v0.21.2/lib/python3.11/site-packages/linopy/model.py:22
     20 from linopy import solvers
     21 from linopy.common import best_int, replace_by_map
---> 22 from linopy.constraints import (
     23     AnonymousConstraint,
     24     AnonymousScalarConstraint,
     25     Constraints,
     26 )
     27 from linopy.eval import Expr
     28 from linopy.expressions import LinearExpression, ScalarLinearExpression

File ~/checkouts/readthedocs.org/user_builds/pypsa/conda/v0.21.2/lib/python3.11/site-packages/linopy/constraints.py:21
     18 from scipy.sparse import coo_matrix
     19 from xarray import DataArray, Dataset
---> 21 from linopy import expressions, variables
     22 from linopy.common import (
     23     _merge_inplace,
     24     has_assigned_model,
   (...)
     27     replace_by_map,
     28 )
     31 class Constraint(DataArray):

File ~/checkouts/readthedocs.org/user_builds/pypsa/conda/v0.21.2/lib/python3.11/site-packages/linopy/expressions.py:23
     20 from xarray.core.dataarray import DataArrayCoordinates
     21 from xarray.core.groupby import _maybe_reorder, peek_at
---> 23 from linopy import constraints, variables
     24 from linopy.common import as_dataarray
     27 def exprwrap(method, *default_args, **new_default_kwargs):

File ~/checkouts/readthedocs.org/user_builds/pypsa/conda/v0.21.2/lib/python3.11/site-packages/linopy/variables.py:398
    393     roll = varwrap(DataArray.roll)
    395     rolling = varwrap(DataArray.rolling)
--> 398 @dataclass(repr=False)
    399 class Variables:
    400     """
    401     A variables container used for storing multiple variable arrays.
    402     """
    404     labels: Dataset = Dataset()

File ~/checkouts/readthedocs.org/user_builds/pypsa/conda/v0.21.2/lib/python3.11/dataclasses.py:1211, in dataclass.<locals>.wrap(cls)
   1210 def wrap(cls):
-> 1211     return _process_class(cls, init, repr, eq, order, unsafe_hash,
   1212                           frozen, match_args, kw_only, slots,
   1213                           weakref_slot)

File ~/checkouts/readthedocs.org/user_builds/pypsa/conda/v0.21.2/lib/python3.11/dataclasses.py:959, in _process_class(cls, init, repr, eq, order, unsafe_hash, frozen, match_args, kw_only, slots, weakref_slot)
    956         kw_only = True
    957     else:
    958         # Otherwise it's a field of some type.
--> 959         cls_fields.append(_get_field(cls, name, type, kw_only))
    961 for f in cls_fields:
    962     fields[f.name] = f

File ~/checkouts/readthedocs.org/user_builds/pypsa/conda/v0.21.2/lib/python3.11/dataclasses.py:816, in _get_field(cls, a_name, a_type, default_kw_only)
    812 # For real fields, disallow mutable defaults.  Use unhashable as a proxy
    813 # indicator for mutability.  Read the __hash__ attribute from the class,
    814 # not the instance.
    815 if f._field_type is _FIELD and f.default.__class__.__hash__ is None:
--> 816     raise ValueError(f'mutable default {type(f.default)} for field '
    817                      f'{f.name} is not allowed: use default_factory')
    819 return f

ValueError: mutable default <class 'xarray.core.dataset.Dataset'> for field labels is not allowed: use default_factory
[2]:
network = pypsa.Network()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [2], line 1
----> 1 network = pypsa.Network()

NameError: name 'pypsa' is not defined

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.0)
    network.add("Bus", "heat bus {}".format(i), carrier="heat")
network.buses
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [3], line 2
      1 for i in range(3):
----> 2     network.add("Bus", "electric bus {}".format(i), v_nom=20.0)
      3     network.add("Bus", "heat bus {}".format(i), carrier="heat")
      4 network.buses

NameError: name 'network' is not defined
[4]:
network.buses["carrier"].value_counts()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [4], line 1
----> 1 network.buses["carrier"].value_counts()

NameError: name 'network' is not defined

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
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [5], line 2
      1 for i in range(3):
----> 2     network.add(
      3         "Line",
      4         "line {}".format(i),
      5         bus0="electric bus {}".format(i),
      6         bus1="electric bus {}".format((i + 1) % 3),
      7         x=0.1,
      8         s_nom=1000,
      9     )
     10 network.lines

NameError: name 'network' is not defined

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.0,
    )
network.links
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [6], line 2
      1 for i in range(3):
----> 2     network.add(
      3         "Link",
      4         "heat pump {}".format(i),
      5         bus0="electric bus {}".format(i),
      6         bus1="heat bus {}".format(i),
      7         p_nom=100,
      8         efficiency=3.0,
      9     )
     10 network.links

NameError: name 'network' is not defined

Add carriers

[7]:
network.add("Carrier", "gas", co2_emissions=0.27)
network.add("Carrier", "biomass", co2_emissions=0.0)
network.carriers
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [7], line 1
----> 1 network.add("Carrier", "gas", co2_emissions=0.27)
      2 network.add("Carrier", "biomass", co2_emissions=0.0)
      3 network.carriers

NameError: name 'network' is not defined

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.0,
        carrier="gas",
    )

network.generators
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [8], line 1
----> 1 network.add(
      2     "Generator",
      3     "gas generator",
      4     bus="electric bus 0",
      5     p_nom=100,
      6     marginal_cost=50,
      7     carrier="gas",
      8     efficiency=0.3,
      9 )
     11 network.add(
     12     "Generator",
     13     "biomass generator",
   (...)
     18     carrier="biomass",
     19 )
     21 for i in range(3):

NameError: name 'network' is not defined

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
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [9], line 2
      1 for i in range(3):
----> 2     network.add(
      3         "Load",
      4         "electric load {}".format(i),
      5         bus="electric bus {}".format(i),
      6         p_set=i * 10,
      7     )
      9 for i in range(3):
     10     network.add(
     11         "Load",
     12         "heat load {}".format(i),
     13         bus="heat bus {}".format(i),
     14         p_set=(3 - i) * 10,
     15     )

NameError: name 'network' is not defined

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()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [11], line 1
----> 1 run_lopf()

Cell In [10], line 2, in run_lopf()
      1 def run_lopf():
----> 2     network.lopf()
      3     df = pd.concat(
      4         [
      5             network.generators_t.p.loc["now"],
   (...)
     10         names=["Component", "index"],
     11     ).reset_index(name="Production")
     13     sns.barplot(data=df, x="index", y="Production", hue="Component")

NameError: name 'network' is not defined

Now, rerun with marginal costs for the heat pump operation.

[12]:
network.links.marginal_cost = 10
run_lopf()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [12], line 1
----> 1 network.links.marginal_cost = 10
      2 run_lopf()

NameError: name 'network' is not defined

Finally, rerun with no CO2 emissions.

[13]:
network.add("GlobalConstraint", "co2_limit", sense="<=", constant=0.0)

run_lopf()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [13], line 1
----> 1 network.add("GlobalConstraint", "co2_limit", sense="<=", constant=0.0)
      3 run_lopf()

NameError: name 'network' is not defined