Sie haben bereits gelernt, wie man Netze in pandapower und pandapipes aufsetzen und berechnen kann. Wesentliches Merkmal der Simulationsumgebung ist zudem die Möglichkeit, Netze miteinander zu koppeln, um analysieren zu können, wie sich der Zustand des einen auf den Zustand des anderen auswirkt. Um eine solche Berechnung durchzuführen, sind die folgenden Dinge vonnöten:
In diesem Tutorial wird eine P2G-Anlage und eine G2P-Einheit genutzt, um ein Strom- mit einem Gasnetz zu verbinden. Eingabewerte für diese Anlagen werden zu Beginn der Simulation festgelegt. Während der Simulation werden Ausgabegrößen anhand von Effizienzfaktoren berechnet.
Die Kopplung der Netze untereinenader erfolgt zunächst in drei Schritten. Eine Zeitreihenbetrachtung ist zunächst nicht enthalten, wird aufbauend auf der Kopplung aber hinzugefügt:
Im Gegensatz zu den bereits erstellten Netzwerken, machen wir uns diesmal nicht die Arbeit, Netze in der Konsole zu generieren. Stattdessen laden wir bereits vorhandene Netze einfach und definieren das Fluid.
from pandapower import networks as e_nw
net_power = e_nw.example_simple()
import pandapipes as ppipes
from pandapipes import networks as g_nw
net_gas = g_nw.gas_meshed_square()
# some adjustments:
net_gas.junction.pn_bar = net_gas.ext_grid.p_bar = 30
net_gas.pipe.diameter_m = 0.4
# set fluid:
ppipes.create_fluid_from_lib(net_gas, 'hydrogen', overwrite=True)
Anschließend wird der "Multinet"-Container erstellt. Er nimmt die zu verbindenden Netzwerke im Rahmen einer gekoppelten Anaylse auf. Jedes Netz muss einen eigenen Namen zugewiesen bekommen. Standardnamen sind "power" und "gas", aber es kann jeder beliebige Name gewählt werden. Die Zahl der Netze ist nicht begrenzt.
from pandapipes.multinet.create_multinet import create_empty_multinet, add_net_to_multinet
multinet = create_empty_multinet('tutorial_multinet')
add_net_to_multinet(multinet, net_power, 'power')
add_net_to_multinet(multinet, net_gas, 'gas')
Die einzelnen Netzwerke können über den Variablennamen oder den Multinet-Container angesprochen werden:
print(multinet.nets['power'])
print(multinet.nets['gas'])
This pandapower network includes the following parameter tables: - bus (7 elements) - load (1 element) - sgen (1 element) - gen (1 element) - switch (8 elements) - shunt (1 element) - ext_grid (1 element) - line (4 elements) - trafo (1 element) This pandapipes network includes the following parameter tables: - junction (6 elements) - junction_geodata (6 elements) - pipe (6 elements) - ext_grid (1 elements) - std_type (2 elements) - sink (1 elements). It contains the following fluid: Fluid hydrogen (gas) with properties: - density (InterExtra) - viscosity (InterExtra) - heat_capacity (InterExtra) - molar_mass (Constant) - compressibility (Linear) - der_compressibility (Constant) - lhv (Constant) - hhv (Constant) and uses the following component models: - Junction - Pipe - ExtGrid - Sink
print(net_power)
print(net_gas)
This pandapower network includes the following parameter tables: - bus (7 elements) - load (1 element) - sgen (1 element) - gen (1 element) - switch (8 elements) - shunt (1 element) - ext_grid (1 element) - line (4 elements) - trafo (1 element) This pandapipes network includes the following parameter tables: - junction (6 elements) - junction_geodata (6 elements) - pipe (6 elements) - ext_grid (1 elements) - std_type (2 elements) - sink (1 elements). It contains the following fluid: Fluid hydrogen (gas) with properties: - density (InterExtra) - viscosity (InterExtra) - heat_capacity (InterExtra) - molar_mass (Constant) - compressibility (Linear) - der_compressibility (Constant) - lhv (Constant) - hhv (Constant) and uses the following component models: - Junction - Pipe - ExtGrid - Sink
print(net_power is multinet.nets['power'])
print(net_gas is multinet.nets['gas'])
True True
Thus, changes to the networks will be found at both places.
Jetzt werden Elemente für die P2G und G2P-Controller hinzugefügt. Jeder Controller ist mit mindestens einem Element eines Netzwerks verbunden, von welchem er Elemente entnimmt oder dorthin überträgt. Im Falle von Kopplungspunkten für Multienergienetze gibt es zwei Verbindungen: Eine Verbindung zu einem Element des Gasnetzes und eine Verbindung zu einem Element des Stromnetzes.
Im Folgenden werden zunächst die Elemente erzeugt, mit denen die Controller verbunden werden:
import pandapower as ppower
import pandapipes as ppipes
p2g_id_el = ppower.create_load(net_power, bus=3, p_mw=2, name="power to gas consumption")
p2g_id_gas = ppipes.create_source(net_gas, junction=1, mdot_kg_per_s=0, name="power to gas feed in")
g2p_id_gas = ppipes.create_sink(net_gas, junction=1, mdot_kg_per_s=0.1, name="gas to power consumption")
g2p_id_el = ppower.create_sgen(net_power, bus=5, p_mw=0, name="fuel cell feed in")
Jetzt werden die eigentlichen Controller erzeugt und initialisiert. Die Netzelemente, die mit den Controllern verbunden sind, werden als Parameter übergeben. Der Controller agiert damit als Kopplungspunkt zwischen den Netzen.
from pandapipes.multinet.control.controller.multinet_control import P2GControlMultiEnergy, \
G2PControlMultiEnergy
p2g_ctrl = P2GControlMultiEnergy(multinet, p2g_id_el, p2g_id_gas, efficiency=0.7,
name_power_net="power", name_gas_net="gas")
g2p_ctrl = G2PControlMultiEnergy(multinet, g2p_id_el, g2p_id_gas, efficiency=0.65,
name_power_net="power", name_gas_net="gas")
Intern arbeiten die Controller mit einem importierten Brennwert. Dieser stammt aus den Fluideigenschaften des Netzes
pandapipes/properties/[fluid_name]/higher_heating_value.txt)
Controller können auf vielfältige Weise eingesetzt werden. Alle Aspekte kann dieses Tutorial nicht abdecken. Weitere Infos finden Sie aber unter:https://pandapower.readthedocs.io/en/latest/control/control_loop.html
Jetzt, wo die Netze und die Controller erstellt worden sind, kann die Berechnung gestartet werden. Es ist bekannt, dass die Berechnung von pandapower und pandapipes-Netzen mit den Kommandos powerflow
bzw. pipeflow
gestartet wird. Werden gekoppelte Netze berechnet, so wird stattdessen der Befehl run_control
eingesetzt, der intern die Berechnung der Teilnetze startet, aber auch dafür sorgt, dass die Controller aufgerufen werden.
from pandapipes.multinet.control.run_control_multinet import run_control
run_control(multinet)
Nach der Berechnung wurden die Ausgabewerte aktualisiert und entsprechen der Eingangsleistung multipliziert mit dem Effizienzfaktor.
print(net_gas.source.loc[p2g_id_gas, 'mdot_kg_per_s'])
print(net_power.sgen.loc[g2p_id_el, 'p_mw'])
0.010132528845283116 8.98097616
Zusammengefasst:
import pandapipes as ppipes
import pandapower as ppower
from pandapipes import networks as g_nw
from pandapower import networks as e_nw
from pandapipes.multinet.create_multinet import create_empty_multinet, add_net_to_multinet
from pandapipes.multinet.control.controller.multinet_control import P2GControlMultiEnergy, G2PControlMultiEnergy
from pandapipes.multinet.control.run_control_multinet import run_control
# get networks:
net_power = e_nw.example_simple()
net_gas = g_nw.gas_meshed_square()
# some adjustments:
net_gas.junction.pn_bar = net_gas.ext_grid.p_bar = 30
net_gas.pipe.diameter_m = 0.4
net_gas.controller.rename(columns={'controller': 'object'}, inplace=True) # due to new version
# set fluid:
fluid = {'name':'hydrogen', 'cal_value':38.4}
ppipes.create_fluid_from_lib(net_gas, fluid['name'], overwrite=True)
# create multinet and add networks:
multinet = create_empty_multinet('tutorial_multinet')
add_net_to_multinet(multinet, net_power, 'power')
add_net_to_multinet(multinet, net_gas, 'gas')
# create elements corresponding to conversion units:
p2g_id_el = ppower.create_load(net_power, bus=3, p_mw=2, name="power to gas consumption")
p2g_id_gas = ppipes.create_source(net_gas, junction=1, mdot_kg_per_s=0, name="power to gas feed in")
g2p_id_gas = ppipes.create_sink(net_gas, junction=1, mdot_kg_per_s=0.1, name="gas to power consumption")
g2p_id_el = ppower.create_sgen(net_power, bus=5, p_mw=0, name="fuel cell feed in")
# create coupling controllers:
p2g_ctrl = P2GControlMultiEnergy(multinet, p2g_id_el, p2g_id_gas, efficiency=0.7,
name_power_net="power", name_gas_net="gas")
g2p_ctrl = G2PControlMultiEnergy(multinet, g2p_id_el, g2p_id_gas, efficiency=0.65,
name_power_net="power", name_gas_net="gas")
# run simulation:
run_control(multinet)
In der Regel möchte man die Zustände des Systems für den Fall ermitteln, dass Eingabedaten mit der Zeit variieren. Dies kann dann der Fall sein, wenn z. B. Lasten ein zeitlich nicht konstantes Profil aufweisen. Die Controller, die wir im vorigen Abschnitt eingeführt haben, bilden selbst kein zeitabhängiges Verhalten ab. Sie können aber mit einem sogenennaten ConstController kombiniert werden, welche Zeitreihen einlesen und in jedem Zeitschritt einen anderen Wert zur Verfügung stellen kann. Es gibt Funktionen, welche die kombinierten Controller direkt erzeugen können. Die Namen dieser Funktionen sind coupled_p2g_const_control
und coupled_g2p_const_control
.
Das Beispiel des letzten Abschnitts wird jetzt um eine zeitabhängige Simulation erweitert. Der folgende Block richtet die Netze wieder ein. Noch fehlen allerdings die Controller.
# prepare just like before
net_power = e_nw.example_simple()
net_gas = g_nw.gas_meshed_square()
net_gas.junction.pn_bar = net_gas.ext_grid.p_bar = 30
net_gas.pipe.diameter_m = 0.4
net_gas.controller.rename(columns={'controller': 'object'}, inplace=True) # due to new version
fluid = {'name':'hydrogen', 'cal_value':38.4}
ppipes.create_fluid_from_lib(net_gas, fluid['name'], overwrite=True)
multinet = create_empty_multinet('tutorial_multinet')
add_net_to_multinet(multinet, net_power, 'power_net')
add_net_to_multinet(multinet, net_gas, 'gas_net')
p2g_id_el = ppower.create_load(net_power, bus=3, p_mw=2, name="power to gas consumption")
p2g_id_gas = ppipes.create_source(net_gas, junction=1, mdot_kg_per_s=0, name="power to gas feed in")
g2p_id_gas = ppipes.create_sink(net_gas, junction=1, mdot_kg_per_s=0.1, name="gas to power consumption")
g2p_id_el = ppower.create_sgen(net_power, bus=5, p_mw=0, name="fuel cell feed in")
Der folgende Block erstellt eine Funktion, die Zufallsdaten für die Zeitreihen erzeugt. Insgesamt werden 10 Zeitschritte berechnet, wie am Parameter der Funktion zu erkennen ist. Der mit Zufallszahlen gefüllte pandas DataFrame wird am Ende der Funktion als Attribut eines Objekts der DFData-Klasse gespeichert. Diese wird von pandapower definiert und dient dem einfacheren Zugriff auf die im Frame gespeichertern Daten. Alle Controller können mit dieser Datenstruktur umgehen.
from pandas import DataFrame
from numpy.random import random
from pandapower.timeseries import DFData
def create_data_source(n_timesteps=10):
profiles = DataFrame()
profiles['power to gas consumption'] = random(n_timesteps) * 2 + 1
profiles['gas to power consumption'] = random(n_timesteps) * 0.1
ds = DFData(profiles)
return profiles, ds
profiles, ds = create_data_source(10)
Im Rahmen von zeitabhängigen Simulationen fallen größere Ergebnismengen an. Für jeden Zeitschritt kann der gesamte Zustand des Netzes gespeichert und anschließend ausgewertet werden. Für die Ergebnisse zeitabhängiger Simulationen wird eine weitere Datenstruktur bereitgestellt: Der OutputWriter. Auch bei diesem handelt es sich um eine Klasse.
Die folgende Funktion legt für jedes Teilnetz einen eigenen OutputWriter an und speichert diese in einem Python-dictionary. Für jedes Netz wird eine Liste auszugebener Größen, die log_variables
, erstellt. Es können Spalten verschiedener Ergebnistabellen kombiniert werden. Die erstellten Listen werden anschließend im OutputWriter gespeichert.
from os.path import join, dirname
from pandapower.timeseries import OutputWriter
def create_output_writers(multinet, time_steps=None):
nets = multinet["nets"]
ows = dict()
for key_net in nets.keys():
ows[key_net] = {}
if isinstance(nets[key_net], ppower.pandapowerNet):
log_variables = [('res_bus', 'vm_pu'),
('res_line', 'loading_percent'),
('res_line', 'i_ka'),
('res_bus', 'p_mw'),
('res_bus', 'q_mvar'),
('res_load', 'p_mw'),
('res_load', 'q_mvar')]
ow = OutputWriter(nets[key_net], time_steps=time_steps,
log_variables=log_variables,
output_path=join(dirname('__file__'),'timeseries', 'results', 'power'),
output_file_type=".csv")
ows[key_net] = ow
elif isinstance(nets[key_net], ppipes.pandapipesNet):
log_variables = [('res_sink', 'mdot_kg_per_s'),
('res_source', 'mdot_kg_per_s'),
('res_ext_grid', 'mdot_kg_per_s'),
('res_pipe', 'v_mean_m_per_s'),
('res_junction', 'p_bar'),
('res_junction', 't_k')]
ow = OutputWriter(nets[key_net], time_steps=time_steps,
log_variables=log_variables,
output_path=join(dirname('__file__'), 'timeseries', 'results', 'gas'),
output_file_type=".csv")
ows[key_net] = ow
else:
raise AttributeError("Could not create an output writer for nets of kind " + str(key_net))
return ows
ows = create_output_writers(multinet, 10)
Jetzt werden die bereits erwähnten Controller hinzugefügt. Es ist zu beachten, dass die data_source, welche die Zeitreihen beschreibt, als Parameter mit übergeben wird. So weiß der jeweilige Controller, woher er die Eingangsdaten des aktuellen Zeitschritts nehmen soll.
from pandapipes.multinet.control.controller.multinet_control import coupled_p2g_const_control, \
coupled_g2p_const_control
coupled_p2g_const_control(multinet, p2g_id_el, p2g_id_gas,
name_power_net="power_net", name_gas_net="gas_net",
profile_name='power to gas consumption', data_source=ds,
p2g_efficiency=0.7)
coupled_g2p_const_control(multinet, g2p_id_el, g2p_id_gas,
name_power_net="power_net", name_gas_net="gas_net",
element_type_power="sgen",
profile_name='gas to power consumption', data_source=ds,
g2p_efficiency=0.65)
(This ConstControl has the following parameters: index: 0 json_excludes: ['self', '__class__'] recycle: False, This G2PControlMultiEnergy has the following parameters: index: 1 json_excludes: ['self', '__class__'] recycle: False)
Die ConstControllers werden in den Teilnetzen gespeichert. Die Kopplungscontroller, welche die Verbindung zwischen den Netzen herstellen, befinden sich dagegen im multinet.
print(multinet.controller)
print(net_power.controller)
print(net_gas.controller)
object in_service order level recycle 0 P2GControlMultiEnergy True 1.0 0 False 1 G2PControlMultiEnergy True 1.0 0 False object in_service order level recycle 0 ConstControl True 0.0 0 True object in_service order level recycle 0 ConstControl True 0.0 0 True
Die Simulation wird mit dem Befehl run_timeseries
gestartet. Zu beachten sind die Parameter der run_timeseries
-Funktion. Sowohl die Zeitschrittweite, als auch die erstellte OutputWriter-Struktur wird der Funktion mit übergeben. Nach der Simulation kann auf die Outputwriter zugegriffen werden, um die gewünschten Größen zu extrahieren. Übriges: Innerhalb der run_timeseries
-Funktion ruft pandapipes wieder die bereits bekannte run_control
-Funktion auf. Im Wesentlichen wird nur eine Schleife um letztere Funktion gelegt, um die Berechnung für die angegebene Zahl von Zeitschritten zu wiederholen.
from pandapipes.multinet.timeseries.run_time_series_multinet import run_timeseries
run_timeseries(multinet, time_steps=range(10), output_writers=ows)
Progress: |██████████████████████████████████████████████████| 100.0% Complete
Nach dem Durcharbeiten dieses Tutorials sollten Sie:
- verstanden haben, wie die pandaplan-Datenstruktur zur Berechnung von Multienergienetzen aufgebaut ist.
- wissen, was ein Controller ist und wie dieser als Kopplungspunkt zwischen Netzen aufgebaut werden kann.
- welche zusaetzlichen Elemente Sie fuer die Zeitreihenberechnung benötigen.
- Mit welchen Befehlen die Berechnung eines gekoppelten Netzes gestartet werden kann.