Note

You can download this example as a Jupyter notebook or start it in interactive mode.

Unit commitment

This tutorial runs through examples of unit commitment for generators at a single bus. Examples of minimum part-load, minimum up time, minimum down time, start up costs, shut down costs and ramp rate restrictions are shown.

To enable unit commitment on a generator, set its attribute committable = True.

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

Minimum part load demonstration

In final hour load goes below part-load limit of coal gen (30%), forcing gas to commit.

[2]:
nu = pypsa.Network(snapshots=range(4))

nu.add("Bus", "bus")

nu.add(
    "Generator",
    "coal",
    bus="bus",
    committable=True,
    p_min_pu=0.3,
    marginal_cost=20,
    p_nom=10000,
)

nu.add(
    "Generator",
    "gas",
    bus="bus",
    committable=True,
    marginal_cost=70,
    p_min_pu=0.1,
    p_nom=1000,
)

nu.add("Load", "load", bus="bus", p_set=[4000, 6000, 5000, 800])
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [2], line 1
----> 1 nu = pypsa.Network(snapshots=range(4))
      3 nu.add("Bus", "bus")
      5 nu.add(
      6     "Generator",
      7     "coal",
   (...)
     12     p_nom=10000,
     13 )

NameError: name 'pypsa' is not defined
[3]:
nu.lopf()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [3], line 1
----> 1 nu.lopf()

NameError: name 'nu' is not defined
[4]:
nu.generators_t.status
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [4], line 1
----> 1 nu.generators_t.status

NameError: name 'nu' is not defined
[5]:
nu.generators_t.p
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [5], line 1
----> 1 nu.generators_t.p

NameError: name 'nu' is not defined

Minimum up time demonstration

Gas has minimum up time, forcing it to be online longer

[6]:
nu = pypsa.Network(snapshots=range(4))

nu.add("Bus", "bus")

nu.add(
    "Generator",
    "coal",
    bus="bus",
    committable=True,
    p_min_pu=0.3,
    marginal_cost=20,
    p_nom=10000,
)

nu.add(
    "Generator",
    "gas",
    bus="bus",
    committable=True,
    marginal_cost=70,
    p_min_pu=0.1,
    up_time_before=0,
    min_up_time=3,
    p_nom=1000,
)

nu.add("Load", "load", bus="bus", p_set=[4000, 800, 5000, 3000])
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [6], line 1
----> 1 nu = pypsa.Network(snapshots=range(4))
      3 nu.add("Bus", "bus")
      5 nu.add(
      6     "Generator",
      7     "coal",
   (...)
     12     p_nom=10000,
     13 )

NameError: name 'pypsa' is not defined
[7]:
nu.lopf()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [7], line 1
----> 1 nu.lopf()

NameError: name 'nu' is not defined
[8]:
nu.generators_t.status
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [8], line 1
----> 1 nu.generators_t.status

NameError: name 'nu' is not defined
[9]:
nu.generators_t.p
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [9], line 1
----> 1 nu.generators_t.p

NameError: name 'nu' is not defined

Minimum down time demonstration

Coal has a minimum down time, forcing it to go off longer.

[10]:
nu = pypsa.Network(snapshots=range(4))

nu.add("Bus", "bus")

nu.add(
    "Generator",
    "coal",
    bus="bus",
    committable=True,
    p_min_pu=0.3,
    marginal_cost=20,
    min_down_time=2,
    down_time_before=1,
    p_nom=10000,
)

nu.add(
    "Generator",
    "gas",
    bus="bus",
    committable=True,
    marginal_cost=70,
    p_min_pu=0.1,
    p_nom=4000,
)

nu.add("Load", "load", bus="bus", p_set=[3000, 800, 3000, 8000])
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [10], line 1
----> 1 nu = pypsa.Network(snapshots=range(4))
      3 nu.add("Bus", "bus")
      5 nu.add(
      6     "Generator",
      7     "coal",
   (...)
     14     p_nom=10000,
     15 )

NameError: name 'pypsa' is not defined
[11]:
nu.lopf()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [11], line 1
----> 1 nu.lopf()

