Meshed AC-DC example

Note

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

Meshed AC-DC example#

This example has a 3-node AC network coupled via AC-DC converters to a 3-node DC network. There is also a single point-to-point DC using the Link component.

The data files for this example are in the examples folder of the github repository: PyPSA/PyPSA.

[1]:
import matplotlib.pyplot as plt

import pypsa

%matplotlib inline
plt.rc("figure", figsize=(8, 8))
[2]:
network = pypsa.examples.ac_dc_meshed(from_master=True)
WARNING:pypsa.io:Importing network from PyPSA version v0.17.1 while current version is v0.32.0. 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
[3]:
# get current type (AC or DC) of the lines from the buses
lines_current_type = network.lines.bus0.map(network.buses.carrier)
lines_current_type
[3]:
Line
0    AC
1    AC
2    DC
3    DC
4    DC
5    AC
6    AC
Name: bus0, dtype: object
[4]:
network.plot(
    line_colors=lines_current_type.map(lambda ct: "r" if ct == "DC" else "b"),
    title="Mixed AC (blue) - DC (red) network - DC (cyan)",
    color_geomap=True,
    jitter=0.3,
)
plt.tight_layout()
/home/docs/checkouts/readthedocs.org/user_builds/pypsa/envs/latest/lib/python3.13/site-packages/cartopy/mpl/feature_artist.py:144: UserWarning: facecolor will have no effect as it has been defined as "never".
  warnings.warn('facecolor will have no effect as it has been '
/home/docs/checkouts/readthedocs.org/user_builds/pypsa/envs/latest/lib/python3.13/site-packages/cartopy/io/__init__.py:241: DownloadWarning: Downloading: https://naturalearth.s3.amazonaws.com/50m_physical/ne_50m_land.zip
  warnings.warn(f'Downloading: {url}', DownloadWarning)
/home/docs/checkouts/readthedocs.org/user_builds/pypsa/envs/latest/lib/python3.13/site-packages/cartopy/io/__init__.py:241: DownloadWarning: Downloading: https://naturalearth.s3.amazonaws.com/50m_physical/ne_50m_ocean.zip
  warnings.warn(f'Downloading: {url}', DownloadWarning)
/home/docs/checkouts/readthedocs.org/user_builds/pypsa/envs/latest/lib/python3.13/site-packages/cartopy/io/__init__.py:241: DownloadWarning: Downloading: https://naturalearth.s3.amazonaws.com/50m_cultural/ne_50m_admin_0_boundary_lines_land.zip
  warnings.warn(f'Downloading: {url}', DownloadWarning)
/home/docs/checkouts/readthedocs.org/user_builds/pypsa/envs/latest/lib/python3.13/site-packages/cartopy/io/__init__.py:241: DownloadWarning: Downloading: https://naturalearth.s3.amazonaws.com/50m_physical/ne_50m_coastline.zip
  warnings.warn(f'Downloading: {url}', DownloadWarning)
../_images/examples_ac-dc-lopf_4_1.png
[5]:
network.links.loc["Norwich Converter", "p_nom_extendable"] = False

We inspect the topology of the network. Therefore use the function determine_network_topology and inspect the subnetworks in network.sub_networks.

[6]:
network.determine_network_topology()
network.sub_networks["n_branches"] = [
    len(sn.branches()) for sn in network.sub_networks.obj
]
network.sub_networks["n_buses"] = [len(sn.buses()) for sn in network.sub_networks.obj]

network.sub_networks
[6]:
carrier slack_bus obj n_branches n_buses
SubNetwork
0 AC Manchester <pypsa.networks.SubNetwork object at 0x7fb11f1... 3 3
1 DC Norwich DC <pypsa.networks.SubNetwork object at 0x7fb11dc... 3 3
2 AC Frankfurt <pypsa.networks.SubNetwork object at 0x7fb11dc... 1 2
3 AC Norway <pypsa.networks.SubNetwork object at 0x7fb11dd... 0 1

The network covers 10 time steps. These are given by the snapshots attribute.

[7]:
network.snapshots
[7]:
DatetimeIndex(['2015-01-01 00:00:00', '2015-01-01 01:00:00',
               '2015-01-01 02:00:00', '2015-01-01 03:00:00',
               '2015-01-01 04:00:00', '2015-01-01 05:00:00',
               '2015-01-01 06:00:00', '2015-01-01 07:00:00',
               '2015-01-01 08:00:00', '2015-01-01 09:00:00'],
              dtype='datetime64[ns]', name='snapshot', freq=None)

There are 6 generators in the network, 3 wind and 3 gas. All are attached to buses:

[8]:
network.generators
[8]:
bus control type p_nom p_nom_mod p_nom_extendable p_nom_min p_nom_max p_min_pu p_max_pu ... 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 weight p_nom_opt
Generator
Manchester Wind Manchester Slack 80.0 0.0 True 100.0 inf 0.0 1.0 ... 0 0 1 0 NaN NaN 1.0 1.0 1.0 0.0
Manchester Gas Manchester PQ 50000.0 0.0 True 0.0 inf 0.0 1.0 ... 0 0 1 0 NaN NaN 1.0 1.0 1.0 0.0
Norway Wind Norway Slack 100.0 0.0 True 100.0 inf 0.0 1.0 ... 0 0 1 0 NaN NaN 1.0 1.0 1.0 0.0
Norway Gas Norway PQ 20000.0 0.0 True 0.0 inf 0.0 1.0 ... 0 0 1 0 NaN NaN 1.0 1.0 1.0 0.0
Frankfurt Wind Frankfurt Slack 110.0 0.0 True 100.0 inf 0.0 1.0 ... 0 0 1 0 NaN NaN 1.0 1.0 1.0 0.0
Frankfurt Gas Frankfurt PQ 80000.0 0.0 True 0.0 inf 0.0 1.0 ... 0 0 1 0 NaN NaN 1.0 1.0 1.0 0.0

6 rows × 37 columns

We see that the generators have different capital and marginal costs. All of them have a p_nom_extendable set to True, meaning that capacities can be extended in the optimization.

The wind generators have a per unit limit for each time step, given by the weather potentials at the site.

[9]:
network.generators_t.p_max_pu.plot.area(subplots=True)
plt.tight_layout()
../_images/examples_ac-dc-lopf_13_0.png

Alright now we know how the network looks like, where the generators and lines are. Now, let’s perform a optimization of the operation and capacities.

[10]:
network.optimize();
WARNING:pypsa.consistency:The following lines have carriers which are not defined:
Index(['0', '1', '2', '3', '4', '5', '6'], dtype='object', name='Line')
WARNING:pypsa.consistency:The following lines have zero x, which could break the linear load flow:
Index(['2', '3', '4'], dtype='object', name='Line')
WARNING:pypsa.consistency:The following lines have zero r, which could break the linear load flow:
Index(['0', '1', '5', '6'], dtype='object', name='Line')
WARNING:pypsa.consistency:The following sub_networks have carriers which are not defined:
Index(['0', '1', '2', '3'], dtype='object', name='SubNetwork')
WARNING:pypsa.consistency:The following buses have carriers which are not defined:
Index(['London', 'Norwich', 'Norwich DC', 'Manchester', 'Bremen', 'Bremen DC',
       'Frankfurt', 'Norway', 'Norway DC'],
      dtype='object', name='Bus')
WARNING:pypsa.consistency:The following links have carriers which are not defined:
Index(['Norwich Converter', 'Norway Converter', 'Bremen Converter', 'DC link'], dtype='object', name='Link')
INFO:linopy.model: Solve problem using Highs solver
INFO:linopy.io: Writing time: 0.06s
INFO:linopy.constants: Optimization successful:
Status: ok
Termination condition: optimal
Solution: 187 primals, 467 duals
Objective: -3.47e+06
Solver model: available
Solver message: optimal

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-fix-p-lower, Link-fix-p-upper, Link-ext-p-lower, Link-ext-p-upper, Kirchhoff-Voltage-Law were not assigned to the network.
Running HiGHS 1.9.0 (git hash: fa40bdf): Copyright (c) 2024 HiGHS under MIT licence terms
Coefficient ranges:
  Matrix [1e-02, 1e+00]
  Cost   [9e-03, 3e+03]
  Bound  [2e+07, 2e+07]
  RHS    [9e-01, 1e+03]
Presolving model
371 rows, 186 cols, 890 nonzeros  0s
283 rows, 98 cols, 948 nonzeros  0s
283 rows, 98 cols, 948 nonzeros  0s
Presolve : Reductions: rows 283(-184); columns 98(-89); elements 948(-38)
Solving the presolved LP
Using EKK dual simplex solver - serial
  Iteration        Objective     Infeasibilities num(sum)
          0    -2.1204300613e+07 Pr: 110(90228.6); Du: 0(1.47603e-11) 0s
         96    -3.4740941308e+06 Pr: 0(0); Du: 0(1.57124e-13) 0s
Solving the original LP from the solution after postsolve
Model name          : linopy-problem-xmfd43g1
Model status        : Optimal
Simplex   iterations: 96
Objective value     : -3.4740941308e+06
Relative P-D gap    :  2.6807637892e-15
HiGHS run time      :          0.00
Writing the solution to /tmp/linopy-solve-37x9ecwd.sol

The objective is given by:

[11]:
network.objective
[11]:
-3474094.1308449395

Why is this number negative? It considers the starting point of the optimization, thus the existent capacities given by network.generators.p_nom are taken into account.

The real system cost are given by

[12]:
network.objective + network.objective_constant
[12]:
18440973.387434203

The optimal capacities are given by p_nom_opt for generators, links and storages and s_nom_opt for lines.

Let’s look how the optimal capacities for the generators look like.

[13]:
network.generators.p_nom_opt.div(1e3).plot.bar(ylabel="GW", figsize=(8, 3))
plt.tight_layout()
../_images/examples_ac-dc-lopf_21_0.png

Their production is again given as a time-series in network.generators_t.

[14]:
network.generators_t.p.div(1e3).plot.area(subplots=True, ylabel="GW")
plt.tight_layout()
../_images/examples_ac-dc-lopf_23_0.png

What are the Locational Marginal Prices in the network. From the optimization these are given for each bus and snapshot.

[15]:
network.buses_t.marginal_price.mean(1).plot.area(figsize=(8, 3), ylabel="Euro per MWh")
plt.tight_layout()
../_images/examples_ac-dc-lopf_25_0.png

We can inspect further quantities as the active power of AC-DC converters and HVDC link.

[16]:
network.links_t.p0
[16]:
Link Norwich Converter Norway Converter Bremen Converter DC link
snapshot
2015-01-01 00:00:00 -250.841318 674.584826 -423.743508 -317.997991
2015-01-01 01:00:00 315.068611 -116.727192 -198.341419 -317.997991
2015-01-01 02:00:00 350.761618 581.970687 -932.732306 -317.997991
2015-01-01 03:00:00 -85.772148 272.557949 -186.785801 -317.997991
2015-01-01 04:00:00 317.366721 -79.749487 -237.617234 -317.997991
2015-01-01 05:00:00 386.747627 -494.197717 107.450090 -317.997991
2015-01-01 06:00:00 900.000000 -257.520720 -642.479280 317.997991
2015-01-01 07:00:00 123.677319 971.918845 -1095.596165 -86.858742
2015-01-01 08:00:00 244.715828 850.880337 -1095.596165 317.997991
2015-01-01 09:00:00 820.022865 -86.851803 -733.171062 -83.684817
[17]:
network.lines_t.p0
[17]:
Line 0 1 2 3 4 5 6
snapshot
2015-01-01 00:00:00 79.474931 -38.105568 -52.967153 -303.808471 370.776355 -202.726815 -534.340860
2015-01-01 01:00:00 -449.464018 787.037310 -211.273022 103.795589 -12.931603 209.362553 -823.210906
2015-01-01 02:00:00 -181.265945 520.561348 -505.913540 -155.151921 426.818766 -248.676623 173.898185
2015-01-01 03:00:00 -45.472529 234.470017 -34.040733 -119.812880 152.745069 -232.717375 -743.788495
2015-01-01 04:00:00 -73.207796 295.420961 -227.198461 90.168260 10.418773 -240.105618 -883.817538
2015-01-01 05:00:00 -594.500052 1198.082901 -125.903894 260.843733 -233.353984 19.359009 -1030.848768
2015-01-01 06:00:00 -661.294714 1378.422245 -632.371427 267.628573 10.107853 -53.448436 319.386680
2015-01-01 07:00:00 -383.768641 540.906725 -469.925715 -346.248396 625.670450 393.715939 600.728881
2015-01-01 08:00:00 -778.280943 1444.069207 -522.116240 -277.400413 573.479924 229.294311 501.346379
2015-01-01 09:00:00 -465.576406 899.565461 -632.371427 187.651438 100.799635 78.617762 -248.566847

…or the active power injection per bus.

[18]:
network.buses_t.p
[18]:
Bus London Norwich Norwich DC Manchester Bremen Bremen DC Frankfurt Norway Norway DC
snapshot
2015-01-01 00:00:00 282.201747 -164.621247 -250.841318 -117.580500 -534.340860 -423.743508 534.340860 0.000000e+00 674.584826
2015-01-01 01:00:00 -658.826571 -577.674757 315.068611 1236.501328 -823.210906 -198.341419 823.210906 2.000888e-10 -116.727192
2015-01-01 02:00:00 67.410679 -769.237972 350.761618 701.827293 173.898185 -932.732306 -173.898185 0.000000e+00 581.970687
2015-01-01 03:00:00 187.244846 -467.187392 -85.772148 279.942546 -743.788495 -186.785801 743.788495 -1.000444e-10 272.557949
2015-01-01 04:00:00 166.897822 -535.526579 317.366721 368.628757 -883.817538 -237.617234 883.817538 3.999503e-10 -79.749487
2015-01-01 05:00:00 -613.859061 -1178.723892 386.747627 1792.582954 -1030.848768 107.450090 1030.848768 3.999503e-10 -494.197717
2015-01-01 06:00:00 -607.846278 -1431.870681 900.000000 2039.716959 319.386680 -642.479280 -319.386680 2.999059e-10 -257.520720
2015-01-01 07:00:00 -777.484580 -147.190786 123.677319 924.675366 600.728881 -1095.596165 -600.728881 -3.999503e-10 971.918845
2015-01-01 08:00:00 -1007.575254 -1214.774896 244.715828 2222.350150 501.346379 -1095.596165 -501.346379 0.000000e+00 850.880337
2015-01-01 09:00:00 -544.194169 -820.947699 820.022865 1365.141868 -248.566847 -733.171062 248.566847 2.000888e-10 -86.851803