Constraining the total capacity per bus and carrier

Note

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

Constraining the total capacity per bus and carrier#

In this small example, we limit the nominal capacity of generators of the same production carrier at the same bus.

Therefore, we introduce a column nom_min_{carrier} and nom_max_{carrier} in the buses dataframe. These are then used as lower and upper bounds of generators of the same carrier at the same bus.

We start with importing a small example network.

[1]:
import pypsa
import pandas as pd
ERROR 1: PROJ: proj_create_from_database: Open of /home/docs/checkouts/readthedocs.org/user_builds/pypsa/conda/latest/share/proj failed
[2]:
n = pypsa.examples.ac_dc_meshed(from_master=True)
n.links_t.p_set = pd.DataFrame(
    index=n.snapshots
)  # remove forced fixed values in optimization
WARNING:pypsa.io:Importing network from PyPSA version v0.17.1 while current version is v0.27.1. Read the release notes at https://pypsa.readthedocs.io/en/latest/release_notes.html to prepare your network for import.
INFO:pypsa.io:Imported network ac-dc-meshed.nc has buses, carriers, generators, global_constraints, lines, links, loads

Now add a second wind generator at bus ‘Frankfurt’ and limit the combined capacity.

[3]:
n.add(
    "Generator",
    "Frankfurt Wind 2",
    bus="Frankfurt",
    capital_cost=120,
    carrier="wind",
    p_nom_extendable=True,
)

n.buses.loc[["Frankfurt", "Manchester"], "nom_min_wind"] = 2000
n.buses.loc[["Frankfurt"], "nom_max_wind"] = 2200

We are running the lopf and check whether the constraint is fulfilled.

[4]:
n.optimize()
WARNING:pypsa.components:The following lines have zero x, which could break the linear load flow:
Index(['2', '3', '4'], dtype='object', name='Line')
WARNING:pypsa.components:The following lines have zero r, which could break the linear load flow:
Index(['0', '1', '5', '6'], dtype='object', name='Line')
WARNING:pypsa.components:The following lines have zero x, which could break the linear load flow:
Index(['2', '3', '4'], dtype='object', name='Line')
WARNING:pypsa.components:The following lines have zero r, which could break the linear load flow:
Index(['0', '1', '5', '6'], dtype='object', name='Line')
INFO:linopy.model: Solve problem using Glpk solver
INFO:linopy.io: Writing time: 0.07s
INFO:linopy.solvers:GLPSOL--GLPK LP/MIP Solver 5.0
Parameter(s) specified in the command line:
 --lp /tmp/linopy-problem-6zrvy0wl.lp --output /tmp/linopy-solve-3hjqmr62.sol
Reading problem data from '/tmp/linopy-problem-6zrvy0wl.lp'...
492 rows, 199 columns, 1053 non-zeros
2806 lines were read
GLPK Simplex Optimizer 5.0
492 rows, 199 columns, 1053 non-zeros
Preprocessing...
403 rows, 198 columns, 964 non-zeros
Scaling...
 A: min|aij| =  9.693e-03  max|aij| =  1.215e+00  ratio =  1.254e+02
GM: min|aij| =  5.786e-01  max|aij| =  1.728e+00  ratio =  2.987e+00
EQ: min|aij| =  3.377e-01  max|aij| =  1.000e+00  ratio =  2.961e+00
Constructing initial basis...
Size of triangular part is 403
      0: obj =  -1.578758761e+07 inf =   7.934e+04 (102)
    164: obj =  -1.007787982e+07 inf =   1.126e-12 (0) 1
*   244: obj =  -1.379307825e+07 inf =   9.329e-13 (0) 1
OPTIMAL LP SOLUTION FOUND
Time used:   0.0 secs
Memory used: 0.6 Mb (680925 bytes)
Writing basic solution to '/tmp/linopy-solve-3hjqmr62.sol'...

INFO:linopy.constants: Optimization successful:
Status: ok
Termination condition: optimal
Solution: 199 primals, 492 duals
Objective: -1.38e+07
Solver model: not available
Solver message: optimal

/home/docs/checkouts/readthedocs.org/user_builds/pypsa/conda/latest/lib/python3.11/site-packages/pypsa/optimization/optimize.py:357: FutureWarning: A value is trying to be set on a copy of a DataFrame or Series through chained assignment using an inplace method.
The behavior will change in pandas 3.0. This inplace method will never work because the intermediate object on which we are setting values always behaves as a copy.

For example, when doing 'df[col].method(value, inplace=True)', try using 'df.method({col: value}, inplace=True)' or df[col] = df[col].method(value) instead, to perform the operation inplace on the original object.


  n.df(c)[attr + "_opt"].update(df)
INFO:pypsa.optimization.optimize:The shadow-prices of the constraints Generator-ext-p-lower, Generator-ext-p-upper, Line-ext-s-lower, Line-ext-s-upper, Link-ext-p-lower, Link-ext-p-upper, Kirchhoff-Voltage-Law were not assigned to the network.
[4]:
('ok', 'optimal')
[5]:
n.generators[["p_nom_opt"]]
[5]:
p_nom_opt
Generator
Manchester Wind 2000.0000
Manchester Gas 0.0000
Norway Wind 895.3730
Norway Gas 91.0015
Frankfurt Wind 100.0000
Frankfurt Gas 884.0930
Frankfurt Wind 2 2100.0000

Looks good! The generators of carrier ‘wind’ at bus ‘Frankfurt’ are just the limit of 2200 MW.