NameError: name 'nu' is not defined
[12]:
nu.objective
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [12], line 1
----> 1 nu.objective

NameError: name 'nu' is not defined
[13]:
nu.generators_t.status
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [13], line 1
----> 1 nu.generators_t.status

NameError: name 'nu' is not defined
[14]:
nu.generators_t.p
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [14], line 1
----> 1 nu.generators_t.p

NameError: name 'nu' is not defined

Start up and shut down costs

Now there are associated costs for shutting down, etc

[15]:
nu = pypsa.Network(snapshots=range(4))

nu.add("Bus", "bus")

nu.add(
    "Generator",
    "coal",
    bus="bus",
    committable=True,
    p_min_pu=0.3,
    marginal_cost=20,
    min_down_time=2,
    start_up_cost=5000,
    p_nom=10000,
)

nu.add(
    "Generator",
    "gas",
    bus="bus",
    committable=True,
    marginal_cost=70,
    p_min_pu=0.1,
    shut_down_cost=25,
    p_nom=4000,
)

nu.add("Load", "load", bus="bus", p_set=[3000, 800, 3000, 8000])
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [15], line 1
----> 1 nu = pypsa.Network(snapshots=range(4))
      3 nu.add("Bus", "bus")
      5 nu.add(
      6     "Generator",
      7     "coal",
   (...)
     14     p_nom=10000,
     15 )

NameError: name 'pypsa' is not defined
[16]:
nu.lopf(nu.snapshots)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [16], line 1
----> 1 nu.lopf(nu.snapshots)

NameError: name 'nu' is not defined
[17]:
nu.objective
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [17], line 1
----> 1 nu.objective

NameError: name 'nu' is not defined
[18]:
nu.generators_t.status
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [18], line 1
----> 1 nu.generators_t.status

NameError: name 'nu' is not defined
[19]:
nu.generators_t.p
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [19], line 1
----> 1 nu.generators_t.p

NameError: name 'nu' is not defined

Ramp rate limits

[20]:
nu = pypsa.Network(snapshots=range(6))

nu.add("Bus", "bus")

nu.add(
    "Generator",
    "coal",
    bus="bus",
    marginal_cost=20,
    ramp_limit_up=0.1,
    ramp_limit_down=0.2,
    p_nom=10000,
)

nu.add("Generator", "gas", bus="bus", marginal_cost=70, p_nom=4000)

nu.add("Load", "load", bus="bus", p_set=[4000, 7000, 7000, 7000, 7000, 3000])
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [20], line 1
----> 1 nu = pypsa.Network(snapshots=range(6))
      3 nu.add("Bus", "bus")
      5 nu.add(
      6     "Generator",
      7     "coal",
   (...)
     12     p_nom=10000,
     13 )

NameError: name 'pypsa' is not defined
[21]:
nu.lopf()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [21], line 1
----> 1 nu.lopf()

NameError: name 'nu' is not defined
[22]:
nu.generators_t.p
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [22], line 1
----> 1 nu.generators_t.p

NameError: name 'nu' is not defined
[23]:
nu = pypsa.Network(snapshots=range(6))

nu.add("Bus", "bus")

nu.add(
    "Generator",
    "coal",
    bus="bus",
    marginal_cost=20,
    ramp_limit_up=0.1,
    ramp_limit_down=0.2,
    p_nom_extendable=True,
    capital_cost=1e2,
)

nu.add("Generator", "gas", bus="bus", marginal_cost=70, p_nom=4000)

nu.add("Load", "load", bus="bus", p_set=[4000, 7000, 7000, 7000, 7000, 3000])
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [23], line 1
----> 1 nu = pypsa.Network(snapshots=range(6))
      3 nu.add("Bus", "bus")
      5 nu.add(
      6     "Generator",
      7     "coal",
   (...)
     13     capital_cost=1e2,
     14 )

NameError: name 'pypsa' is not defined
[24]:
nu.lopf(nu.snapshots)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [24], line 1
----> 1 nu.lopf(nu.snapshots)

NameError: name 'nu' is not defined
[25]:
nu.generators.p_nom_opt
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [25], line 1
----> 1 nu.generators.p_nom_opt

