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: https://github.com/PyPSA/PyPSA.

[1]:
import pypsa
import numpy as np
import pandas as pd
import os
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
%matplotlib inline
plt.rc('figure', figsize=(8,8))
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
/tmp/ipykernel_155/3205102310.py in <module>
      4 import os
      5 import matplotlib.pyplot as plt
----> 6 import cartopy.crs as ccrs
      7 get_ipython().run_line_magic('matplotlib', 'inline')
      8 plt.rc('figure', figsize=(8,8))

ModuleNotFoundError: No module named 'cartopy'
[2]:
network = pypsa.examples.ac_dc_meshed(from_master=True)
WARNING:pypsa.io:
Importing PyPSA from older version of PyPSA than current version.
Please read the release notes at https://pypsa.org/doc/release_notes.html
carefully to prepare your network for import.
Currently used PyPSA version [0, 18, 1], imported network file PyPSA version [0, 17, 1].

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]:
name
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=.3)
plt.tight_layout()
WARNING:pypsa.plot:Cartopy needs to be installed to use `geomap=True`.
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
/tmp/ipykernel_155/922412573.py in <module>
      1 network.plot(line_colors=lines_current_type.map(lambda ct: "r" if ct=="DC" else "b"),
      2              title='Mixed AC (blue) - DC (red) network - DC (cyan)',
----> 3             color_geomap=True, jitter=.3)
      4 plt.tight_layout()

~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pypsa/plot.py in plot(n, margin, ax, geomap, projection, bus_colors, bus_alpha, bus_sizes, bus_cmap, line_colors, link_colors, transformer_colors, line_widths, link_widths, transformer_widths, line_cmap, link_cmap, transformer_cmap, flow, branch_components, layouter, title, boundaries, geometry, jitter, color_geomap)
    144
    145         if projection is None:
--> 146             projection = get_projection_from_crs(n.srid)
    147
    148         if ax is None:

~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pypsa/plot.py in get_projection_from_crs(crs)
    330     if crs == 4326:
    331         # if data is in latlon system, return default map with latlon system
--> 332         return ccrs.PlateCarree()
    333     try:
    334         return ccrs.epsg(crs)

NameError: name 'ccrs' is not defined
[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]:
attribute carrier slack_bus obj n_branches n_buses
0 AC Manchester SubNetwork 0 3 3
1 DC Norwich DC SubNetwork 1 3 3
2 AC Frankfurt SubNetwork 2 1 2
3 AC Norway SubNetwork 3 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 capital_cost efficiency marginal_cost p_nom p_nom_extendable p_nom_min carrier control type ... 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
name
Manchester Wind Manchester 2793.651603 1.000000 0.110000 80.0 True 100.0 wind Slack ... 0.0 0 0 1 0 NaN NaN 1.0 1.0 0.0
Manchester Gas Manchester 196.615168 0.350026 4.532368 50000.0 True 0.0 gas PQ ... 0.0 0 0 1 0 NaN NaN 1.0 1.0 0.0
Norway Wind Norway 2184.374796 1.000000 0.090000 100.0 True 100.0 wind Slack ... 0.0 0 0 1 0 NaN NaN 1.0 1.0 0.0
Norway Gas Norway 158.251250 0.356836 5.892845 20000.0 True 0.0 gas PQ ... 0.0 0 0 1 0 NaN NaN 1.0 1.0 0.0
Frankfurt Wind Frankfurt 2129.456122 1.000000 0.100000 110.0 True 100.0 wind Slack ... 0.0 0 0 1 0 NaN NaN 1.0 1.0 0.0
Frankfurt Gas Frankfurt 102.676953 0.351666 4.086322 80000.0 True 0.0 gas PQ ... 0.0 0 0 1 0 NaN NaN 1.0 1.0 0.0

6 rows × 30 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.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_155/3801474310.py in <module>
----> 1 network.lopf();

~/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'

The objective is given by:

[11]:
network.objective
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
/tmp/ipykernel_155/2252453118.py in <module>
----> 1 network.objective

AttributeError: 'Network' object has no attribute 'objective'

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
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
/tmp/ipykernel_155/3503365564.py in <module>
----> 1 network.objective + network.objective_constant

AttributeError: 'Network' object has no attribute 'objective'

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()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/tmp/ipykernel_155/3043318860.py in <module>
----> 1 network.generators_t.p.div(1e3).plot.area(subplots=True, ylabel='GW')
      2 plt.tight_layout()

~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pandas/plotting/_core.py in area(self, x, y, **kwargs)
   1494             >>> ax = df.plot.area(x='day')
   1495         """
-> 1496         return self(kind="area", x=x, y=y, **kwargs)
   1497
   1498     def pie(self, **kwargs):

~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pandas/plotting/_core.py in __call__(self, *args, **kwargs)
    970                     data.columns = label_name
    971
--> 972         return plot_backend.plot(data, kind=kind, **kwargs)
    973
    974     __call__.__doc__ = __doc__

~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pandas/plotting/_matplotlib/__init__.py in plot(data, kind, **kwargs)
     69             kwargs["ax"] = getattr(ax, "left_ax", ax)
     70     plot_obj = PLOT_CLASSES[kind](data, **kwargs)
---> 71     plot_obj.generate()
     72     plot_obj.draw()
     73     return plot_obj.result

~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pandas/plotting/_matplotlib/core.py in generate(self)
    284     def generate(self):
    285         self._args_adjust()
--> 286         self._compute_plot_data()
    287         self._setup_subplots()
    288         self._make_plot()

~/checkouts/readthedocs.org/user_builds/pypsa-docs-staging/envs/latest/lib/python3.7/site-packages/pandas/plotting/_matplotlib/core.py in _compute_plot_data(self)
    451         # no non-numeric frames or series allowed
    452         if is_empty:
--> 453             raise TypeError("no numeric data to plot")
    454
    455         self.data = numeric_data.apply(self._convert_to_ndarray)

TypeError: no numeric data to plot

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 futher quantities as the active power of AC-DC converters and HVDC link.

[16]:
network.links_t.p0
[16]:
snapshot
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
[17]:
network.lines_t.p0
[17]:
snapshot
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

…or the active power injection per bus.

[18]:
network.buses_t.p
[18]:
snapshot
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