Physik der sozio-ökonomischen Systeme mit dem Computer

(Physics of Socio-Economic Systems with the Computer)

Vorlesung gehalten an der J.W.Goethe-Universität in Frankfurt am Main

(Wintersemester 2020/21)

von Dr.phil.nat. Dr.rer.pol. Matthias Hanauske

Frankfurt am Main 02.11.2020

Zweiter Vorlesungsteil:

Theorie der komplexen Netzwerke am Beispiel des europäischen Stromnetzwerkes

Introduction

PyPSA “Python for Power System Analysis” ( see e.g. https://pypsa.org/index.html and https://pypsa.readthedocs.io/en/latest/index.html ) is a free software toolbox for simulating and optimising modern electric power systems. We will focus on the complex electric power network of Europe which consists of several thousand of fundamental electrically node (buses) which are connected with edges. These edges can be connections with controllable active power (links) or transmission lines, overhead lines and cables (lines). Additionally, energy sources (power generators), power consumers (loads), storage units and converters (transformers) can be attached to the electric power nodes.

Zunächst importieren wir das in PyPSA implementierte europäischen Stromnetzwerk (Download unter http://itp.uni-frankfurt.de/~hanauske/VPSOC/VPSOCorona/ElectrikNetworks/ElectricNetworksEUR.zip).

In [1]:
import pypsa
n = pypsa.Network('../../E-Netz/python/data/elec.nc')
WARNING:pypsa.io:
Importing PyPSA from older version of PyPSA than current version 0.16.1.
Please read the release notes at https://pypsa.org/doc/release_notes.html
carefully to prepare your network for import.

INFO:pypsa.io:Imported network elec.nc has buses, carriers, generators, lines, links, loads, storage_units, transformers

und definieren einige globale Größen:

In [2]:
NKn=len(n.buses)
NLinks=len(n.links)
NLines=len(n.lines)
NGenerators=len(n.generators)
NLoads=len(n.loads)
NStorage=len(n.storage_units)

print("Anzahl der Knoten: ",NKn)
print("Anzahl der Links: ",NLinks)
print("Anzahl der Lines: ",NLines)
print("Anzahl der Generatoren: ",NGenerators)
print("Anzahl der Stromverbraucher: ",NLoads)
print("Anzahl der Speichereinheiten",NStorage)
Anzahl der Knoten:  4973
Anzahl der Links:  61
Anzahl der Lines:  6000
Anzahl der Generatoren:  7773
Anzahl der Stromverbraucher:  2968
Anzahl der Speichereinheiten 6501

Jeder Knoten und jede Kante (Generator, Stromverbraucher, Speichereinheit,Link,Line) hat einen ihm eigenen Indexwert; z.B. Knoten i=12:

In [3]:
iKnoten=list(n.buses.index.values)
iGenerator=list(n.generators.index.values)
iLoads=list(n.loads.index.values)
iLinks=list(n.links.index.values)
iLines=list(n.lines.index.values)
#loadbuses=list(n.loads_t.p_set.iloc[0].index)
iStorage=list(n.storage_units.index.values)
iKnoten[12]
Out[3]:
'6975'

Gewisse Eigenschaften des Knotens (z.B. sein Ort (x,y) und in welchem Land er sich befindet) erhält man mit:

In [4]:
n.buses.loc[iKnoten[12]]
Out[4]:
v_nom                     220
symbol                  joint
under_construction      False
tags                         
x                    -7.30498
y                     40.6001
carrier                    AC
country                    PT
substation_lv           False
substation_off          False
type                         
v_mag_pu_set                1
v_mag_pu_min                0
v_mag_pu_max              inf
control                    PQ
sub_network                  
Name: 6975, dtype: object
In [5]:
n.buses.loc[iKnoten[4]].country
Out[5]:
'PT'

Im folgenden übertragen wir zunächst die grundlegenden netzwerkrelevanten Größen des europäischen Stromnetzwerk von PyPSA in ein networkx Netzwerk. Da wir später auch ein Spiel auf diesem Netzwerk definieren werden, erzeugen wir zusätzlich an jedem Knoten einen Spieler, welcher gewisse Eigenschaften hat und eigenständige Strategieentscheidungen wählen kann.

In [6]:
import networkx as nx
import numpy as np
from random import randint
from itertools import groupby
G = nx.Graph()
In [7]:
#Erzeugung einer Liste zum Speichern der Knoten/Spieler Eigenschaften
PropPlayers=np.zeros([NKn,10])

#Erzeugung der Knoten (buses), Knotenattribut ist die Busnummer
for k in range(0,NKn):
  G.add_node(k, bus=iKnoten[k])
  PropPlayers[k].flat[0] = k #Knotennummer
  PropPlayers[k].flat[1] = n.buses.loc[iKnoten[k]].x #x-Koordinate auf dem 2-D Gitter
  PropPlayers[k].flat[2] = n.buses.loc[iKnoten[k]].y #y-Koordinate auf dem 2-D Gitter
  strategy=randint(0, 100)/100.0 #Zufaellige Strategienwahl (0,1)
  PropPlayers[k].flat[3] = strategy 
  PropPlayers[k].flat[4] = iKnoten[k]#Busnummer
  PropPlayers[k].flat[7] = 0
  if iKnoten[k] in iLoads:
    aa=list(n.loads_t.p_set[iKnoten[k]])
    PropPlayers[k].flat[7] = sum(aa)/float(len(aa))#Durchschnittlicher Load am Knoten
#print('Anzahl nodes network',len(G))
In [8]:
#Erzeugung der Kanten (Links und Lines), Attribut ist type=link oder type=line
for link_num in range(0,NLinks):
  link_bus0=iKnoten.index(n.links.loc[iLinks[link_num]].bus0)
  link_bus1=iKnoten.index(n.links.loc[iLinks[link_num]].bus1)
  G.add_edge(link_bus0,link_bus1, type='link')
for line_num in range(0,NLines):
  line_bus0=iKnoten.index(n.lines.loc[iLines[line_num]].bus0)
  line_bus1=iKnoten.index(n.lines.loc[iLines[line_num]].bus1)
  G.add_edge(line_bus0,line_bus1, type='line')
In [9]:
#Konstruktion mehrerer laenderspezifischer Teilnetzwerke
#print('Anzahl nodes DE',len(G_DE))
list_DE=[]
list_NL=[]
list_FR=[]
list_PL=[]
list_CZ=[]
list_BE=[]
list_CH=[]
list_AT=[]
list_LU=[]
list_DK=[]
#['DE', 'PT', 'NL', 'IT', 'ES', 'IE', 'HU', 'GR', 'FR', 'AL', 'MK', 'CH', 'RO', 'BG', 'RS', 'HR', 'BA', 'ME', 'BE', 'AT', 'SK', 'CZ', 'SI', 'PL', 'LU', 'GB', 'LV', 'LT', 'DK', 'SE', 'EE', 'NO', 'FI']
for k in range(0,NKn):
  PropPlayers[k].flat[5] = len(G.edges([k]))
  if n.buses.loc[iKnoten[k]].country == 'DE':
    PropPlayers[k].flat[6] = 1
    list_DE.append(k)
  if n.buses.loc[iKnoten[k]].country == 'NL':
    PropPlayers[k].flat[6] = 3
    list_NL.append(k)
  if n.buses.loc[iKnoten[k]].country == 'FR':
    PropPlayers[k].flat[6] = 9
    list_FR.append(k)
  if n.buses.loc[iKnoten[k]].country == 'PL':
    PropPlayers[k].flat[6] = 24
    list_PL.append(k)
  if n.buses.loc[iKnoten[k]].country == 'CZ':
    PropPlayers[k].flat[6] = 22
    list_CZ.append(k)
  if n.buses.loc[iKnoten[k]].country == 'BE':
    PropPlayers[k].flat[6] = 14
    list_BE.append(k)
  if n.buses.loc[iKnoten[k]].country == 'CH':
    PropPlayers[k].flat[6] = 12
    list_CH.append(k)
  if n.buses.loc[iKnoten[k]].country == 'AT':
    PropPlayers[k].flat[6] = 20
    list_AT.append(k)
  if n.buses.loc[iKnoten[k]].country == 'LU':
    PropPlayers[k].flat[6] = 25
    list_LU.append(k)
  if n.buses.loc[iKnoten[k]].country == 'DK':
    PropPlayers[k].flat[6] = 29
    list_DK.append(k)
#print(list_DE)
country_colorlist=["green","red","blue","sienna","orange","darkcyan",
                   "darkviolet","deeppink","peru","palegoldenrod"]
G_DE = G.subgraph(list_DE)
G_NL = G.subgraph(list_NL)
G_FR = G.subgraph(list_FR)
G_PL = G.subgraph(list_PL)
G_CZ = G.subgraph(list_CZ)
G_BE = G.subgraph(list_BE)
G_CH = G.subgraph(list_CH)
G_AT = G.subgraph(list_AT)
G_LU = G.subgraph(list_LU)
G_DK = G.subgraph(list_DK)

Wir möchten nun einen Teil des Netzwerkes darstellen. Die Visualisierung kann man z.B. mittels der "Plotly Python Open Source Graphing Library" erstellen. In dieser Bibliothek ist es möglich interaktive Abbildungen in Python darzustellen ( siehe https://plotly.com/python/ ). Wir ordnen ein jedem Knoten ein Textfeld zu, so dass der Benutzer (wenn er mit der Maus auf den Knoten zeigt) die Knotennummer und den zugehörigen Knotengrad sieht. Zusätzlich veranschaulichen wir ...

In [10]:
degree_sequence = []
for i in G.nodes():
    degree_sequence.append(G.degree(i))
maxk=max(degree_sequence)
In [13]:
xx=[3,7]
yy=[48,52]
xx=[-10,20]
yy=[35,55]
plotnode=[]
node_x=[]
node_y=[]
node_z=[]
group=[]
countrycolor=[]
for k in range(NKn):
    if PropPlayers[k][1]>xx[0] and PropPlayers[k][1]<xx[1] and PropPlayers[k][2]>yy[0] and PropPlayers[k][2]<yy[1]:
        plotnode.append(int(PropPlayers[k][0]))
        node_x.append(PropPlayers[k][1])
        node_y.append(PropPlayers[k][2])
        node_z.append(0)
        group.append(degree_sequence[k])
#        group.append(PropPlayers[k][7])
        countrycolor.append(int(PropPlayers[k][6]))
edge_x = []
edge_y = []
edge_z = []
edgepl_x = []
edgepl_y = []
for edge in G.edges():
    if edge[0] in plotnode and edge[1] in plotnode:
        edge_x+=[PropPlayers[edge[0]][1],PropPlayers[edge[1]][1], None]
        edge_y+=[PropPlayers[edge[0]][2],PropPlayers[edge[1]][2], None]
        edge_z+=[0,0, None]
        edgepl_x.append([PropPlayers[edge[0]][1],PropPlayers[edge[1]][1]])
        edgepl_y.append([PropPlayers[edge[0]][2],PropPlayers[edge[1]][2]])
In [14]:
import numpy as np
import matplotlib.pyplot as plt 
import matplotlib
from mpl_toolkits.mplot3d import Axes3D
params = {
    'figure.figsize'    : [8,5],
    'text.usetex'       : True,
    'axes.titlesize' : 14,
    'axes.labelsize' : 16,  
    'xtick.labelsize' : 14 ,
    'ytick.labelsize' : 14 
}
matplotlib.rcParams.update(params) 
In [15]:
xyconsumers=[5,1,0]
xybuses=[5,3,1]
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.scatter(node_x, node_y, zs=0, s=1, marker='o', c=countrycolor)
for i in range(len(plotnode)):
    ax.plot(edgepl_x[i], edgepl_y[i],[0,0], linewidth=0.1, c="grey")
#ax.scatter(xybuses[0], xybuses[1], zs=xybuses[2], s=40, marker='o', c="green")
#ax.view_init(azim=-65, elev=35)
#ax.set_xlim(0, 5)
#ax.set_ylim(0, 5)
ax.set_xlabel(r"$\rm x$")
ax.set_ylabel(r"$\rm y$")
ax.set_zlabel(r"$\rm t$");

Die obige Darstellung der europäischen Netzwerk-Knoten zeigt gut die geografische Europakarte. Wir wollen uns diese Struktur und die zugehörigen Knotengrade der Netzwerk-Busse mittels plotly veranschaulichen:

In [16]:
import plotly.graph_objects as go
In [17]:
node_trace = go.Scatter(
    x=node_x, y=node_y,
    mode='markers',
    hoverinfo='text',
    marker=dict(
        showscale=True,
        # colorscale options
        #'Greys' | 'YlGnBu' | 'Greens' | 'YlOrRd' | 'Bluered' | 'RdBu' |
        #'Reds' | 'Blues' | 'Picnic' | 'Rainbow' | 'Portland' | 'Jet' |
        #'Hot' | 'Blackbody' | 'Earth' | 'Electric' | 'Viridis' |
        colorscale='YlGnBu',
        reversescale=True,
        color=[],
        opacity=0.5,
        size=5,
        colorbar=dict(
            thickness=15,
            title='Knotengrad',
            xanchor='left',
            titleside='right'
        ),
        line_width=1))

edge_trace = go.Scatter(
    x=edge_x, y=edge_y,
    line=dict(width=0.5, color='grey'),
    hoverinfo='none',
    mode='lines')

node_text = []
for node in plotnode:
    node_text.append('Knoten '+str(node)+', Knotengrad = '+str(G.degree(node)))
node_trace.marker.color = degree_sequence
node_trace.text = node_text
In [18]:
fig = go.Figure(data=[edge_trace, node_trace],
             layout=go.Layout(
                showlegend=False,
                hovermode='closest',
                margin=dict(b=20,l=5,r=5,t=40),
                xaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
                yaxis=dict(showgrid=False, zeroline=False, showticklabels=False))
                )
fig.show()
In [19]:
edge_trace=go.Scatter3d(x=edge_x,
               y=edge_y,
               z=edge_z,
               mode='lines',
               line=dict(color='black', width=0.3),
               hoverinfo='none'
               )

node_trace=go.Scatter3d(x=node_x,
               y=node_y,
               z=node_z,
               mode='markers',
               name='actors',
               marker=dict(symbol='circle',
                             size=2,
                             color=countrycolor,
#                             colorscale='Jet',
                             line=dict(color='black', width=0.1)
                             ),
               text=node_text,
               opacity=0.9,
               hoverinfo='text'
               )
In [20]:
axis=dict(showbackground=False,
          backgroundcolor="white",
          showline=False,
          zeroline=False,
          showgrid=True,
          gridcolor="rgb(244, 233, 245)",
          showticklabels=False,
          showaxeslabels=False,
          )

layout = go.Layout(
         width=700,
         height=700,
         showlegend=False,
         scene=dict(
             xaxis=dict(axis),
             yaxis=dict(axis),
             zaxis=dict(axis),
        ),
    margin=dict(b=20,l=10,r=10,t=10),
    hovermode='closest',
   )
data=[node_trace,edge_trace]
fig=go.Figure(data=data, layout=layout)
fig.show()

Weitere Eigenschaften des Netzwerkes können mittels der folgenden python Scripts berechnet werden (Download unter http://itp.uni-frankfurt.de/~hanauske/VPSOC/VPSOCorona/ElectrikNetworks/ElectricNetworks-pythonScripts.zip):

Die Verteilungsfunktion der Knotengrade: pic1.jpg

Struktur des Netzwerkes in räumlicher Darstellung: pic2.jpg

Struktur des Netzwerkes mit den entsprechenden Loads in räumlicher Darstellung: pic2a.jpg

Loads in räumlicher Darstellung: pic2c.jpg

Struktur des Netzwerkes im kamada_kawai_layout: pic3.jpg