NameError: name 'nu' is not defined
[26]:
nu.generators_t.p
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [26], line 1
----> 1 nu.generators_t.p

NameError: name 'nu' is not defined
[27]:
nu = pypsa.Network(snapshots=range(7))

nu.add("Bus", "bus")

# Can get bad interactions if SU > RU and p_min_pu; similarly if SD > RD
nu.add(
    "Generator",
    "coal",
    bus="bus",
    marginal_cost=20,
    committable=True,
    p_min_pu=0.05,
    initial_status=0,
    ramp_limit_start_up=0.1,
    ramp_limit_up=0.2,
    ramp_limit_down=0.25,
    ramp_limit_shut_down=0.15,
    p_nom=10000.0,
)

nu.add("Generator", "gas", bus="bus", marginal_cost=70, p_nom=10000)

nu.add("Load", "load", bus="bus", p_set=[0.0, 200.0, 7000, 7000, 7000, 2000, 0])
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [27], line 1
----> 1 nu = pypsa.Network(snapshots=range(7))
      3 nu.add("Bus", "bus")
      5 # Can get bad interactions if SU > RU and p_min_pu; similarly if SD > RD

NameError: name 'pypsa' is not defined
[28]:
nu.lopf()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [28], line 1
----> 1 nu.lopf()

NameError: name 'nu' is not defined
[29]:
nu.generators_t.p
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [29], line 1
----> 1 nu.generators_t.p

NameError: name 'nu' is not defined
[30]:
nu.generators_t.status
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [30], line 1
----> 1 nu.generators_t.status

NameError: name 'nu' is not defined
[31]:
nu.generators.loc["coal"]
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [31], line 1
----> 1 nu.generators.loc["coal"]

NameError: name 'nu' is not defined

Rolling horizon example

This example solves sequentially in batches

[32]:
sets_of_snapshots = 6
p_set = [4000, 5000, 700, 800, 4000]

nu = pypsa.Network(snapshots=range(len(p_set) * sets_of_snapshots))

nu.add("Bus", "bus")

nu.add(
    "Generator",
    "coal",
    bus="bus",
    committable=True,
    p_min_pu=0.3,
    marginal_cost=20,
    min_down_time=2,
    min_up_time=3,
    up_time_before=1,
    ramp_limit_up=1,
    ramp_limit_down=1,
    ramp_limit_start_up=1,
    ramp_limit_shut_down=1,
    shut_down_cost=150,
    start_up_cost=200,
    p_nom=10000,
)

nu.add(
    "Generator",
    "gas",
    bus="bus",
    committable=True,
    marginal_cost=70,
    p_min_pu=0.1,
    up_time_before=2,
    min_up_time=3,
    shut_down_cost=20,
    start_up_cost=50,
    p_nom=1000,
)

nu.add("Load", "load", bus="bus", p_set=p_set * sets_of_snapshots)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [32], line 4
      1 sets_of_snapshots = 6
      2 p_set = [4000, 5000, 700, 800, 4000]
----> 4 nu = pypsa.Network(snapshots=range(len(p_set) * sets_of_snapshots))
      6 nu.add("Bus", "bus")
      8 nu.add(
      9     "Generator",
     10     "coal",
   (...)
     24     p_nom=10000,
     25 )

NameError: name 'pypsa' is not defined
[33]:
overlap = 2
for i in range(sets_of_snapshots):
    nu.lopf(nu.snapshots[i * len(p_set) : (i + 1) * len(p_set) + overlap], pyomo=False)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [33], line 3
      1 overlap = 2
      2 for i in range(sets_of_snapshots):
----> 3     nu.lopf(nu.snapshots[i * len(p_set) : (i + 1) * len(p_set) + overlap], pyomo=False)

NameError: name 'nu' is not defined
[34]:
pd.concat(
    {"Active": nu.generators_t.status.astype(bool), "Output": nu.generators_t.p}, axis=1
)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In [34], line 1
----> 1 pd.concat(
      2     {"Active": nu.generators_t.status.astype(bool), "Output": nu.generators_t.p}, axis=1
      3 )

NameError: name 'pd' is not defined